Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342// ac-native.c — Sub-second boot AC piece runner// Runs as PID 1 in a minimal initramfs.// UEFI → EFI stub kernel → this binary → piece.mjs
#include <stdio.h>#include <stdlib.h>#include <stdarg.h>#include <string.h>#include <signal.h>#include <time.h>#include <math.h>#include <unistd.h>#include <sys/wait.h>#include <fcntl.h>#include <dirent.h>#include <errno.h>#include <sys/mount.h>#include <sys/statvfs.h>#include <sys/stat.h>#include <sys/reboot.h>#include <linux/reboot.h>#include <linux/input.h>#include <linux/fs.h> // BLKRRPART for forced partition re-read (install)#include <sys/ioctl.h>#include <pthread.h>
#include "drm-display.h"#include "framebuffer.h"#include "graph.h"#include "font.h"#include "input.h"#include "audio.h"#include "wifi.h"#include "tts.h"#include "js-bindings.h"#include "machines.h"#include "lanserv.h"#include "recorder.h"#include "camera.h"#include <openssl/sha.h>#ifdef USE_WAYLAND#include "wayland-display.h"#endif
static volatile int running = 1;static FILE *logfile = NULL;static volatile int log_dirty = 0;int ac_log_stderr_muted = 0; // When set, ac_log skips stderr (PTY active)int voice_off = 1; // Keystroke TTS disabled by default (enable with voice:on in config)static int is_removable(const char *blkname);static void get_parent_block(const char *part, char *out, int out_sz);
// ── Performance logger (crash-resilient chunked files) ──// Writes /mnt/perf/NNNN.csv every 30s, each chunk fsync'd and closed.// On hard crash you lose at most 30 seconds. Keeps last 5 minutes (10 chunks).#define PERF_CHUNK_SECS 30#define PERF_CHUNK_FRAMES (60 * PERF_CHUNK_SECS) // 1800 frames per chunk#define PERF_MAX_CHUNKS 10 // 10 × 30s = 5 minutes#define PERF_BUF_SIZE PERF_CHUNK_FRAMES
typedef struct { uint32_t frame; uint16_t total_us; // total frame time in microseconds (capped at 65535) uint16_t act_us; uint16_t sim_us; uint16_t paint_us; uint16_t present_us; uint8_t voices; // active synth voices uint8_t events; // input events this frame uint8_t js_heap_mb; // QuickJS heap in MB (capped at 255) uint8_t flags; // bit0=trackpadFX, bit1=cursor_visible} PerfRecord;
static PerfRecord *perf_buf = NULL;static int perf_buf_count = 0; // records in current chunk bufferstatic int perf_chunk_seq = 0; // monotonic chunk sequence numberstatic int perf_flush_frame = 0; // last frame we flushed
static void perf_init(void) { perf_buf = calloc(PERF_BUF_SIZE, sizeof(PerfRecord)); if (!perf_buf) fprintf(stderr, "[perf] alloc failed\n"); mkdir("/mnt/perf", 0755); // ensure directory exists}
static void perf_record(PerfRecord *r) { if (!perf_buf || perf_buf_count >= PERF_BUF_SIZE) return; perf_buf[perf_buf_count++] = *r;}
// Write current buffer as a numbered chunk file, fsync, close, then// delete the oldest chunk if we exceed PERF_MAX_CHUNKS.void perf_flush(void) { if (!perf_buf || perf_buf_count == 0) return;
char path[128]; snprintf(path, sizeof(path), "/mnt/perf/%04d.csv", perf_chunk_seq);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return;
FILE *f = fdopen(fd, "w"); if (!f) { close(fd); return; }
fprintf(f, "frame,total_us,act_us,sim_us,paint_us,present_us,voices,events,heap_mb,flags\n"); for (int i = 0; i < perf_buf_count; i++) { PerfRecord *r = &perf_buf[i]; fprintf(f, "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u\n", r->frame, r->total_us, r->act_us, r->sim_us, r->paint_us, r->present_us, r->voices, r->events, r->js_heap_mb, r->flags); } fflush(f); fsync(fd); fclose(f); // also closes fd
perf_buf_count = 0;
// Delete oldest chunk beyond retention window int old_seq = perf_chunk_seq - PERF_MAX_CHUNKS; if (old_seq >= 0) { char old_path[128]; snprintf(old_path, sizeof(old_path), "/mnt/perf/%04d.csv", old_seq); unlink(old_path); }
perf_chunk_seq++;}
static void perf_destroy(void) { perf_flush(); free(perf_buf); perf_buf = NULL;}
// Forward declaration — defined after init_log_mount()extern char g_machine_id[64];
// DRM handoff for xdg-open browser popup// SIGUSR1 = release DRM master (browser takes over display)// SIGUSR2 = reboot request from cage child (or reclaim DRM in DRM mode)static volatile int drm_handoff_release = 0;static volatile int drm_handoff_reclaim = 0;static volatile int reboot_requested = 0;volatile int poweroff_requested = 0; // extern'd in js-bindings.c
static void sigusr_handler(int sig) { if (sig == SIGUSR1) drm_handoff_release = 1; if (sig == SIGUSR2) reboot_requested = 1;}
static void sigterm_handler(int sig) { (void)sig; poweroff_requested = 1;}
// Shutdown/reboot that works in all three contexts:// - PID 1 (bare metal, direct DRM boot): reboot() syscall works directly.// - Child of init script (bare metal): exit with special code so the init// shell script can invoke `poweroff -f` and the reboot syscall itself.// We ALSO try the reboot syscall directly since we're running as root// with CAP_SYS_BOOT — that's faster than round-tripping through init.// - Under systemd (NixOS): fall back to systemctl.static void draw_shutdown_anim(void);
// Forward declarations for ac_poweroff bootpic capture.static int bootpic_capture_to(const char *prefix);// ac_log is defined further down — bootpic_capture_to logs through it.void ac_log(const char *fmt, ...);
static void ac_poweroff(void) { // Off-pic: snap a final frame before the farewell animation. Runs // synchronously — the function is bounded by camera_open's retry // budget so a missing camera can only delay shutdown by a few // hundred ms (one-shot, no retries here vs the boot side). bootpic_capture_to("off");
// Farewell animation (1.5s) — runs only if the display context is // available (skipped during early-boot emergency poweroffs before main // initializes graphics). draw_shutdown_anim(); sync(); usleep(500000); sync(); // Try the kernel syscall first — works as root with CAP_SYS_BOOT, which // we always have on bare metal (PID 1 or child of init). Only non-root // contexts (NixOS under systemd) need to shell out. if (reboot(LINUX_REBOOT_CMD_POWER_OFF) == 0) { // Shouldn't reach here — syscall succeeds → kernel halts. _exit(0); } // Syscall failed (likely EPERM on systemd) — fall back. system("systemctl poweroff || /sbin/poweroff -f || /bin/poweroff -f || poweroff -f"); // If the initramfs init script is our parent, exit(0) tells it we've // finished; it will do its own `poweroff -f` + sysrq as a last resort. _exit(0);}
static void ac_reboot(void) { sync(); usleep(500000); sync(); if (reboot(LINUX_REBOOT_CMD_RESTART) == 0) { _exit(2); } system("systemctl reboot || /sbin/reboot -f || /bin/reboot -f || reboot -f"); _exit(2);}
// 📸 Bootpics — snap a webcam frame at boot and at shutdown, save to// /mnt/bootpics/<prefix>-<unix_ts>.pgm. PGM (Portable Gray Map) is a// trivial format any decoder can read with no library: a one-line ASCII// header ("P5\n<w> <h>\n255\n") followed by w*h raw 8-bit luminance.// Grayscale only — that's all the existing camera_grab() exposes (it// converts YUYV→Y for QR scanning) and a faithful nostalgia colorspace// for these "this happened" snapshots.//// Bootside runs in a detached pthread so it never blocks the splash:// 1. retry camera_open with backoff (USB UVC enumerates lazily)// 2. throw away ~3 frames so auto-exposure settles// 3. one real grab, write PGM, close// Off-side runs synchronously inside ac_poweroff(), bounded by the// camera-open retry budget so a missing camera can't hang shutdown.static int bootpic_capture_to(const char *prefix) { ACCamera cam = {0}; cam.fd = -1; int opened = -1; // Retry up to 20 times with 200ms sleep between (4s total budget). // The camera_open call tries /dev/video0..3 in sequence each time — // gives the kernel time to enumerate USB UVC devices on early boot. for (int attempt = 0; attempt < 20; attempt++) { opened = camera_open(&cam); if (opened == 0) break; usleep(200000); } if (opened != 0) return -1;
// Warm-up grabs — UVC cameras need a few frames before exposure // and white balance stabilize, otherwise the first frame looks // fully black or fully white. for (int i = 0; i < 3; i++) { if (camera_grab(&cam) != 0) { // First few grabs can fail while the stream is starting; // tolerate up to half the warm-up budget failing. if (i >= 1) { camera_close(&cam); return -1; } } } if (camera_grab(&cam) != 0) { camera_close(&cam); return -1; }
// Build path: /mnt/bootpics/<prefix>-<unix_ts>.pgm mkdir("/mnt/bootpics", 0755); char path[160]; time_t now = time(NULL); snprintf(path, sizeof(path), "/mnt/bootpics/%s-%lld.pgm", prefix, (long long)now);
FILE *fp = fopen(path, "wb"); if (!fp) { camera_close(&cam); return -1; } fprintf(fp, "P5\n%d %d\n255\n", cam.width, cam.height); if (cam.gray) { fwrite(cam.gray, 1, (size_t)cam.width * (size_t)cam.height, fp); } fclose(fp); camera_close(&cam); ac_log("[bootpic] saved %s (%dx%d)\n", path, cam.width, cam.height); return 0;}
static void *bootpic_boot_thread_fn(void *arg) { (void)arg; // Detached — no caller waits on us. A small lead-in lets the // kernel finish its USB enumeration without us hammering open() // before the device node appears. usleep(300000); bootpic_capture_to("boot"); return NULL;}
static void bootpic_capture_boot_async(void) { pthread_t t; pthread_attr_t attr; if (pthread_attr_init(&attr) != 0) return; pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&t, &attr, bootpic_boot_thread_fn, NULL); pthread_attr_destroy(&attr);}
// Hardware device identity. Computed once at boot from DMI fields:// sha256(product_serial + "/" + system_uuid + "/" + board_serial)// truncated to 16 hex chars. Server (api/ac-device) maps fingerprint// to a curated slot like "ac0", "ac1" so each upcycled laptop has a// stable model number across reflashes. Cached in /mnt/.ac-device-slot// so the splash can render the badge on the next boot before wifi.// Declared here (above their helpers + above the body of main()) so// file order is fingerprint-helpers → other-globals-block → main.static char ac_device_fp[24] = ""; // 16 hex chars + roomstatic char ac_device_slot[16] = ""; // "ac0".."ac1023"
// Read a /sys/class/dmi/id/* field, trim trailing whitespace + newlines.// Returns the byte count written to dst (0 on missing / empty).static size_t read_dmi_field(const char *name, char *dst, size_t dstlen) { char path[128]; snprintf(path, sizeof(path), "/sys/class/dmi/id/%s", name); FILE *fp = fopen(path, "r"); if (!fp) { dst[0] = '\0'; return 0; } size_t n = fread(dst, 1, dstlen - 1, fp); fclose(fp); dst[n] = '\0'; while (n > 0 && (dst[n-1] == '\n' || dst[n-1] == '\r' || dst[n-1] == ' ' || dst[n-1] == '\t')) { dst[--n] = '\0'; } return n;}
// Compute the device fingerprint from DMI fields and cache it in// ac_device_fp. Also reads the cached slot from /mnt/.ac-device-slot// so the splash can render the badge before the first wifi-connect.static void compute_device_fingerprint(void) { char serial[128] = {0}, uuid[128] = {0}, board[128] = {0}; read_dmi_field("product_serial", serial, sizeof(serial)); read_dmi_field("product_uuid", uuid, sizeof(uuid)); if (uuid[0] == '\0') read_dmi_field("system_uuid", uuid, sizeof(uuid)); read_dmi_field("board_serial", board, sizeof(board));
// Fall back to MAC address when DMI is fully blank (qemu, exotic // hardware) so the fingerprint is at least stable for that // installation. /sys/class/net is the most portable readable path. char mac[64] = {0}; if (!serial[0] && !uuid[0] && !board[0]) { DIR *nd = opendir("/sys/class/net"); if (nd) { struct dirent *de; while ((de = readdir(nd))) { if (de->d_name[0] == '.') continue; if (strcmp(de->d_name, "lo") == 0) continue; char addrpath[256]; snprintf(addrpath, sizeof(addrpath), "/sys/class/net/%s/address", de->d_name); FILE *af = fopen(addrpath, "r"); if (af) { if (fgets(mac, sizeof(mac), af)) { size_t L = strlen(mac); while (L && (mac[L-1] == '\n' || mac[L-1] == ' ')) mac[--L] = 0; } fclose(af); if (mac[0]) break; } } closedir(nd); } }
char concat[640]; snprintf(concat, sizeof(concat), "%s/%s/%s/%s", serial, uuid, board, mac);
unsigned char digest[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char *)concat, strlen(concat), digest);
// First 16 hex chars (8 bytes) — plenty unique for ~thousands of // devices, short enough to fit comfortably in a config / URL param. static const char hexc[] = "0123456789abcdef"; for (int i = 0; i < 8; i++) { ac_device_fp[i*2] = hexc[(digest[i] >> 4) & 0xf]; ac_device_fp[i*2 + 1] = hexc[digest[i] & 0xf]; } ac_device_fp[16] = '\0';
// Cached slot from previous boot's wifi refresh. FILE *sf = fopen("/mnt/.ac-device-slot", "r"); if (sf) { size_t r = fread(ac_device_slot, 1, sizeof(ac_device_slot) - 1, sf); fclose(sf); ac_device_slot[r] = '\0'; size_t L = strlen(ac_device_slot); while (L > 0 && (ac_device_slot[L-1] == '\n' || ac_device_slot[L-1] == ' ' || ac_device_slot[L-1] == '\t' || ac_device_slot[L-1] == '\r')) { ac_device_slot[--L] = '\0'; } }
ac_log("[ac-device] fp=%s slot=%s\n", ac_device_fp, ac_device_slot[0] ? ac_device_slot : "(unassigned)");}
static void signal_handler(int sig) { running = 0;
// Best-effort crash report to /mnt/crash.json for next-boot upload if (sig == SIGSEGV || sig == SIGBUS || sig == SIGABRT || sig == SIGFPE) { const char *signame = "UNKNOWN"; switch (sig) { case SIGSEGV: signame = "SIGSEGV"; break; case SIGBUS: signame = "SIGBUS"; break; case SIGABRT: signame = "SIGABRT"; break; case SIGFPE: signame = "SIGFPE"; break; } FILE *f = fopen("/mnt/crash.json", "w"); if (f) { time_t now = time(NULL); fprintf(f, "{\"signal\":\"%s\",\"machineId\":\"%s\",\"time\":%ld}\n", signame, g_machine_id, (long)now); fclose(f); sync(); } // Re-raise to get default behavior (core dump / termination) signal(sig, SIG_DFL); raise(sig); }}
// Log to stderr (when unmuted) and logfilevoid ac_log(const char *fmt, ...) { va_list args, args2; va_start(args, fmt); va_copy(args2, args); if (!ac_log_stderr_muted) vfprintf(stderr, fmt, args); va_end(args); if (logfile) { vfprintf(logfile, fmt, args2); fflush(logfile); log_dirty = 1; } va_end(args2);}
// Flush log file to disk without closing itvoid ac_log_flush(void) { if (logfile) { fflush(logfile); if (log_dirty) { fsync(fileno(logfile)); log_dirty = 0; } }}
// Temporarily close the log file (e.g. before flash writes to same partition)void ac_log_pause(void) { if (logfile) { fflush(logfile); if (log_dirty) { fsync(fileno(logfile)); log_dirty = 0; } fclose(logfile); logfile = NULL; }}
// Reopen the log file in append mode after a pausevoid ac_log_resume(void) { if (!logfile) { logfile = fopen("/mnt/ac-native.log", "a"); // If reopen fails, logging continues to stderr only }}
// Mount minimal filesystems (PID 1 only)static void mount_minimal_fs(void) { mkdir("/proc", 0755); mkdir("/sys", 0755); mkdir("/dev", 0755); mkdir("/tmp", 0755);
mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); // Re-mount devtmpfs — safe here because by DRM fallback time i915 is fully loaded, // so the fresh devtmpfs will have /dev/dri/card0. (Init does NOT re-mount devtmpfs // so cage can see the kernel's original card0 early.) mount("devtmpfs", "/dev", "devtmpfs", 0, NULL); mkdir("/dev/pts", 0755); mount("devpts", "/dev/pts", "devpts", 0, "ptmxmode=0666"); mkdir("/dev/shm", 0755); mount("tmpfs", "/dev/shm", "tmpfs", 0, NULL); // Don't re-mount /tmp — init already mounted it and may have put logs there.
// Enable zram swap (compressed RAM — effectively doubles available memory) // Firefox + GTK needs significant memory beyond the initramfs tmpfs system("(modprobe zram 2>/dev/null || true); " "[ -e /sys/block/zram0/disksize ] && [ -b /dev/zram0 ] && " "echo 1G > /sys/block/zram0/disksize && " "mkswap /dev/zram0 >/dev/null 2>&1 && " "swapon /dev/zram0 2>/dev/null");
// Bring up loopback interface (needed for Claude OAuth callback server) system("/bin/ip link set lo up 2>/dev/null || /usr/sbin/ip link set lo up 2>/dev/null || ifconfig lo up 2>/dev/null");
// Wait for display device (up to 10s — Gemini Lake GPUs can be slow) for (int i = 0; i < 1000; i++) { if (access("/dev/dri/card0", F_OK) == 0 || access("/dev/dri/card1", F_OK) == 0 || access("/dev/fb0", F_OK) == 0) break; usleep(10000); }
// Set performance power mode FILE *gov = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "w"); if (gov) { fputs("performance", gov); fclose(gov); } // Set all CPUs to performance for (int c = 1; c < 16; c++) { char path[128]; snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor", c); gov = fopen(path, "w"); if (gov) { fputs("performance", gov); fclose(gov); } }}
// Try to mount boot USB for log writing (non-blocking, best-effort)char log_dev[32] = ""; // non-static: accessed by js-bindings.c for flash target checkstatic void try_mount_log(void) { mkdir("/mnt", 0755); // Wait for USB block devices to appear (up to 2s after EFI handoff) fprintf(stderr, "[ac-native] Waiting for USB block devices...\n"); for (int w = 0; w < 100; w++) { if (access("/dev/sda1", F_OK) == 0 || access("/dev/sda2", F_OK) == 0 || access("/dev/sdb1", F_OK) == 0 || access("/dev/sdb2", F_OK) == 0) break; usleep(20000); } fprintf(stderr, "[ac-native] sda1=%s sda2=%s sdb1=%s sdb2=%s\n", access("/dev/sda1", F_OK) == 0 ? "yes" : "no", access("/dev/sda2", F_OK) == 0 ? "yes" : "no", access("/dev/sdb1", F_OK) == 0 ? "yes" : "no", access("/dev/sdb2", F_OK) == 0 ? "yes" : "no"); const char *devs[] = { "/dev/sda1", "/dev/sda2", "/dev/sdb1", "/dev/sdb2", "/dev/sdc1", "/dev/sdc2", "/dev/sdd1", "/dev/sdd2", "/dev/nvme0n1p1", "/dev/nvme0n1p2", "/dev/nvme1n1p1", "/dev/nvme1n1p2", "/dev/mmcblk0p1", "/dev/mmcblk0p2", "/dev/mmcblk1p1", "/dev/mmcblk1p2", NULL };
// Pass 0: removable media first (USB install source). // Pass 1: fallback to internal ESP (ensures config.json loads on disk boots). for (int pass = 0; pass < 2; pass++) { for (int i = 0; devs[i]; i++) { if (access(devs[i], F_OK) != 0) continue;
char blk[32] = ""; get_parent_block(devs[i] + 5, blk, sizeof(blk)); // skip "/dev/" int rem = blk[0] ? is_removable(blk) : -1; if (pass == 0 && rem != 1) continue; if (pass == 1 && rem == 1) continue;
int mr = mount(devs[i], "/mnt", "vfat", 0, NULL); if (mr != 0) fprintf(stderr, "[ac-native] mount %s failed: %s\n", devs[i], strerror(errno)); if (mr == 0) { logfile = fopen("/mnt/ac-native.log", "a"); if (logfile) { // Separator between boots for multi-boot log history fprintf(logfile, "\n=== BOOT %s ===\n", devs[i]); fprintf(logfile, "[ac-native] Log opened on %s (removable=%d)\n", devs[i], rem); fflush(logfile); fsync(fileno(logfile)); strncpy(log_dev, devs[i], sizeof(log_dev) - 1); fprintf(stderr, "[ac-native] Log: %s -> /mnt/ac-native.log (removable=%d)\n", devs[i], rem); // Log available block devices for storage diagnostics { const char *bdevs[] = {"sda","sdb","sdc","sdd","nvme0n1","nvme1n1","mmcblk0","mmcblk1",NULL}; for (int b = 0; bdevs[b]; b++) { char bp[48]; snprintf(bp, sizeof(bp), "/sys/block/%s", bdevs[b]); if (access(bp, F_OK) == 0) { int brem = is_removable(bdevs[b]); ac_log("[storage] /dev/%s removable=%d\n", bdevs[b], brem); } } } // Dump init debug lines to USB from /tmp/ac-init.log (written by init) // and also try kmsg as backup { FILE *initlog = fopen("/mnt/init.log", "w"); if (initlog) { // Diagnostics: what does /tmp look like? fprintf(initlog, "diag: pid=%d\n", getpid()); fprintf(initlog, "diag: /tmp/ac-init.log access=%d\n", access("/tmp/ac-init.log", F_OK)); fprintf(initlog, "diag: /tmp/cage-stderr.log access=%d\n", access("/tmp/cage-stderr.log", F_OK)); // List /tmp contents DIR *tmpdir = opendir("/tmp"); if (tmpdir) { struct dirent *te; while ((te = readdir(tmpdir)) != NULL) { if (te->d_name[0] != '.') fprintf(initlog, "diag: /tmp/%s\n", te->d_name); } closedir(tmpdir); } else { fprintf(initlog, "diag: opendir /tmp failed\n"); } // Primary: read tmpfs log written by init script FILE *tmplog = fopen("/tmp/ac-init.log", "r"); if (tmplog) { char kbuf[512]; while (fgets(kbuf, sizeof(kbuf), tmplog)) fputs(kbuf, initlog); fclose(tmplog); } // Also dump cage stderr if it exists FILE *cage_err = fopen("/tmp/cage-stderr.log", "r"); if (cage_err) { fprintf(initlog, "--- cage stderr ---\n"); char kbuf[512]; while (fgets(kbuf, sizeof(kbuf), cage_err)) fputs(kbuf, initlog); fclose(cage_err); } // Backup: scan kmsg for ac-init lines int kmsg = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); if (kmsg >= 0) { lseek(kmsg, 0, SEEK_SET); char kbuf[512]; ssize_t r; int found = 0; while ((r = read(kmsg, kbuf, sizeof(kbuf) - 1)) > 0) { kbuf[r] = 0; if (strstr(kbuf, "ac-init:")) { if (!found) { fprintf(initlog, "--- kmsg ---\n"); found = 1; } char *msg = strstr(kbuf, "ac-init:"); fprintf(initlog, "%s\n", msg); } } close(kmsg); } fflush(initlog); fsync(fileno(initlog)); fclose(initlog); } } return; } umount("/mnt"); } fprintf(stderr, "[ac-native] Log mount failed: %s\n", devs[i]); } } // Fallback: log to tmpfs (won't survive reboot but stderr goes to console) fprintf(stderr, "[ac-native] No USB log mount available\n");}
// ── Persistent machine ID ──// Generated on first boot, read back on subsequent boots.// Accessible from js-bindings.c via extern.char g_machine_id[64] = {0};ACMachines g_machines = {0};
static void init_machine_id(void) { FILE *f = fopen("/mnt/.machine-id", "r"); if (f) { if (fgets(g_machine_id, sizeof(g_machine_id), f)) { char *nl = strchr(g_machine_id, '\n'); if (nl) *nl = '\0'; } fclose(f); ac_log("[machine] ID loaded: %s\n", g_machine_id); } else { unsigned int rval = 0; FILE *urand = fopen("/dev/urandom", "r"); if (urand) { fread(&rval, sizeof(rval), 1, urand); fclose(urand); } else { rval = (unsigned int)(time(NULL) ^ getpid()); } snprintf(g_machine_id, sizeof(g_machine_id), "ac-%08x", rval); f = fopen("/mnt/.machine-id", "w"); if (f) { fprintf(f, "%s\n", g_machine_id); fclose(f); ac_log("[machine] New ID generated: %s\n", g_machine_id); } else { ac_log("[machine] WARNING: Could not write /mnt/.machine-id\n"); } }}
// Forward declarations for time-of-day functions (defined later)static int get_la_offset(void);static int get_la_hour(void);
// Global display pointer — exposed to js-bindings for browser DRM handoffvoid *g_display = NULL;
// Shutdown animation context — main() populates these once the display +// graph are initialized so ac_poweroff() can draw a "bye @handle" farewell// screen that mirrors the boot animation before the kernel halts.static ACGraph *g_shutdown_graph = NULL;static ACFramebuffer *g_shutdown_screen = NULL;static int g_shutdown_pixel_scale = 3;
#ifdef USE_WAYLAND// Global Wayland display — used by ac_display_present dispatchstatic ACWaylandDisplay *g_wayland_display = NULL;#endif
// Unified display present — dispatches to Wayland or DRM backendstatic void ac_display_present(ACDisplay *display, ACFramebuffer *screen, int scale) {#ifdef USE_WAYLAND if (g_wayland_display) { wayland_display_present(g_wayland_display, screen, scale); return; }#endif display_present(display, screen, scale);}
// DRM master release/acquire (defined in drm-display.c)extern int drm_release_master(void *display);extern int drm_acquire_master(void *display);
// Boot title — defaults to "notepat", overridden by config.json handleint wifi_disabled = 0; // set from config.json "wifi":false (extern'd in js-bindings.c)static char boot_title[80] = "notepat";static ACColor boot_title_colors[80];static int boot_title_colors_len = 0;
// Boot mood — most recent mood for the user, displayed as the splash// subtitle in place of the default "enjoy <city>!" line. Two sources:// 1. config.json "mood" — baked at flash time by ac-inscribe.// 2. /mnt/last-mood — written at runtime after a successful// /api/mood/@<handle> fetch on the previous boot's wifi-connect.// /mnt/last-mood (when present) wins, since it's always at least as// fresh as the inscription. Empty string disables the mood subtitle// and falls back to the original "enjoy <city>!" rendering.static char boot_mood[256] = "";
// Flash-time preset city — baked into config.json "city" by// `ac-inscribe --city` so a device greets from wherever it's being shipped// (e.g. "Ridgewood") before it has ever geolocated. read_cached_city() uses// this as the fallback when /mnt/last-city.txt (the live IP-lookup cache)// doesn't exist yet; once the device geolocates, that cache wins.static char preset_city[96] = "";
// (Hardware device identity globals are defined further up — before the// compute_device_fingerprint() helper that needs them in file order.)
static uint8_t clamp_u8(int v) { if (v < 0) return 0; if (v > 255) return 255; return (uint8_t)v;}
static int parse_config_string(const char *json, const char *key, char *out, int out_sz) { if (!json || !key || !out || out_sz < 2) return 0; const char *kp = strstr(json, key); if (!kp) return 0; const char *colon = strchr(kp, ':'); if (!colon) return 0; const char *q1 = strchr(colon, '"'); if (!q1) return 0; const char *q2 = strchr(q1 + 1, '"'); if (!q2) return 0; int len = (int)(q2 - q1 - 1); if (len <= 0 || len >= out_sz) return 0; memcpy(out, q1 + 1, len); out[len] = 0; return 1;}
static int parse_config_bool(const char *json, const char *key, int *out) { if (!json || !key || !out) return 0; const char *kp = strstr(json, key); if (!kp) return 0; const char *colon = strchr(kp, ':'); if (!colon) return 0; const char *p = colon + 1; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, "true", 4) == 0) { *out = 1; return 1; } if (strncmp(p, "false", 5) == 0) { *out = 0; return 1; } return 0;}
static int parse_json_int_field(const char *start, const char *limit, const char *key, int *out) { if (!start || !limit || !key || !out || start >= limit) return 0; const char *kp = strstr(start, key); if (!kp || kp >= limit) return 0; const char *colon = strchr(kp, ':'); if (!colon || colon >= limit) return 0; const char *p = colon + 1; while (p < limit && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; if (p >= limit) return 0; char *endp = NULL; long v = strtol(p, &endp, 10); if (!endp || endp == p || endp > limit) return 0; *out = (int)v; return 1;}
static void parse_boot_title_colors(const char *json) { boot_title_colors_len = 0; if (!json) return;
// Prefer explicit title_colors, fallback to handle colors from API payload. const char *cp = strstr(json, "\"title_colors\""); if (!cp) cp = strstr(json, "\"colors\""); if (!cp) return;
const char *arr0 = strchr(cp, '['); if (!arr0) return; const char *arr1 = strchr(arr0 + 1, ']'); if (!arr1) return;
const char *p = arr0 + 1; while (p < arr1 && boot_title_colors_len < (int)(sizeof(boot_title_colors) / sizeof(boot_title_colors[0]))) { const char *obj0 = strchr(p, '{'); if (!obj0 || obj0 >= arr1) break; const char *obj1 = strchr(obj0, '}'); if (!obj1 || obj1 > arr1) break;
int r = -1, g = -1, b = -1; if (parse_json_int_field(obj0, obj1, "\"r\"", &r) && parse_json_int_field(obj0, obj1, "\"g\"", &g) && parse_json_int_field(obj0, obj1, "\"b\"", &b)) { int i = boot_title_colors_len++; boot_title_colors[i] = (ACColor){clamp_u8(r), clamp_u8(g), clamp_u8(b), 255}; } p = obj1 + 1; }}
static void load_boot_visual_config(void) { // Try USB/HD config first, fall back to initramfs-baked default FILE *cfg = fopen("/mnt/config.json", "r"); if (!cfg) cfg = fopen("/default-config.json", "r"); if (!cfg) return;
char buf[32768] = {0}; size_t n = fread(buf, 1, sizeof(buf) - 1, cfg); fclose(cfg); buf[n] = '\0';
// Skip identity block marker line if present ("AC_IDENTITY_BLOCK_V1\n") char *json = buf; if (strncmp(buf, "AC_IDENTITY_BLOCK_V1", 20) == 0) { char *nl = strchr(buf, '\n'); if (nl) json = nl + 1; }
char handle[64] = {0}; if (parse_config_string(json, "\"handle\"", handle, sizeof(handle))) { setenv("AC_HANDLE", handle, 1); if ((int)strlen(handle) < (int)sizeof(boot_title) - 20) { // Time-of-day greeting based on LA time int hour = get_la_hour(); const char *greeting; if (hour >= 5 && hour < 12) greeting = "good morning"; else if (hour >= 12 && hour < 17) greeting = "good afternoon"; else greeting = "good evening"; snprintf(boot_title, sizeof(boot_title), "%s @%s", greeting, handle); } } parse_boot_title_colors(json);
// Boot mood: prefer /mnt/last-mood (written at the previous boot's // wifi-connect from /api/mood/@<handle>) since it's always fresher // than the inscription's baked value. Fall back to config.json's // "mood" field if last-mood doesn't exist or is empty. { FILE *lm = fopen("/mnt/last-mood", "r"); if (lm) { size_t r = fread(boot_mood, 1, sizeof(boot_mood) - 1, lm); fclose(lm); boot_mood[r] = '\0'; // Strip a single trailing newline if present. size_t blen = strlen(boot_mood); if (blen > 0 && boot_mood[blen - 1] == '\n') boot_mood[blen - 1] = '\0'; } if (!boot_mood[0]) { parse_config_string(json, "\"mood\"", boot_mood, sizeof(boot_mood)); } if (boot_mood[0]) ac_log("[ac-native] Boot mood: %s\n", boot_mood);
// Flash-time preset greeting city (used until the device geolocates). parse_config_string(json, "\"city\"", preset_city, sizeof(preset_city)); if (preset_city[0]) ac_log("[ac-native] Preset city: %s\n", preset_city); }
// Read wifi flag (default: enabled) int wifi_val = 1; if (parse_config_bool(json, "\"wifi\"", &wifi_val)) { wifi_disabled = !wifi_val; ac_log("[config] wifi: %s\n", wifi_disabled ? "disabled" : "enabled"); }
// Bake Claude/GitHub tokens early so boot-fade badge check (access()) // succeeds while the fade is animating. The duplicate in main() runs // after the fade and was leaving the badges invisible. { char ct[512] = {0}, gp[256] = {0}; if (parse_config_string(json, "\"claudeToken\"", ct, sizeof(ct)) && ct[0]) { FILE *tf = fopen("/claude-token", "w"); if (tf) { fputs(ct, tf); fclose(tf); } ac_log("[tokens] claude token from config (%d bytes)\n", (int)strlen(ct)); } if (parse_config_string(json, "\"githubPat\"", gp, sizeof(gp)) && gp[0]) { FILE *gf = fopen("/github-pat", "w"); if (gf) { fputs(gp, gf); fclose(gf); } ac_log("[tokens] github pat from config (%d bytes)\n", (int)strlen(gp)); } }
// Extract claudeCreds JSON and write to /tmp for PTY to pick up const char *cc = strstr(buf, "\"claudeCreds\""); if (cc) { const char *start = strchr(cc, '{'); if (start) { int depth = 0; const char *end = start; while (*end) { if (*end == '{') depth++; else if (*end == '}') { depth--; if (depth == 0) { end++; break; } } end++; } if (depth == 0 && end > start) { mkdir("/tmp/.claude", 0755); FILE *cf = fopen("/tmp/.claude/.credentials.json", "w"); if (cf) { fwrite(start, 1, end - start, cf); fclose(cf); ac_log("[ac-native] Claude credentials written (%d bytes)\n", (int)(end - start)); } } } }
// Extract claudeState JSON and write to /tmp/.claude.json const char *cs = strstr(buf, "\"claudeState\""); if (cs) { const char *start = strchr(cs, '{'); if (start) { int depth = 0; const char *end = start; while (*end) { if (*end == '{') depth++; else if (*end == '}') { depth--; if (depth == 0) { end++; break; } } end++; } if (depth == 0 && end > start) { FILE *sf = fopen("/tmp/.claude.json", "w"); if (sf) { fwrite(start, 1, end - start, sf); fclose(sf); ac_log("[ac-native] Claude state written (%d bytes)\n", (int)(end - start)); } } } }
ac_log("[ac-native] Boot title: %s (colors=%d)\n", boot_title, boot_title_colors_len);}
static ACColor rainbow_title_color(int ci, int frame, int alpha) { double hue = fmod((double)ci / 7.0 * 360.0 + frame * 2.0, 360.0); double h6 = hue / 60.0; int hi = (int)h6 % 6; double fr = h6 - (int)h6; double sv = 0.7, vv = 1.0; double p = vv * (1.0 - sv), q = vv * (1.0 - sv * fr), tt = vv * (1.0 - sv * (1.0 - fr)); double cr, cg, cb; switch (hi) { case 0: cr = vv; cg = tt; cb = p; break; case 1: cr = q; cg = vv; cb = p; break; case 2: cr = p; cg = vv; cb = tt; break; case 3: cr = p; cg = q; cb = vv; break; case 4: cr = tt; cg = p; cb = vv; break; default: cr = vv; cg = p; cb = q; break; } return (ACColor){(uint8_t)(cr * 255), (uint8_t)(cg * 255), (uint8_t)(cb * 255), clamp_u8(alpha)};}
static ACColor title_char_color(int ci, int frame, int alpha) { if (boot_title_colors_len <= 0) return rainbow_title_color(ci, frame, alpha);
int title_len = (int)strlen(boot_title); int idx = ci; // Map palette to the handle portion (everything after @) const char *at = strchr(boot_title, '@'); int handle_start = at ? (int)(at - boot_title) + 1 : 0; // char after @ if (handle_start > 0 && ci >= handle_start && boot_title_colors_len > 0) { idx = ci - handle_start; } else if (ci < handle_start) { // greeting prefix and "@" get rainbow colors return rainbow_title_color(ci, frame, alpha); } if (idx < 0) idx = 0; if (boot_title_colors_len > 0) idx %= boot_title_colors_len; ACColor c = boot_title_colors[idx];
// Keep custom colors visible over dark boot backgrounds. int pulse = (int)(18.0 * sin((double)(frame + ci * 6) * 0.08)); int r = (c.r * 7 + 255 * 3) / 10 + pulse; int g = (c.g * 7 + 255 * 3) / 10 + pulse; int b = (c.b * 7 + 255 * 3) / 10 + pulse; return (ACColor){clamp_u8(r), clamp_u8(g), clamp_u8(b), clamp_u8(alpha)};}
// Forward declarationsstatic void draw_boot_status(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, const char *status, int pixel_scale);
// Check if a block device is removable (USB = 1, internal = 0)static int is_removable(const char *blkname) { char path[128]; snprintf(path, sizeof(path), "/sys/block/%s/removable", blkname); FILE *f = fopen(path, "r"); if (!f) return -1; // unknown int val = 0; if (fscanf(f, "%d", &val) != 1) val = -1; fclose(f); return val;}
// Copy a file from src to dst path, returns bytes copied or -1 on errorstatic long copy_file(const char *src, const char *dst) { FILE *in = fopen(src, "rb"); if (!in) { ac_log("[copy_file] cannot open src %s: errno=%d\n", src, errno); return -1; } FILE *out = fopen(dst, "wb"); if (!out) { ac_log("[copy_file] cannot open dst %s: errno=%d\n", dst, errno); fclose(in); return -1; } char buf[65536]; long total = 0; size_t n; while ((n = fread(buf, 1, sizeof(buf), in)) > 0) { size_t written = fwrite(buf, 1, n, out); if (written != n) { ac_log("[copy_file] write failed at offset %ld: wanted %zu got %zu errno=%d\n", total, n, written, errno); fclose(out); fclose(in); return -1; } total += n; } if (ferror(in)) { ac_log("[copy_file] read failed from %s: errno=%d\n", src, errno); fclose(out); fclose(in); return -1; } if (fflush(out) != 0) { ac_log("[copy_file] fflush failed: errno=%d\n", errno); fclose(out); fclose(in); return -1; } if (fsync(fileno(out)) != 0) { ac_log("[copy_file] fsync failed: errno=%d\n", errno); fclose(out); fclose(in); return -1; } fclose(out); fclose(in); return total;}
// Install failure reason — set by auto_install_to_hd, displayed on failure screenstatic char install_fail_reason[256] = "";static char install_fail_detail[256] = "";
// Unmount every entry in /proc/mounts whose SOURCE device matches// /dev/<parent> or any of its partitions. Loops until /proc/mounts no longer// shows any matching entry — necessary for mount-stacked /mnt where the// topmost fs might not be the one we want to unmount. Each iteration finds// ONE matching target, umount2()'s it (MNT_DETACH pops the topmost mount at// that path, which may or may not be the one we targeted — but after enough// iterations the stack empties of any matching entries). Returns total// successful umounts.static int force_unmount_disk(const char *parent_blk, const char *dlog_path) { int unmounted = 0; FILE *dl = dlog_path ? fopen(dlog_path, "a") : NULL; if (dl) fprintf(dl, "--- force_unmount_disk(%s) start ---\n", parent_blk);
// First pass: log the full /proc/mounts so we can see the initial stack { FILE *mp = fopen("/proc/mounts", "r"); if (mp && dl) { fprintf(dl, "--- initial /proc/mounts ---\n"); char line[512]; while (fgets(line, sizeof(line), mp)) fputs(line, dl); fprintf(dl, "--- end /proc/mounts ---\n"); fclose(mp); } else if (mp) { fclose(mp); } }
const int MAX_ITER = 16; // belt-and-suspenders against mount-stack depth for (int iter = 0; iter < MAX_ITER; iter++) { // Find ONE target whose source starts with /dev/<parent> char target[128] = ""; char source[128] = ""; FILE *mp = fopen("/proc/mounts", "r"); if (!mp) break; char line[512]; while (fgets(line, sizeof(line), mp)) { char src[128], tgt[128], fst[64]; if (sscanf(line, "%127s %127s %63s", src, tgt, fst) != 3) continue; if (strncmp(src, "/dev/", 5) != 0) continue; if (strncmp(src + 5, parent_blk, strlen(parent_blk)) != 0) continue; strncpy(target, tgt, sizeof(target) - 1); target[sizeof(target) - 1] = 0; strncpy(source, src, sizeof(source) - 1); source[sizeof(source) - 1] = 0; break; } fclose(mp); if (!target[0]) { if (dl) fprintf(dl, "iter=%d: no matching mounts remain — done\n", iter); break; } // Umount by path (MNT_DETACH pops the topmost mount, which may not be // the one we scanned — that's ok, we loop and re-scan). int r = umount2(target, MNT_DETACH); if (dl) fprintf(dl, "iter=%d: umount2(%s, MNT_DETACH) src=%s = %d errno=%d\n", iter, target, source, r, r < 0 ? errno : 0); ac_log("[install] iter=%d umount2(%s) src=%s rc=%d errno=%d\n", iter, target, source, r, r < 0 ? errno : 0); if (r == 0) { unmounted++; } else { // Try without MNT_DETACH as a fallback (plain umount). r = umount2(target, 0); if (dl) fprintf(dl, "iter=%d: umount2(%s, 0) fallback = %d errno=%d\n", iter, target, r, r < 0 ? errno : 0); if (r != 0) break; unmounted++; } // Tiny yield so the kernel can finish detaching before we re-scan. usleep(50000); }
if (dl) { fprintf(dl, "--- force_unmount_disk(%s) total=%d ---\n", parent_blk, unmounted); // Log post-unmount /proc/mounts so we can verify the stack is clean. FILE *mp = fopen("/proc/mounts", "r"); if (mp) { fprintf(dl, "--- post /proc/mounts ---\n"); char line[512]; while (fgets(line, sizeof(line), mp)) fputs(line, dl); fprintf(dl, "--- end /proc/mounts ---\n"); fclose(mp); } fclose(dl); } return unmounted;}
// Try BLKRRPART on a disk in a retry loop. Lazy unmounts take time to release// the device fully — the kernel keeps the block device referenced until all// superblock cleanups finish. We retry with backoff: 0 → 250ms → 500ms → 1s →// 2s. Returns 0 on success, -1 on failure (errno set by last attempt).// Also logs each attempt to dlog_path if provided.static int blkrrpart_with_retry(const char *disk_path, const char *dlog_path) { int delays_ms[] = {0, 250, 500, 1000, 2000}; FILE *dl = dlog_path ? fopen(dlog_path, "a") : NULL; int last_errno = 0; for (int i = 0; i < (int)(sizeof(delays_ms) / sizeof(delays_ms[0])); i++) { if (delays_ms[i] > 0) { sync(); usleep(delays_ms[i] * 1000); } int dfd = open(disk_path, O_RDONLY | O_CLOEXEC); if (dfd < 0) { last_errno = errno; if (dl) fprintf(dl, "BLKRRPART try %d: open failed errno=%d\n", i, last_errno); continue; } int ri = ioctl(dfd, BLKRRPART); last_errno = ri < 0 ? errno : 0; close(dfd); if (dl) fprintf(dl, "BLKRRPART try %d (delay=%dms) rc=%d errno=%d (%s)\n", i, delays_ms[i], ri, last_errno, ri < 0 ? strerror(last_errno) : "ok"); ac_log("[install] BLKRRPART %s try=%d delay=%dms rc=%d errno=%d\n", disk_path, i, delays_ms[i], ri, last_errno); if (ri == 0) { if (dl) fclose(dl); return 0; } } // Last resort: sysfs rescan (NVMe has /sys/class/nvme/nvme*/rescan_controller // and every block device has /sys/block/<dev>/uevent which triggers a // udev change event that forces partition rescan). char sys_uevent[128]; snprintf(sys_uevent, sizeof(sys_uevent), "/sys/block/%s/uevent", disk_path + 5); // skip "/dev/" FILE *ue = fopen(sys_uevent, "w"); if (ue) { fprintf(ue, "change\n"); fclose(ue); if (dl) fprintf(dl, "wrote 'change' to %s\n", sys_uevent); ac_log("[install] triggered sysfs change on %s\n", sys_uevent); // Give udev time to process usleep(500000); } else if (dl) { fprintf(dl, "sysfs fallback open(%s) failed errno=%d\n", sys_uevent, errno); } if (dl) fclose(dl); errno = last_errno; return -1;}
// Score removable install sources by how much of the current boot payload they// contain. Higher scores are preferred during W-to-install so we choose the// universal ACEFI partition over the simpler ACBOOT fallback when both exist.// 3 = universal layout (BOOTX64 + LOADER + KERNEL + initramfs + loader entry)// 2 = chainloader layout (BOOTX64 + KERNEL)// 1 = monolithic layout (BOOTX64 only)// 0 = not a usable install sourcestatic int install_source_layout_score(const char *mountpoint) { char bootx64[128]; char loader[128]; char kernel[128]; char initramfs_gz[128]; char initramfs_lz4[128]; char loader_entry[160];
snprintf(bootx64, sizeof(bootx64), "%s/EFI/BOOT/BOOTX64.EFI", mountpoint); snprintf(loader, sizeof(loader), "%s/EFI/BOOT/LOADER.EFI", mountpoint); snprintf(kernel, sizeof(kernel), "%s/EFI/BOOT/KERNEL.EFI", mountpoint); snprintf(initramfs_gz, sizeof(initramfs_gz), "%s/initramfs.cpio.gz", mountpoint); snprintf(initramfs_lz4, sizeof(initramfs_lz4), "%s/initramfs.cpio.lz4", mountpoint); snprintf(loader_entry, sizeof(loader_entry), "%s/loader/entries/ac-native.conf", mountpoint);
if (access(bootx64, F_OK) == 0 && access(loader, F_OK) == 0 && access(kernel, F_OK) == 0 && (access(initramfs_gz, F_OK) == 0 || access(initramfs_lz4, F_OK) == 0) && access(loader_entry, F_OK) == 0) { return 3; } if (access(bootx64, F_OK) == 0 && access(kernel, F_OK) == 0) return 2; if (access(bootx64, F_OK) == 0) return 1; return 0;}
// Auto-install kernel to internal drive's EFI System Partition// Returns 1 on success, 0 on failure (sets install_fail_reason/detail).static int auto_install_to_hd(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, int pixel_scale) { char source_mount[32] = "/mnt"; char source_dev[32] = ""; int source_mounted_tmp = 0; char kernel_src[96] = ""; install_fail_reason[0] = '\0'; install_fail_detail[0] = '\0'; char bootloader_src[96] = ""; char loader_src[96] = ""; char chain_kernel_src[96] = ""; char initramfs_src[96] = ""; char install_kernel_src[96] = ""; char config_src[64] = ""; char loader_conf_src[96] = ""; char loader_entry_src[128] = "";
ac_log("[install] auto_install_to_hd starting\n"); if (display) draw_boot_status(graph, screen, display, "installing to disk...", pixel_scale);
// Detect source layout: monolithic (BOOTX64.EFI is kernel), chainloader // (BOOTX64.EFI boots KERNEL.EFI), or universal systemd-boot // (BOOTX64.EFI + LOADER.EFI + KERNEL.EFI + initramfs + loader entry). int systemd_boot_layout = 0; int chainloader_layout = 0; int source_score = 0;
// Prefer current /mnt only when it is removable and actually bootable. if (log_dev[0]) { char blk[32] = ""; get_parent_block(log_dev + 5, blk, sizeof(blk)); if (blk[0] && is_removable(blk) == 1) { source_score = install_source_layout_score("/mnt"); if (source_score > 0) { ac_log("[install] current /mnt source score=%d (%s)\n", source_score, log_dev); strncpy(source_dev, log_dev, sizeof(source_dev) - 1); source_dev[sizeof(source_dev) - 1] = '\0'; } } }
// Scan removable partitions and prefer the richest boot layout. This lets // W-install source from ACEFI on the new hybrid USB instead of blindly // using whichever removable partition happened to get mounted at /mnt. { const char *src_candidates[] = { "/dev/sda1", "/dev/sda2", "/dev/sdb1", "/dev/sdb2", "/dev/sdc1", "/dev/sdc2", "/dev/sdd1", "/dev/sdd2", NULL }; char best_dev[32] = ""; int best_score = source_score; mkdir("/tmp/src", 0755); for (int i = 0; src_candidates[i]; i++) { if (access(src_candidates[i], F_OK) != 0) continue; if (source_dev[0] && strcmp(src_candidates[i], source_dev) == 0) continue; char blk[32] = ""; get_parent_block(src_candidates[i] + 5, blk, sizeof(blk)); if (!blk[0] || is_removable(blk) != 1) continue; if (mount(src_candidates[i], "/tmp/src", "vfat", 0, NULL) != 0) continue; int score = install_source_layout_score("/tmp/src"); umount("/tmp/src"); if (score > best_score) { best_score = score; strncpy(best_dev, src_candidates[i], sizeof(best_dev) - 1); best_dev[sizeof(best_dev) - 1] = '\0'; } } if (best_dev[0]) { if (mount(best_dev, "/tmp/src", "vfat", 0, NULL) == 0) { strncpy(source_dev, best_dev, sizeof(source_dev) - 1); source_dev[sizeof(source_dev) - 1] = '\0'; strncpy(source_mount, "/tmp/src", sizeof(source_mount) - 1); source_mount[sizeof(source_mount) - 1] = '\0'; source_mounted_tmp = 1; source_score = best_score; ac_log("[install] selected richer removable source %s score=%d\n", source_dev, source_score); } else { ac_log("[install] failed to mount preferred source %s errno=%d\n", best_dev, errno); } } }
systemd_boot_layout = (source_score >= 3); chainloader_layout = (!systemd_boot_layout && source_score >= 2);
snprintf(bootloader_src, sizeof(bootloader_src), "%s/EFI/BOOT/BOOTX64.EFI", source_mount); snprintf(loader_src, sizeof(loader_src), "%s/EFI/BOOT/LOADER.EFI", source_mount); snprintf(chain_kernel_src, sizeof(chain_kernel_src), "%s/EFI/BOOT/KERNEL.EFI", source_mount); snprintf(kernel_src, sizeof(kernel_src), "%s/EFI/BOOT/BOOTX64.EFI", source_mount); snprintf(config_src, sizeof(config_src), "%s/config.json", source_mount); snprintf(loader_conf_src, sizeof(loader_conf_src), "%s/loader/loader.conf", source_mount); snprintf(loader_entry_src, sizeof(loader_entry_src), "%s/loader/entries/ac-native.conf", source_mount); // Initramfs is at the ESP root in EVERY layout the oven produces (Phase 2 // de-embed), not just systemd-boot. Drop the `&& systemd_boot_layout` // gate — the kernel-direct install path needs initramfs from the source // ACBOOT/ACEFI mount even when source_score detected chainloader-only. if (access(source_dev, F_OK) == 0) { char initramfs_gz[96]; char initramfs_lz4[96]; snprintf(initramfs_gz, sizeof(initramfs_gz), "%s/initramfs.cpio.gz", source_mount); snprintf(initramfs_lz4, sizeof(initramfs_lz4), "%s/initramfs.cpio.lz4", source_mount); if (access(initramfs_gz, F_OK) == 0) { strncpy(initramfs_src, initramfs_gz, sizeof(initramfs_src) - 1); initramfs_src[sizeof(initramfs_src) - 1] = '\0'; } else if (access(initramfs_lz4, F_OK) == 0) { strncpy(initramfs_src, initramfs_lz4, sizeof(initramfs_src) - 1); initramfs_src[sizeof(initramfs_src) - 1] = '\0'; } }
if (systemd_boot_layout) { strncpy(install_kernel_src, chain_kernel_src, sizeof(install_kernel_src) - 1); install_kernel_src[sizeof(install_kernel_src) - 1] = '\0'; ac_log("[install] detected universal systemd-boot layout\n"); } else if (chainloader_layout) { strncpy(install_kernel_src, chain_kernel_src, sizeof(install_kernel_src) - 1); install_kernel_src[sizeof(install_kernel_src) - 1] = '\0'; ac_log("[install] detected chainloader layout\n"); } else { strncpy(install_kernel_src, kernel_src, sizeof(install_kernel_src) - 1); install_kernel_src[sizeof(install_kernel_src) - 1] = '\0'; if (source_score == 1) ac_log("[install] detected monolithic layout\n"); }
if (!source_dev[0] || source_score == 0 || access(install_kernel_src, F_OK) != 0 || (systemd_boot_layout && (access(loader_src, F_OK) != 0 || initramfs_src[0] == '\0' || access(loader_entry_src, F_OK) != 0))) { ac_log("[install] No removable install source with kernel found\n"); snprintf(install_fail_reason, sizeof(install_fail_reason), "no USB boot source found"); // Log available block devices for diagnostics ac_log("[install] Block devices:\n"); int dpos = 0; const char *scan[] = {"sda","sdb","sdc","sdd","nvme0n1","nvme1n1","mmcblk0",NULL}; for (int i = 0; scan[i]; i++) { char dp[32]; snprintf(dp, sizeof(dp), "/sys/block/%s", scan[i]); if (access(dp, F_OK) == 0) { int rem = is_removable(scan[i]); ac_log("[install] /dev/%s removable=%d\n", scan[i], rem); dpos += snprintf(install_fail_detail + dpos, sizeof(install_fail_detail) - dpos, "/dev/%s %s ", scan[i], rem == 1 ? "(USB)" : rem == 0 ? "(int)" : "(?)"); } } if (source_mounted_tmp) umount("/tmp/src"); return 0; }
mkdir("/tmp/hd", 0755);
// Determine which block device install source is on (skip it as destination) char usb_blk[16] = ""; if (source_dev[0]) { const char *p = source_dev + 5; // skip "/dev/" int len = 0; while (p[len] && (p[len] < '0' || p[len] > '9')) len++; // For nvme: "nvme0n1p1" → parent "nvme0n1" // For sd: "sda1" → parent "sda" if (len > 0) { if (len > (int)sizeof(usb_blk) - 1) len = sizeof(usb_blk) - 1; memcpy(usb_blk, p, len); usb_blk[len] = '\0'; } }
// Scan for internal (non-removable) block devices with partitions. // NVMe first (always internal), then eMMC (always internal — common // on Chromebooks + budget laptops), then SATA/USB last. const char *part_candidates[] = { "nvme0n1", "nvme1n1", // NVMe SSDs "mmcblk0", "mmcblk1", // eMMC (Chromebooks, budget laptops) "sda", "sdb", "sdc", "sdd", // SATA/USB NULL };
int installed = 0; for (int i = 0; part_candidates[i] && !installed; i++) { const char *blk = part_candidates[i];
// Skip the USB boot device if (usb_blk[0] && strcmp(blk, usb_blk) == 0) continue;
// For sd* devices, skip if removable if (blk[0] == 's' && blk[1] == 'd') { int rem = is_removable(blk); if (rem == 1) continue; // removable = USB }
// Wipe-and-install pre-pass: ALWAYS repartition the candidate // disk before installing (was: only blank disks). Boot-time `w` // is meant to install fresh AC Native OS; if the user pressed it // they expect Fedora / whatever was there to be REPLACED, not // dual-booted. Previous behavior found existing ESPs (e.g. // Fedora's /boot/efi) and added our boot tree alongside, leaving // the rest of the disk intact — install reported "complete" but // didn't actually wipe. // // Order: zero first 16 MiB → sfdisk single GPT ESP spanning the // disk → kernel reread → wait for p1 to materialize → fall through // to the per-partition loop, which will now find p1 (newly created, // no fs yet) and use the rescue-mkfs path on pass 1 to format + // populate it. { char parent_path[32]; snprintf(parent_path, sizeof(parent_path), "/dev/%s", blk); char first_part[32]; if (blk[0] == 'n' || strncmp(blk, "mmcblk", 6) == 0) snprintf(first_part, sizeof(first_part), "/dev/%sp1", blk); else snprintf(first_part, sizeof(first_part), "/dev/%s1", blk); if (access(parent_path, F_OK) == 0) { ac_log("[install] WIPE+INSTALL on %s (was: %s) — running sfdisk\n", parent_path, (access(first_part, F_OK) == 0) ? "had partitions, all replaced" : "blank disk"); // Force-unmount everything on the target disk before we // touch its partition table. Without this, the kernel // would block sfdisk with EBUSY for the in-use partitions. force_unmount_disk(blk, "/tmp/install-debug.log"); sync(); usleep(200000); // Wipe first 16 MiB so any leftover GPT/MBR/FS signatures // don't confuse sfdisk or the firmware on next boot. char wcmd[256]; snprintf(wcmd, sizeof(wcmd), "dd if=/dev/zero of=%s bs=1M count=16 conv=fsync 2>&1 | head -3", parent_path); system(wcmd); // Single GPT ESP spanning the disk. char rcmd[512]; snprintf(rcmd, sizeof(rcmd), "{ echo 'label: gpt'; echo 'name=ACBOOT,type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B'; } | " "sfdisk --force --no-reread %s 2>&1", parent_path); int srrc = system(rcmd); ac_log("[install] sfdisk on %s rc=%d\n", parent_path, srrc); sync(); usleep(500000); blkrrpart_with_retry(parent_path, "/tmp/install-debug.log"); // Wait for p1 to appear (up to 5s). for (int wait = 0; wait < 50; wait++) { if (access(first_part, F_OK) == 0) break; usleep(100000); } ac_log("[install] wipe+install: %s exists=%d after sfdisk\n", first_part, access(first_part, F_OK) == 0); } }
// Two-pass partition scan. Pass 0: probe p1..p16 for an existing // vfat partition (non-destructive) — finds the Chromebook ESP at // p12 before we would otherwise reformat p1 (stateful/ext4) on // Chromebooks and clobber user data. Pass 1: fall back to the // p=1 rescue reformat for stock Linux layouts where no ESP exists. for (int pass = 0; pass < 2 && !installed; pass++) { int allow_rescue_mkfs = (pass == 1); for (int p = 1; p <= 16 && !installed; p++) { char devpath[32]; // NVMe + eMMC use "p<N>" suffix (name ends in a digit); SATA // just appends the number to the base name. if (blk[0] == 'n' || strncmp(blk, "mmcblk", 6) == 0) snprintf(devpath, sizeof(devpath), "/dev/%sp%d", blk, p); else // SATA: sda1 snprintf(devpath, sizeof(devpath), "/dev/%s%d", blk, p);
if (access(devpath, F_OK) != 0) continue;
// Try mounting as FAT (ESP is always FAT32) ac_log("[install] trying %s\n", devpath); if (mount(devpath, "/tmp/hd", "vfat", 0, NULL) != 0) { int mount_errno = errno; ac_log("[install] mount failed: %s (errno=%d)\n", devpath, mount_errno); // If the partition exists but has no filesystem (e.g. a prior // sfdisk created it but mkfs never succeeded), try to format // it now. Only attempt this on the first partition (which is // the ESP slot) and only if the partition is large enough. // Gated to pass==1 so we never clobber Chromebook p1 STATE // when an existing vfat ESP is available elsewhere (e.g. p12). if (p == 1 && allow_rescue_mkfs) { long long part_bytes = 0; int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { unsigned long long sz = 0; if (ioctl(pfd, BLKGETSIZE64, &sz) == 0) part_bytes = (long long)sz; close(pfd); } long part_mb = (long)(part_bytes / 1048576LL); ac_log("[install] %s size=%ldMB — try format to rescue unmountable partition\n", devpath, part_mb); if (part_mb >= 512) { // Flush buffer cache, then mkfs int fpfd = open(devpath, O_RDONLY | O_CLOEXEC); if (fpfd >= 0) { ioctl(fpfd, BLKFLSBUF); close(fpfd); } system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); sync(); usleep(500000); char mkfs_cmd[256]; snprintf(mkfs_cmd, sizeof(mkfs_cmd), "mkfs.vfat -F 32 -n AC-NATIVE %s 2>&1", devpath); int mkrc = system(mkfs_cmd); ac_log("[install] rescue mkfs rc=%d\n", mkrc); if (WIFEXITED(mkrc) && WEXITSTATUS(mkrc) == 0) { usleep(500000); if (mount(devpath, "/tmp/hd", "vfat", 0, NULL) == 0) { ac_log("[install] rescue format + mount OK\n"); // Fall through to normal install flow below } else { ac_log("[install] rescue mount still failed\n"); continue; } } else { continue; } } else { continue; } } else { continue; } }
// Create EFI boot directories mkdir("/tmp/hd/EFI", 0755); mkdir("/tmp/hd/EFI/BOOT", 0755);
// Check free space against the full payload we plan to copy. { struct stat src_st; struct statvfs hd_vfs; long long install_bytes = 0; long long free_bytes = 0; if (stat(install_kernel_src, &src_st) == 0) install_bytes += src_st.st_size; if (chainloader_layout && stat(bootloader_src, &src_st) == 0) install_bytes += src_st.st_size; if (systemd_boot_layout) { if (stat(bootloader_src, &src_st) == 0) install_bytes += src_st.st_size; if (stat(loader_src, &src_st) == 0) install_bytes += src_st.st_size; if (stat(initramfs_src, &src_st) == 0) install_bytes += src_st.st_size; if (stat(loader_conf_src, &src_st) == 0) install_bytes += src_st.st_size; if (stat(loader_entry_src, &src_st) == 0) install_bytes += src_st.st_size; } if (stat(config_src, &src_st) == 0) install_bytes += src_st.st_size; if (statvfs("/tmp/hd", &hd_vfs) == 0) free_bytes = (long long)hd_vfs.f_bavail * (long long)hd_vfs.f_bsize; long need_mb = (long)((install_bytes + (1048576 - 1)) / 1048576) + 10; long free_mb = (long)(free_bytes / 1048576); ac_log("[install] payload=%ldMB free=%ldMB on %s\n", (long)((install_bytes + (1048576 - 1)) / 1048576), free_mb, devpath); if (install_bytes > 0 && free_bytes < install_bytes + 10LL * 1048576LL) { ac_log("[install] NOT ENOUGH SPACE — need %ldMB, have %ldMB\n", need_mb, free_mb); // Check the PARTITION SIZE (not free space). If a previous // sfdisk already expanded it to 1024MB but mkfs failed, the // partition is big enough — we just need to unmount + reformat, // no repartitioning needed (which avoids the EBUSY nightmare). long long part_size_bytes = 0; { int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { unsigned long long sz = 0; if (ioctl(pfd, BLKGETSIZE64, &sz) == 0) part_size_bytes = (long long)sz; close(pfd); } } long part_mb = (long)(part_size_bytes / 1048576LL); ac_log("[install] partition %s size=%ldMB (need %ldMB)\n", devpath, part_mb, need_mb);
if (part_mb >= need_mb) { // Partition already large enough — just unmount + reformat ac_log("[install] partition big enough, skip repartition → direct reformat\n"); umount("/tmp/hd"); umount2("/tmp/hd", MNT_DETACH); // Unmount all mounts on this disk char parent_blk_tmp[32] = ""; { const char *d = devpath + 5; strncpy(parent_blk_tmp, d, sizeof(parent_blk_tmp) - 1); char *pp = strstr(parent_blk_tmp, "p"); if (pp && pp > parent_blk_tmp && *(pp-1) >= '0' && *(pp-1) <= '9' && *(pp+1) >= '1' && *(pp+1) <= '9') *pp = 0; else { int len = strlen(parent_blk_tmp); while (len > 0 && parent_blk_tmp[len-1] >= '0' && parent_blk_tmp[len-1] <= '9') len--; parent_blk_tmp[len] = 0; } } const char *DLOG = "/tmp/install-debug.log"; FILE *__dl = fopen(DLOG, "w"); if (__dl) { fprintf(__dl, "=== direct-reformat (no repartition) ===\n"); fclose(__dl); } force_unmount_disk(parent_blk_tmp, DLOG); sync(); // Flush block device cache { int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { ioctl(pfd, BLKFLSBUF); close(pfd); } char dd[64]; snprintf(dd, sizeof(dd), "/dev/%s", parent_blk_tmp); int dfd = open(dd, O_RDONLY | O_CLOEXEC); if (dfd >= 0) { ioctl(dfd, BLKFLSBUF); close(dfd); } } system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); sync(); usleep(2000000); // Format directly char rcmd[512]; const char *MKFS_ERR = "/tmp/mkfs-err.log"; int mkfs_exit = -1; for (int mkfs_try = 1; mkfs_try <= 5 && mkfs_exit != 0; mkfs_try++) { if (mkfs_try > 1) { int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { ioctl(pfd, BLKFLSBUF); close(pfd); } system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); sync(); usleep(3000000); } snprintf(rcmd, sizeof(rcmd), "echo '--- mkfs attempt %d ---' >> %s; " "(mkfs.vfat -F 32 -n AC-NATIVE %s > %s 2>&1; rc=$?; " "cat %s >> %s; exit $rc)", mkfs_try, DLOG, devpath, MKFS_ERR, MKFS_ERR, DLOG); int rrc = system(rcmd); mkfs_exit = WIFEXITED(rrc) ? WEXITSTATUS(rrc) : -1; ac_log("[install] direct mkfs rc=%d attempt=%d\n", rrc, mkfs_try); FILE *mf = fopen(MKFS_ERR, "r"); if (mf) { char line[512]; while (fgets(line, sizeof(line), mf)) { size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; ac_log("[mkfs/%d] %s\n", mkfs_try, line); } fclose(mf); } } if (mkfs_exit == 0) { ac_log("[install] direct reformat succeeded\n"); goto install_copy_phase; } ac_log("[install] direct reformat failed, falling through to repartition\n"); }
// Repartition: expand ESP to 1024MB char parent_blk[32] = ""; // Extract parent device: /dev/nvme0n1p1 → nvme0n1, /dev/sda1 → sda { const char *d = devpath + 5; // skip "/dev/" strncpy(parent_blk, d, sizeof(parent_blk) - 1); // Remove partition suffix: "nvme0n1p1" → "nvme0n1", "sda1" → "sda" char *pp = strstr(parent_blk, "p"); if (pp && pp > parent_blk && *(pp-1) >= '0' && *(pp-1) <= '9' && *(pp+1) >= '1' && *(pp+1) <= '9') *pp = 0; // NVMe: nvme0n1p1 → nvme0n1 else { // SATA: sda1 → sda (strip trailing digits) int len = strlen(parent_blk); while (len > 0 && parent_blk[len-1] >= '0' && parent_blk[len-1] <= '9') len--; parent_blk[len] = 0; } } ac_log("[install] repartitioning /dev/%s → 1024MB ESP\n", parent_blk); if (display) { char msg[80]; snprintf(msg, sizeof(msg), "expanding to 1024MB..."); draw_boot_status(graph, screen, display, msg, pixel_scale); } // Release every handle on the target disk BEFORE sfdisk. // The previous version relied on a shell `umount -l` loop // that (a) couldn't umount partitions by device path under // busybox, and (b) left enough lazy-mount residue that // BLKRRPART returned EBUSY every time. The new helpers // parse /proc/mounts in C and umount2 each target // explicitly, then retry BLKRRPART with backoff. umount("/tmp/hd"); umount2("/tmp/hd", MNT_DETACH); // Write install-debug.log to TMPFS so it survives even // if the unmount loop pops the whole /mnt stack. We copy // it back to /mnt/install-debug.log at the end so the // USB has a post-mortem trace. const char *DLOG = "/tmp/install-debug.log"; // Fresh log per attempt FILE *__dl = fopen(DLOG, "w"); if (__dl) { fprintf(__dl, "=== install-debug starting ===\n"); fprintf(__dl, "parent_blk=%s devpath=%s\n", parent_blk, devpath); fclose(__dl); } // Unmount every mount currently referencing this disk. int n_unmounted = force_unmount_disk(parent_blk, DLOG); ac_log("[install] force_unmount_disk(%s) released %d mounts\n", parent_blk, n_unmounted); sync();
// CRITICAL: After MNT_DETACH (lazy unmount), the kernel // VFS still holds block device references from cached // dentries/inodes/superblock. We MUST flush buffer cache // on both the partition and whole disk via BLKFLSBUF, // then drop page caches, before the block layer will // release its exclusive hold. Without this, BLKRRPART // and mkfs both fail with EBUSY. { // Flush buffer cache on partition int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { ioctl(pfd, BLKFLSBUF); close(pfd); ac_log("[install] BLKFLSBUF on %s: ok\n", devpath); } // Flush buffer cache on whole disk char disk_dev[64]; snprintf(disk_dev, sizeof(disk_dev), "/dev/%s", parent_blk); int dfd = open(disk_dev, O_RDONLY | O_CLOEXEC); if (dfd >= 0) { ioctl(dfd, BLKFLSBUF); close(dfd); ac_log("[install] BLKFLSBUF on %s: ok\n", disk_dev); } // Drop all page/dentry/inode caches system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); sync(); ac_log("[install] caches flushed + dropped\n"); } usleep(1000000); // 1s settle after cache flush
// Nuke the old filesystem signatures BEFORE sfdisk. This // is critical: the old Fedora ESP's FAT boot sector and // GPT partition UUID are still on disk, and mkfs.vfat // later hits EBUSY trying to get O_EXCL because the // kernel's block device cache still maps to the old FS. // dd'ing zeros over the first 64KB removes the old FAT // signature + GPT primary header. The backup GPT at the // end of the disk also needs clearing but sfdisk --force // will overwrite it. busybox dd is in initramfs. char rcmd[512]; snprintf(rcmd, sizeof(rcmd), "echo '--- wiping old FS signatures ---' >> %s; " "dd if=/dev/zero of=/dev/%s bs=512 count=128 conv=fsync 2>&1 | tee -a %s; " "sync", DLOG, parent_blk, DLOG); system(rcmd); usleep(500000);
// Repartition: create 1024MB EFI System Partition. // CRITICAL: --no-reread tells sfdisk NOT to call BLKRRPART // itself. The kernel was failing BLKRRPART with EBUSY // because the block device cache still holds references // from the old filesystem, and that blocked sfdisk too. // With --no-reread, sfdisk writes the partition table // and returns cleanly; we refresh partitions explicitly // via partx -u below, which is non-destructive and // works even when the device is "in use" at the kernel // level. snprintf(rcmd, sizeof(rcmd), "{ echo 'label: gpt'; echo 'type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, size=1024M'; } | sfdisk --force --no-reread /dev/%s >> %s 2>&1", parent_blk, DLOG); int rrc = system(rcmd); ac_log("[install] sfdisk --no-reread rc=%d\n", rrc); // Give the kernel time to process the new GPT sync(); usleep(500000);
// Refresh the kernel's partition table. Primary method: // BLKRRPART ioctl (no external binary needed). Falls back // to partx/partprobe if available for belt-and-suspenders. { char disk_dev[64]; snprintf(disk_dev, sizeof(disk_dev), "/dev/%s", parent_blk); ac_log("[install] BLKRRPART on %s...\n", disk_dev); int brr = blkrrpart_with_retry(disk_dev, DLOG); ac_log("[install] BLKRRPART result=%d\n", brr); } // Also try partx/partprobe as secondary refresh (may not // be in initramfs — that's OK, || true swallows the error). snprintf(rcmd, sizeof(rcmd), "echo '--- partx refresh ---' >> %s; " "partx -u /dev/%s >> %s 2>&1 || true; " "partprobe /dev/%s >> %s 2>&1 || true; " "sfdisk --verify /dev/%s >> %s 2>&1 || true; " "ls -l /dev/%s* >> %s 2>&1 || true", DLOG, parent_blk, DLOG, parent_blk, DLOG, parent_blk, DLOG, parent_blk, DLOG); system(rcmd); // sfdisk wrote a single partition → the new ESP is p1 on // the parent disk, regardless of what partition index we // entered this branch with. Chromebook case: we arrived // via devpath=/dev/mmcblk1p12 (existing 16MB EFI-SYSTEM), // sfdisk rewrote the table to a single 1024MB entry at // p1 and wiped p12 entirely — so mkfs + mount need to // target the new partition, not the old devpath. char new_devpath[48]; if (parent_blk[0] == 'n' || strncmp(parent_blk, "mmcblk", 6) == 0) { snprintf(new_devpath, sizeof(new_devpath), "/dev/%sp1", parent_blk); } else { snprintf(new_devpath, sizeof(new_devpath), "/dev/%s1", parent_blk); } if (strcmp(new_devpath, devpath) != 0) { ac_log("[install] canonicalizing devpath %s → %s after sfdisk\n", devpath, new_devpath); strncpy(devpath, new_devpath, sizeof(devpath) - 1); devpath[sizeof(devpath) - 1] = '\0'; } // Wait for partition device node to appear (up to 10s) int devpath_ready = 0; for (int wait = 0; wait < 20; wait++) { usleep(500000); struct stat st; if (stat(devpath, &st) == 0 && S_ISBLK(st.st_mode)) { ac_log("[install] device %s ready after %dms\n", devpath, (wait+1)*500); devpath_ready = 1; break; } if (wait % 4 == 0) ac_log("[install] waiting for %s... (%d)\n", devpath, wait); } if (!devpath_ready) { ac_log("[install] device %s never appeared after sfdisk — see %s\n", devpath, DLOG); } // Wipe any remaining FS signature. Try wipefs first; if // it's missing from initramfs, fall back to dd'ing zeros // over the first 4KB of the partition (covers FAT BPB, // ext superblock, and any other FS magic). snprintf(rcmd, sizeof(rcmd), "echo '--- wipefs partition ---' >> %s; " "if command -v wipefs >/dev/null 2>&1; then " " wipefs -a %s >> %s 2>&1; " "else " " echo 'wipefs not found, using dd fallback' >> %s; " " dd if=/dev/zero of=%s bs=512 count=8 conv=fsync >> %s 2>&1; " "fi", DLOG, devpath, DLOG, DLOG, devpath, DLOG); system(rcmd); // Aggressively flush block device buffer cache + page cache // to release the kernel's exclusive hold on the partition. { int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { ioctl(pfd, BLKFLSBUF); close(pfd); } char disk_dev2[64]; snprintf(disk_dev2, sizeof(disk_dev2), "/dev/%s", parent_blk); int dfd = open(disk_dev2, O_RDONLY | O_CLOEXEC); if (dfd >= 0) { ioctl(dfd, BLKFLSBUF); close(dfd); } } system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); sync(); ac_log("[install] pre-mkfs cache flush done\n"); // Long settle — let the kernel + nvme driver fully release // the device. 5 seconds to be safe. usleep(5000000);
// Reformat. Retry up to 3 times if mkfs fails — the // kernel sometimes needs multiple passes after a fresh // GPT to release exclusive holds on the block device. // // CRITICAL: run mkfs in a subshell that PRESERVES its // exit code. The previous implementation used // mkfs ... > err 2>&1; cat err >> dlog // which made system() always return cat's exit code (0), // silently masking every mkfs failure and causing the // install to proceed onto an unformatted partition. // // The subshell pattern below captures mkfs's rc, tees // the output, and `exit $rc` propagates it back through // system(). const char *MKFS_ERR = "/tmp/mkfs-err.log"; snprintf(rcmd, sizeof(rcmd), "echo '--- mkfs attempt 1 ---' >> %s; " "(mkfs.vfat -F 32 -n AC-NATIVE %s > %s 2>&1; rc=$?; " "cat %s >> %s; exit $rc)", DLOG, devpath, MKFS_ERR, MKFS_ERR, DLOG); rrc = system(rcmd); int mkfs_exit = WIFEXITED(rrc) ? WEXITSTATUS(rrc) : -1; ac_log("[install] mkfs rc=%d attempt=1 (WIFEXITED=%d status=%d)\n", rrc, WIFEXITED(rrc), mkfs_exit); // Inline dump mkfs output so we see the exact error { FILE *mf = fopen(MKFS_ERR, "r"); if (mf) { char line[512]; while (fgets(line, sizeof(line), mf)) { size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; ac_log("[mkfs/1] %s\n", line); } fclose(mf); } } // Attempt 2–5 if needed (more retries with aggressive flush) for (int mkfs_try = 2; mkfs_try <= 5 && mkfs_exit != 0; mkfs_try++) { // Flush block device buffer cache before each retry { int pfd = open(devpath, O_RDONLY | O_CLOEXEC); if (pfd >= 0) { ioctl(pfd, BLKFLSBUF); close(pfd); } char dd2[64]; snprintf(dd2, sizeof(dd2), "/dev/%s", parent_blk); int dfd = open(dd2, O_RDONLY | O_CLOEXEC); if (dfd >= 0) { ioctl(dfd, BLKFLSBUF); close(dfd); } } sync(); system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true"); usleep(3000000); // 3s settle between retries snprintf(rcmd, sizeof(rcmd), "echo '--- mkfs attempt %d ---' >> %s; " "(mkfs.vfat -F 32 -n AC-NATIVE %s > %s 2>&1; rc=$?; " "cat %s >> %s; exit $rc)", mkfs_try, DLOG, devpath, MKFS_ERR, MKFS_ERR, DLOG); rrc = system(rcmd); mkfs_exit = WIFEXITED(rrc) ? WEXITSTATUS(rrc) : -1; ac_log("[install] mkfs rc=%d attempt=%d (WIFEXITED=%d status=%d)\n", rrc, mkfs_try, WIFEXITED(rrc), mkfs_exit); FILE *mf = fopen(MKFS_ERR, "r"); if (mf) { char line[512]; while (fgets(line, sizeof(line), mf)) { size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; ac_log("[mkfs/%d] %s\n", mkfs_try, line); } fclose(mf); } } rrc = (mkfs_exit == 0) ? 0 : -1; if (rrc != 0) { snprintf(install_fail_reason, sizeof(install_fail_reason), "format failed on %s", devpath); snprintf(install_fail_detail, sizeof(install_fail_detail), "mkfs.vfat rc=%d after repartition (see %s)", rrc, DLOG); if (display) draw_boot_status(graph, screen, display, "format failed!", pixel_scale); usleep(2000000); continue; } usleep(500000); // Remount and retry install_copy_phase: if (mount(devpath, "/tmp/hd", "vfat", 0, NULL) != 0) { ac_log("[install] remount failed after repartition\n"); snprintf(install_fail_reason, sizeof(install_fail_reason), "repartition failed on %s", devpath); snprintf(install_fail_detail, sizeof(install_fail_detail), "sfdisk=%d mkfs then mount failed", rrc); if (display) draw_boot_status(graph, screen, display, "repartition failed!", pixel_scale); usleep(2000000); continue; } mkdir("/tmp/hd/EFI", 0755); mkdir("/tmp/hd/EFI/BOOT", 0755); ac_log("[install] repartitioned OK, retrying copy\n"); } } // Copy kernel + initramfs onto the destination ESP. ALWAYS use // the kernel-direct layout, regardless of what the source USB // looks like: // // /EFI/BOOT/BOOTX64.EFI = the actual kernel (EFI stub) // /initramfs.cpio.gz = initramfs at ESP root // // Why not systemd-boot for installs? splash.efi as BOOTX64.EFI // worked on the source USB (because firmware found splash, ran // it, splash chained to LOADER.EFI = systemd-boot, sd-boot // loaded kernel + initramfs from the same FAT). But on the // installed disk the SAME splash.efi → "Chinese characters // scrolling, cuts to black, falls to boot selector" — symptom // of the firmware loading the EFI binary and getting confused // by partial output / failed chain. The kernel-direct boot // (kernel as BOOTX64.EFI) IS what the USB ACBOOT partition uses // and it boots cleanly on the same hardware. Just use that. // // The kernel's CONFIG_CMDLINE has `initrd=\initramfs.cpio.gz` // baked in, so the EFI stub finds initramfs at the ESP root // automatically — no loader config needed. long sz = 0; const char *kernel_to_install = (systemd_boot_layout || chainloader_layout) ? chain_kernel_src // KERNEL.EFI on source : kernel_src; // monolithic BOOTX64.EFI is the kernel long ksz = copy_file(kernel_to_install, "/tmp/hd/EFI/BOOT/BOOTX64.EFI"); ac_log("[install] kernel-direct: BOOTX64.EFI = %ld bytes (from %s)\n", ksz, kernel_to_install); long isz = 0; if (initramfs_src[0]) { const char *initramfs_name = strstr(initramfs_src, ".lz4") ? "initramfs.cpio.lz4" : "initramfs.cpio.gz"; char initramfs_dst[128]; snprintf(initramfs_dst, sizeof(initramfs_dst), "/tmp/hd/%s", initramfs_name); isz = copy_file(initramfs_src, initramfs_dst); ac_log("[install] initramfs: %ld bytes (from %s)\n", isz, initramfs_src); } else { ac_log("[install] no initramfs source — kernel must have it embedded\n"); } sz = (ksz > 0 && (isz > 0 || initramfs_src[0] == '\0')) ? (ksz + isz) : -1;
if (sz <= 0) { ac_log("[install] copy failed on %s (sz=%ld)\n", devpath, sz); snprintf(install_fail_reason, sizeof(install_fail_reason), "copy failed on %s", devpath); snprintf(install_fail_detail, sizeof(install_fail_detail), "install payload copy returned %ld bytes", sz); umount("/tmp/hd"); continue; }
{ // Also overwrite Windows Boot Manager path (ThinkPad BIOS often // boots this first regardless of boot order) — but only if space permits struct statvfs vfs; long free_bytes = 0; if (statvfs("/tmp/hd", &vfs) == 0) free_bytes = (long)vfs.f_bavail * (long)vfs.f_bsize; if (!systemd_boot_layout && free_bytes > sz + 1048576) { mkdir("/tmp/hd/EFI/Microsoft", 0755); mkdir("/tmp/hd/EFI/Microsoft/Boot", 0755); copy_file(chainloader_layout ? bootloader_src : kernel_src, "/tmp/hd/EFI/Microsoft/Boot/bootmgfw.efi"); }
// Preserve user config + wifi creds on installed disk if (access(config_src, F_OK) == 0) { copy_file(config_src, "/tmp/hd/config.json"); ac_log("[install] copied config.json\n"); } { char wifi_src[64]; snprintf(wifi_src, sizeof(wifi_src), "%s/wifi_creds.json", source_mount); if (access(wifi_src, F_OK) == 0) { copy_file(wifi_src, "/tmp/hd/wifi_creds.json"); ac_log("[install] copied wifi_creds.json\n"); } }
// Write install marker so next boot knows it's installed FILE *marker = fopen("/tmp/hd/EFI/BOOT/ac-installed", "w"); if (marker) { fputs("1\n", marker); fclose(marker); }
sync(); installed = 1; ac_log("[install] Installed from %s to %s (systemd-boot=%d chainloader=%d)\n", source_dev, devpath, systemd_boot_layout, chainloader_layout); }
umount("/tmp/hd");
// Register a UEFI NVRAM boot entry pointing at the freshly // written ESP. Without this, ThinkPad / most non-Mac firmware // won't try `\EFI\BOOT\BOOTX64.EFI` on internal disks at all // — only removable media gets the fallback path. The new // install would write fine but the firmware wouldn't see it // at boot, falling through to USB or a stale Fedora entry. // // Skipped silently if efivarfs isn't mounted (some firmware // exposes it read-only); install still completes — user can // boot via F12 menu and we'll add the entry on second boot. if (installed) { char parent_blk[32] = ""; get_parent_block(devpath + 5, parent_blk, sizeof(parent_blk)); int part_num = 1; { int len = (int)strlen(devpath); int i = len; while (i > 0 && devpath[i-1] >= '0' && devpath[i-1] <= '9') i--; if (i < len) part_num = atoi(devpath + i); } if (parent_blk[0] && access("/sys/firmware/efi/efivars", F_OK) == 0) { char ebcmd[512]; // Remove any prior "AC Native OS" entries (idempotent reinstall). snprintf(ebcmd, sizeof(ebcmd), "for n in $(efibootmgr 2>/dev/null | awk '/AC Native OS/{print substr($1,5,4)}'); do " "efibootmgr -B -b $n >/dev/null 2>&1; done"); system(ebcmd); // Add new entry pointing at our /EFI/BOOT/BOOTX64.EFI // on the new ESP. Position it first in BootOrder so // firmware tries it before any stale Fedora entries. snprintf(ebcmd, sizeof(ebcmd), "efibootmgr -c -d /dev/%s -p %d -L 'AC Native OS' " "-l '\\EFI\\BOOT\\BOOTX64.EFI' >> /tmp/install-debug.log 2>&1", parent_blk, part_num); int erc = system(ebcmd); ac_log("[install] efibootmgr add rc=%d for /dev/%s p%d\n", erc, parent_blk, part_num); } }
// Copy the install debug log onto the boot USB so post-mortem // is possible without re-running. tmpfs evaporates on reboot; // /mnt is the live USB partition. if (access("/tmp/install-debug.log", F_OK) == 0) { copy_file("/tmp/install-debug.log", "/mnt/install-debug.log"); sync(); } } } }
if (installed && display) { draw_boot_status(graph, screen, display, "installed to disk!", pixel_scale); usleep(800000); } else if (!installed) { ac_log("[install] No suitable HD partition found for install\n"); if (!install_fail_reason[0]) { snprintf(install_fail_reason, sizeof(install_fail_reason), "no internal FAT32/ESP partition found"); // Build detail: list what we tried int dpos = 0; dpos += snprintf(install_fail_detail + dpos, sizeof(install_fail_detail) - dpos, "usb=%s ", usb_blk[0] ? usb_blk : "?"); for (int i = 0; part_candidates[i]; i++) { const char *blk = part_candidates[i]; char dp[32]; snprintf(dp, sizeof(dp), "/sys/block/%s", blk); if (access(dp, F_OK) != 0) continue; int rem = (blk[0] == 's' && blk[1] == 'd') ? is_removable(blk) : 0; const char *skip = ""; if (usb_blk[0] && strcmp(blk, usb_blk) == 0) skip = "=boot"; else if (rem == 1) skip = "=USB"; else skip = "=tried"; dpos += snprintf(install_fail_detail + dpos, sizeof(install_fail_detail) - dpos, "%s%s ", blk, skip); } } } if (source_mounted_tmp) umount("/tmp/src");
// Dump the full install-debug.log inline to ac_log so the trace ALWAYS // lands in ac-native.log (which we know reliably survives to the USB). // This is the primary diagnostic channel — the parallel copy to // /mnt/install-debug.log below is a best-effort secondary that may fail // silently if /mnt got trashed by the repartition attempt. { FILE *src = fopen("/tmp/install-debug.log", "r"); if (src) { ac_log("[install] === /tmp/install-debug.log BEGIN ===\n"); char line[1024]; int line_count = 0; while (fgets(line, sizeof(line), src)) { // Strip trailing newline for consistent ac_log formatting size_t len = strlen(line); if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; ac_log("[install-debug] %s\n", line); line_count++; if (line_count > 500) { ac_log("[install] ... (truncated at 500 lines)\n"); break; } } ac_log("[install] === /tmp/install-debug.log END (%d lines) ===\n", line_count); fclose(src); } else { ac_log("[install] /tmp/install-debug.log not present (errno=%d)\n", errno); } }
// Parallel copy of the tmpfs log to /mnt (best effort). Even if the // inline dump above succeeded, keep this so the file is also visible // as a standalone artifact when /mnt is intact. { FILE *src = fopen("/tmp/install-debug.log", "rb"); if (src) { FILE *dst = fopen("/mnt/install-debug.log", "wb"); if (dst) { char buf[4096]; size_t n; while ((n = fread(buf, 1, sizeof(buf), src)) > 0) fwrite(buf, 1, n, dst); fflush(dst); fsync(fileno(dst)); fclose(dst); ac_log("[install] copied /tmp/install-debug.log → /mnt/install-debug.log\n"); } else { ac_log("[install] /mnt/install-debug.log copy failed (errno=%d: %s)\n", errno, strerror(errno)); } fclose(src); } }
return installed;}
static void frame_sync_60fps(struct timespec *next) { next->tv_nsec += 16666667; if (next->tv_nsec >= 1000000000) { next->tv_nsec -= 1000000000; next->tv_sec++; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next, NULL);}
// Check if a specific key is currently held using pre-opened fds (fast path)static int check_key_held_fds(int keycode, int *fds, int nfds) { for (int i = 0; i < nfds; i++) { unsigned long bits[(KEY_MAX + 7) / 8 / sizeof(unsigned long) + 1] = {0}; if (ioctl(fds[i], EVIOCGKEY(sizeof(bits)), bits) >= 0) { if (bits[keycode / (sizeof(unsigned long) * 8)] & (1UL << (keycode % (sizeof(unsigned long) * 8)))) return 1; } } return 0;}
// Get LA offset from UTC: 7 for PDT, 8 for PST// DST: second Sunday of March 2am PT → first Sunday of November 2am PTstatic int get_la_offset(void) { time_t now = time(NULL); struct tm *utc = gmtime(&now); int m = utc->tm_mon, y = utc->tm_year + 1900; if (m > 2 && m < 10) return 7; // Apr-Oct: PDT if (m < 2 || m > 10) return 8; // Jan-Feb, Dec: PST if (m == 2) { // March: find second Sunday struct tm mar1 = {0}; mar1.tm_year = y - 1900; mar1.tm_mon = 2; mar1.tm_mday = 1; mktime(&mar1); int secondSun = 8 + (7 - mar1.tm_wday) % 7; // DST starts at 2am PST = 10:00 UTC on that day if (utc->tm_mday > secondSun) return 7; if (utc->tm_mday < secondSun) return 8; return (utc->tm_hour >= 10) ? 7 : 8; } // November: find first Sunday struct tm nov1 = {0}; nov1.tm_year = y - 1900; nov1.tm_mon = 10; nov1.tm_mday = 1; mktime(&nov1); int firstSun = 1 + (7 - nov1.tm_wday) % 7; // DST ends at 2am PDT = 9:00 UTC on that day if (utc->tm_mday < firstSun) return 7; if (utc->tm_mday > firstSun) return 8; return (utc->tm_hour < 9) ? 7 : 8;}
static int get_la_hour(void) { time_t now = time(NULL); struct tm *utc = gmtime(&now); return (utc->tm_hour - get_la_offset() + 24) % 24;}
// Fill buf with the city name for the boot greeting. The geo piece writes// /mnt/last-city.txt after a successful IP lookup, so this reads whatever// was cached on a previous boot. When that cache is missing/empty (first// boot, before any IP lookup) it falls back to the flash-time preset city// (config.json "city", baked by `ac-inscribe --city`) so a device greets// from wherever it's shipped, then to "Los Angeles" as a last resort.static void read_cached_city(char *buf, size_t len) { if (!buf || len == 0) return; buf[0] = 0; FILE *f = fopen("/mnt/last-city.txt", "r"); if (f) { if (fgets(buf, (int)len, f)) { size_t n = strlen(buf); while (n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r' || buf[n-1] == ' ' || buf[n-1] == '\t')) { buf[--n] = 0; } } fclose(f); } if (buf[0] == 0) { // No live geolocation cache yet — fall back to the flash-time preset // city (where the device was shipped), then to "Los Angeles". const char *fallback = preset_city[0] ? preset_city : "Los Angeles"; strncpy(buf, fallback, len - 1); buf[len - 1] = 0; }}
static void play_install_prompt_beep(ACAudio *audio) { if (!audio || !audio->pcm) return; audio_synth(audio, WAVE_SINE, 620.0, 0.06, 0.40, 0.001, 0.05, 0.0); usleep(35000); audio_synth(audio, WAVE_SINE, 760.0, 0.06, 0.40, 0.001, 0.05, 0.0);}
static void play_install_accept_beep(ACAudio *audio) { if (!audio || !audio->pcm) return; audio_synth(audio, WAVE_TRIANGLE, 880.0, 0.08, 0.55, 0.001, 0.06, -0.1); usleep(45000); audio_synth(audio, WAVE_TRIANGLE, 1175.0, 0.10, 0.65, 0.001, 0.08, 0.1);}
static void play_install_reject_beep(ACAudio *audio) { if (!audio || !audio->pcm) return; audio_synth(audio, WAVE_SAWTOOTH, 260.0, 0.08, 0.45, 0.001, 0.06, 0.0); usleep(45000); audio_synth(audio, WAVE_SAWTOOTH, 180.0, 0.12, 0.45, 0.001, 0.09, 0.0);}
static void play_install_success_beep(ACAudio *audio) { if (!audio || !audio->pcm) return; audio_synth(audio, WAVE_TRIANGLE, 659.25, 0.12, 0.55, 0.001, 0.08, -0.2); usleep(50000); audio_synth(audio, WAVE_TRIANGLE, 783.99, 0.12, 0.60, 0.001, 0.08, 0.0); usleep(50000); audio_synth(audio, WAVE_TRIANGLE, 987.77, 0.16, 0.68, 0.001, 0.12, 0.2);}
static void play_install_failure_beep(ACAudio *audio) { if (!audio || !audio->pcm) return; audio_synth(audio, WAVE_SAWTOOTH, 180.0, 0.11, 0.50, 0.001, 0.08, 0.0); usleep(55000); audio_synth(audio, WAVE_SAWTOOTH, 140.0, 0.15, 0.50, 0.001, 0.11, 0.0);}
// Draw y/n install confirmation screen// Returns 1 if user confirms with Y, 0 if N/Escapestatic int draw_install_confirm(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, int *fds, int nfds, ACTts *tts, ACAudio *audio, int pixel_scale) { int is_dark = 1; // always dark struct timespec anim_time; clock_gettime(CLOCK_MONOTONIC, &anim_time);
if (tts) tts_speak(tts, "wipe and install AC native OS? this erases all data on the disk. press Y for yes, N for no"); play_install_prompt_beep(audio);
for (;;) { uint8_t bg = is_dark ? 20 : 255; graph_wipe(graph, (ACColor){bg, bg, (uint8_t)(bg + (is_dark ? 5 : 0)), 255});
uint8_t fg = is_dark ? 220 : 0; graph_ink(graph, (ACColor){fg, fg, fg, 255});
int tw = font_measure_matrix("wipe + install?", 2); font_draw_matrix(graph, "wipe + install?", (screen->width - tw) / 2, screen->height / 2 - 24, 2);
// Warning text — be explicit. The previous warning said "overwrite // EFI boot" which suggested only the ESP would change; the // operation actually wipes the entire disk now (sfdisk single ESP // spanning everything → kills Fedora root, swap, etc.). graph_ink(graph, (ACColor){220, 80, 80, 255}); const char *warn = "ERASES ALL DATA on internal disk"; int ww = font_measure_matrix(warn, 1); font_draw_matrix(graph, warn, (screen->width - ww) / 2, screen->height / 2 + 2, 1);
// Y/N prompt graph_ink(graph, (ACColor){60, 200, 60, 255}); const char *yn = "Y = yes N = no"; int yw = font_measure_matrix(yn, 1); font_draw_matrix(graph, yn, (screen->width - yw) / 2, screen->height / 2 + 20, 1);
ac_display_present(display, screen, pixel_scale); frame_sync_60fps(&anim_time);
// Read key events struct input_event ev; for (int ki = 0; ki < nfds; ki++) { while (read(fds[ki], &ev, sizeof(ev)) == sizeof(ev)) { if (ev.type == EV_KEY && ev.value == 1) { if (ev.code == KEY_Y) { play_install_accept_beep(audio); return 1; } if (ev.code == KEY_N || ev.code == KEY_ESC) { play_install_reject_beep(audio); return 0; } } } } }}
// Pause after install attempt so USB boots do not continue into the piece.// Returns 1 if reboot requested, 0 if user chose to continue boot (failure-only path).static int draw_install_reboot_prompt(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, ACInput *input, ACTts *tts, ACAudio *audio, int install_ok, int pixel_scale) { struct timespec anim_time; clock_gettime(CLOCK_MONOTONIC, &anim_time);
if (install_ok) { if (tts) tts_speak(tts, "install complete. remove USB stick. press R to reboot."); play_install_success_beep(audio); } else { if (tts) tts_speak(tts, "install failed. press R to reboot or escape to continue."); play_install_failure_beep(audio); }
int blink = 0; int no_input_frames = 0; while (running) { blink++; graph_wipe(graph, (ACColor){20, 20, 25, 255});
const char *title = install_ok ? "disk install complete" : "disk install failed"; ACColor title_color = install_ok ? (ACColor){90, 220, 120, 255} : (ACColor){220, 100, 90, 255}; graph_ink(graph, title_color); int tw = font_measure_matrix(title, 2); font_draw_matrix(graph, title, (screen->width - tw) / 2, screen->height / 2 - 34, 2);
graph_ink(graph, (ACColor){220, 220, 220, 255}); const char *line1 = install_ok ? "remove USB stick now" : "check EFI target and try again"; int l1w = font_measure_matrix(line1, 1); font_draw_matrix(graph, line1, (screen->width - l1w) / 2, screen->height / 2 + 0, 1);
// Show failure diagnostics if (!install_ok && install_fail_reason[0]) { graph_ink(graph, (ACColor){200, 160, 100, 255}); int rw = font_measure_matrix(install_fail_reason, 1); font_draw_matrix(graph, install_fail_reason, (screen->width - rw) / 2, screen->height / 2 + 16, 1);
if (install_fail_detail[0]) { graph_ink(graph, (ACColor){140, 140, 160, 255}); int dw = font_measure_matrix(install_fail_detail, 1); font_draw_matrix(graph, install_fail_detail, (screen->width - dw) / 2, screen->height / 2 + 28, 1); } }
int yoff = (!install_ok && install_fail_reason[0]) ? 16 : 0; graph_ink(graph, (ACColor){180, 180, 210, 255}); const char *line2 = "R/Enter = reboot"; int l2w = font_measure_matrix(line2, 1); font_draw_matrix(graph, line2, (screen->width - l2w) / 2, screen->height / 2 + 32 + yoff, 1);
if (!install_ok) { graph_ink(graph, (ACColor){150, 150, 170, 255}); const char *line3 = "Esc = continue USB boot"; int l3w = font_measure_matrix(line3, 1); font_draw_matrix(graph, line3, (screen->width - l3w) / 2, screen->height / 2 + 46 + yoff, 1); }
if ((blink % 60) < 36) { graph_ink(graph, (ACColor){120, 120, 140, 255}); const char *line4 = install_ok ? "waiting for reboot..." : "waiting for key..."; int l4w = font_measure_matrix(line4, 1); font_draw_matrix(graph, line4, (screen->width - l4w) / 2, screen->height / 2 + 62 + yoff, 1); }
ac_display_present(display, screen, pixel_scale); frame_sync_60fps(&anim_time);
if (!input) { no_input_frames++; if (no_input_frames > 900) return install_ok ? 1 : 0; // ~15s fallback continue; } input_poll(input); for (int i = 0; i < input->event_count; i++) { ACEvent *ev = &input->events[i]; if (ev->type != AC_EVENT_KEYBOARD_DOWN) continue;
if (ev->key_code == KEY_R || ev->key_code == KEY_ENTER || ev->key_code == KEY_KPENTER) { if (tts) tts_wait(tts); // let "install complete" TTS finish play_install_accept_beep(audio); usleep(300000); // let beep play return 1; } if (!install_ok && (ev->key_code == KEY_ESC || ev->key_code == KEY_SPACE)) { play_install_reject_beep(audio); return 0; } } } return install_ok ? 1 : 0;}
// Check if we booted from an installed (non-removable) disk// by looking for ac-installed marker on an internal ESP// Extract parent block device name from a partition path:// "nvme0n1p1" → "nvme0n1"// "mmcblk0p1" → "mmcblk0"// "sda1" → "sda"// NVMe and eMMC both use the "<dev>p<N>" partition-suffix scheme (because// the parent name ends in a digit), so strip the trailing "pN" for both.static void get_parent_block(const char *part, char *out, int out_sz) { out[0] = 0; int len = (int)strlen(part); if (len >= out_sz) return; // NVMe / eMMC: strip trailing "pN" (e.g. nvme0n1p1 → nvme0n1, // mmcblk0p1 → mmcblk0). if (strncmp(part, "nvme", 4) == 0 || strncmp(part, "mmcblk", 6) == 0) { int base_min = (part[0] == 'n') ? 4 : 6; for (int i = len - 1; i > base_min; i--) { if (part[i - 1] == 'p' && part[i] >= '0' && part[i] <= '9') { memcpy(out, part, i - 1); out[i - 1] = 0; return; } } } // SATA/USB: strip trailing digits (e.g. sda1 → sda) int i = len; while (i > 0 && part[i - 1] >= '0' && part[i - 1] <= '9') i--; if (i > 0 && i < out_sz) { memcpy(out, part, i); out[i] = 0; }}
static int is_installed_on_hd(void) { if (getpid() != 1) return 0; // not bare metal mkdir("/tmp/chk", 0755); const char *parts[] = { "/dev/nvme0n1p1", "/dev/nvme0n1p2", "/dev/mmcblk0p1", "/dev/mmcblk0p2", "/dev/sda1", "/dev/sdb1", NULL }; for (int i = 0; parts[i]; i++) { // Extract parent block device and skip removable (USB) drives char blk[32] = ""; get_parent_block(parts[i] + 5, blk, sizeof(blk)); fprintf(stderr, "[install-check] %s → parent=%s\n", parts[i], blk); if (blk[0] && is_removable(blk) == 1) { fprintf(stderr, "[install-check] → removable, skipping\n"); continue; } if (mount(parts[i], "/tmp/chk", "vfat", MS_RDONLY, NULL) != 0) { fprintf(stderr, "[install-check] → mount failed\n"); continue; } int found = access("/tmp/chk/EFI/BOOT/ac-installed", F_OK) == 0; umount("/tmp/chk"); fprintf(stderr, "[install-check] → mounted, ac-installed=%s\n", found ? "YES" : "no"); if (found) return 1; } fprintf(stderr, "[install-check] not installed on HD\n"); return 0;}
// Draw startup fade animation (black → white with title)// Returns 1 if user pressed W and confirmed install, 0 otherwisestatic int draw_startup_fade(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, ACTts *tts, ACAudio *audio, int pixel_scale) { struct timespec anim_time; clock_gettime(CLOCK_MONOTONIC, &anim_time); // Show install option whenever booting from USB (even if already installed — // user may want to update). Detect USB by checking if boot device is removable. int show_install = 0; if (log_dev[0]) { char boot_blk[32] = ""; get_parent_block(log_dev + 5, boot_blk, sizeof(boot_blk)); show_install = (boot_blk[0] && is_removable(boot_blk) == 1) ? 1 : 0; ac_log("[boot-anim] boot_dev=%s parent=%s removable=%d show_install=%d\n", log_dev, boot_blk, is_removable(boot_blk), show_install); } else { // No log_dev — check if any removable block device has our EFI boot file // If not, we're booted from internal disk (post-install) — don't show install show_install = 0; DIR *blkdir = opendir("/sys/block"); if (blkdir) { struct dirent *bent; while ((bent = readdir(blkdir)) != NULL) { if (bent->d_name[0] == '.') continue; if (is_removable(bent->d_name) == 1) { show_install = 1; ac_log("[boot-anim] removable device %s found, show_install=1\n", bent->d_name); break; } } closedir(blkdir); } if (!show_install) ac_log("[boot-anim] no removable media, show_install=0\n"); }
// Open evdev fds for key checking — retry until devices appear int key_fds[24]; int key_fd_count = 0; for (int retry = 0; retry < 20 && key_fd_count == 0; retry++) { DIR *dir = opendir("/dev/input"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) && key_fd_count < 24) { if (strncmp(ent->d_name, "event", 5) != 0) continue; char path[64]; snprintf(path, sizeof(path), "/dev/input/%s", ent->d_name); int fd = open(path, O_RDONLY | O_NONBLOCK); if (fd >= 0) { key_fds[key_fd_count++] = fd; fprintf(stderr, "[boot-anim] opened %s (fd %d)\n", path, fd); } } closedir(dir); } if (key_fd_count == 0) { fprintf(stderr, "[boot-anim] no input devices yet, waiting... (%d/20)\n", retry + 1); usleep(100000); // 100ms } } fprintf(stderr, "[boot-anim] %d event devices\n", key_fd_count);
// Start with a solid frame immediately (hides any kernel text) // White for daytime, black for evening/night int boot_hour = get_la_hour(); int boot_is_day = (boot_hour >= 7 && boot_hour < 18); graph_wipe(graph, boot_is_day ? (ACColor){255, 255, 255, 255} : (ACColor){0, 0, 0, 255}); ac_display_present(display, screen, pixel_scale);
// Check if this is a fresh boot of a new version int is_new_version = 0;#ifdef AC_GIT_HASH#ifdef AC_BUILD_TS { const char *current_ver = AC_GIT_HASH "-" AC_BUILD_TS; char prev_ver[128] = ""; FILE *vf = fopen("/mnt/booted-version", "r"); if (vf) { if (fgets(prev_ver, sizeof(prev_ver), vf)) { // Strip trailing newline char *nl = strchr(prev_ver, '\n'); if (nl) *nl = 0; } fclose(vf); } is_new_version = (strcmp(prev_ver, current_ver) != 0); ac_log("[boot] version=%s prev=%s fresh=%s\n", current_ver, prev_ver, is_new_version ? "YES" : "no"); // Write current version immediately so next boot sees it vf = fopen("/mnt/booted-version", "w"); if (vf) { fprintf(vf, "%s", current_ver); fclose(vf); } // Append to boot history log (persists across reboots) vf = fopen("/mnt/boot-history.log", "a"); if (vf) { time_t now_t = time(NULL); struct tm *tm = gmtime(&now_t); char ts[32]; strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%SZ", tm); fprintf(vf, "%s %s %s\n", ts, current_ver, is_new_version ? "FRESH" : "same"); fclose(vf); } sync(); }#endif#endif
// Generate or load persistent machine ID (needs /mnt mounted) init_machine_id();
// Initialize machines monitoring daemon machines_init(&g_machines);
// LAN dev server: HTTP control endpoint + mDNS (.local) responder#ifdef AC_BUILD_NAME lanserv_start(AC_BUILD_NAME);#else lanserv_start("dev");#endif
// Read cached city once for both TTS greeting (f==10) and subtitle (f>130). char greet_city[96]; read_cached_city(greet_city, sizeof greet_city);
// 120 frames = 2 second animation (was 180/3s — shaved 1s off every boot). // W press → halt and show y/n confirmation. // Any other key → skip animation and start playing. // Keys accepted after frame 40 (667ms hold so the W hint is legible). int total_frames = 120; int hold_frames = 40; int skip_anim = 0; int w_pressed = 0; // Matrix-rain background state — persistent across frames. One entry // per column; column width fixed at 10 px. #define RAIN_MAX_COLS 128 int rain_col_y[RAIN_MAX_COLS]; int rain_col_speed[RAIN_MAX_COLS]; unsigned int rain_col_seed[RAIN_MAX_COLS]; { unsigned int s = (unsigned int)time(NULL); for (int c = 0; c < RAIN_MAX_COLS; c++) { s ^= s << 13; s ^= s >> 17; s ^= s << 5; rain_col_y[c] = -(int)(s % 400); s ^= s << 13; s ^= s >> 17; s ^= s << 5; rain_col_speed[c] = 2 + (int)(s % 4); s ^= s << 13; s ^= s >> 17; s ^= s << 5; rain_col_seed[c] = s; } } for (int f = 0; f < total_frames && !skip_anim && !w_pressed; f++) { double t = (double)f / (double)total_frames;
// Drain all key events — detect W press or other-key skip { struct input_event ev; for (int ki = 0; ki < key_fd_count; ki++) { while (read(key_fds[ki], &ev, sizeof(ev)) == sizeof(ev)) { if (ev.type == EV_KEY && ev.value == 1) { if (f < hold_frames) { fprintf(stderr, "[boot-anim] drained key %d at f=%d (hold period)\n", ev.code, f); continue; } fprintf(stderr, "[boot-anim] key %d at f=%d\n", ev.code, f); if (ev.code == KEY_W && show_install) { w_pressed = 1; } else if (ev.code != KEY_RESERVED) { skip_anim = 1; } } } } }
// Startup greeting — time-of-day + handle, or boot melody if no handle if (f == 10) { const char *at = strchr(boot_title, '@'); if (at && tts) { // Personalized TTS greeting char greet[256]; int hour = get_la_hour(); const char *tod; if (hour >= 5 && hour < 12) tod = "good morning"; else if (hour >= 12 && hour < 17) tod = "good afternoon"; else tod = "good evening";#ifdef AC_BUILD_NAME char name_tts[64]; strncpy(name_tts, AC_BUILD_NAME, sizeof(name_tts) - 1); name_tts[sizeof(name_tts) - 1] = 0; for (char *p = name_tts; *p; p++) { if (*p == '-') *p = ' '; } snprintf(greet, sizeof(greet), "%s %s. enjoy %s! %s.", tod, at + 1, greet_city, name_tts);#else snprintf(greet, sizeof(greet), "%s %s. enjoy %s!", tod, at + 1, greet_city);#endif tts_speak(tts, greet); } else if (audio) { // No handle — play a short ascending arpeggio (C E G C') audio_synth(audio, WAVE_SINE, 523.3, 0.15, 0.6, 0.003, 0.10, -0.3); // C5 } } // Stagger the arpeggio notes across frames for no-handle boot if (!strchr(boot_title, '@') && audio) { if (f == 20) audio_synth(audio, WAVE_SINE, 659.3, 0.15, 0.6, 0.003, 0.10, 0.0); // E5 if (f == 30) audio_synth(audio, WAVE_SINE, 784.0, 0.15, 0.6, 0.003, 0.10, 0.3); // G5 if (f == 42) audio_synth(audio, WAVE_SINE, 1047.0, 0.20, 0.5, 0.003, 0.15, 0.0); // C6 } // W hint is visual only — no TTS
// Fade from black/white to time-of-day themed bg (complete in first 0.3s) double fade_t = t * 3.33; if (fade_t > 1.0) fade_t = 1.0; int hour = get_la_hour(); int is_day = (hour >= 7 && hour < 18); // light mode 7am-6pm int target_r, target_g, target_b; if (is_day) { // Light mode: soft warm backgrounds if (hour >= 7 && hour < 12) { target_r = 235; target_g = 230; target_b = 220; // morning cream } else { target_r = 240; target_g = 235; target_b = 215; // afternoon warm white } } else { // Dark mode: rich atmospheric backgrounds if (hour >= 5 && hour < 7) { target_r = 100; target_g = 45; target_b = 20; // sunrise orange } else if (hour >= 18 && hour < 20) { target_r = 80; target_g = 25; target_b = 60; // sunset purple } else { target_r = 15; target_g = 15; target_b = 40; // night deep blue } } int start_r = is_day ? 255 : 0; int start_g = is_day ? 255 : 0; int start_b = is_day ? 255 : 0; int bg_r = start_r + (int)((target_r - start_r) * fade_t); int bg_g = start_g + (int)((target_g - start_g) * fade_t); int bg_b = start_b + (int)((target_b - start_b) * fade_t); graph_wipe(graph, (ACColor){(uint8_t)bg_r, (uint8_t)bg_g, (uint8_t)bg_b, 255});
// Matrix-rain BG: pseudo-CJK glyphs falling in columns. Started // as an homage to the "garbled-character" bug we hit when the // firmware loaded a chain-loading EFI binary directly — user // liked the look, so we're keeping it as a boot aesthetic. // Glyphs are random 7x9 pixel patterns drawn per-frame with a // per-column xorshift seed; no CJK font needed. Brighter leader // + fading trail for the classic terminal-rain feel. { int col_w = 10; int glyph_w = 7, glyph_h = 9; int nc = screen->width / col_w; if (nc > RAIN_MAX_COLS) nc = RAIN_MAX_COLS; // Slight fade toward title, so rain doesn't fight the text. int rain_alpha_scale = is_day ? 80 : 180; for (int c = 0; c < nc; c++) { rain_col_y[c] += rain_col_speed[c]; if (rain_col_y[c] > screen->height + glyph_h * 12) { unsigned int s = rain_col_seed[c]; s ^= s << 13; s ^= s >> 17; s ^= s << 5; rain_col_seed[c] = s; rain_col_y[c] = -(int)(s % 300); rain_col_speed[c] = 2 + (int)(s % 4); } int cx = c * col_w + 1; // Trail of ~10 glyphs fading behind the leader. for (int trail = 0; trail < 10; trail++) { int gy = rain_col_y[c] - trail * glyph_h; if (gy < -glyph_h || gy >= screen->height) continue; int tb = (trail == 0) ? 220 : 180 - trail * 18; if (tb < 15) continue; int b = (tb * rain_alpha_scale) >> 8; // Leader glows a bit whitish — classic matrix-rain tip. ACColor cg = (trail == 0) ? (ACColor){ (uint8_t)(b * 0.8), 255, (uint8_t)(b * 0.8), (uint8_t)b } : (ACColor){ 40, (uint8_t)(b * 0.9 + 20), (uint8_t)(b * 0.4), (uint8_t)b }; graph_ink(graph, cg); // Random 7x9 bitmap from deterministic seed + column + trail + slow frame shimmer. unsigned int gseed = rain_col_seed[c] ^ ((unsigned)trail * 2654435761u) ^ ((unsigned)(f / 4) * 2246822519u); for (int row = 0; row < glyph_h; row++) { for (int col = 0; col < glyph_w; col++) { gseed ^= gseed << 13; gseed ^= gseed >> 17; gseed ^= gseed << 5; if (gseed & 1) graph_plot(graph, cx + col, gy + row); } } } } }
// Device-id badge: small "acN" in the top-right corner. Fades in // with the splash so it's not visible during the dark moment. // Only rendered when a slot is registered — unassigned devices // get no badge (avoids confusing "this is acNothing" noise). if (ac_device_slot[0]) { int badge_alpha = (int)(180.0 * fade_t); graph_ink(graph, is_day ? (ACColor){80, 100, 130, (uint8_t)badge_alpha} : (ACColor){180, 200, 230, (uint8_t)badge_alpha}); int bw = font_measure_matrix(ac_device_slot, 1); font_draw_matrix(graph, ac_device_slot, screen->width - bw - 6, 6, 1); }
// Title — per-handle palette (fallback rainbow), animated pulse int alpha = (int)(255.0 * fade_t); if (alpha > 0) { const char *title = boot_title; // Auto-scale: use 3x unless title is too wide, then 2x int scale = 3; int tw = font_measure_matrix(title, scale); if (tw > screen->width - 20) { scale = 2; tw = font_measure_matrix(title, scale); } int tx = (screen->width - tw) / 2; int ty = screen->height / 2 - 20; for (int ci = 0; title[ci]; ci++) { ACColor cc = title_char_color(ci, f, alpha); graph_ink(graph, cc); char ch[2] = { title[ci], 0 }; tx = font_draw_matrix(graph, ch, tx, ty, scale); } }
// Version + build name + build date (high-contrast panel, top-right)#ifdef AC_GIT_HASH if (alpha > 40) { char ver[64]; char bts[64]; char bname[64] = ""; char ddrv[64] = ""; snprintf(ver, sizeof(ver), "version %s", AC_GIT_HASH);#ifdef AC_BUILD_TS snprintf(bts, sizeof(bts), "%s", AC_BUILD_TS);#else snprintf(bts, sizeof(bts), "build unknown");#endif#ifdef AC_BUILD_NAME snprintf(bname, sizeof(bname), "%s", AC_BUILD_NAME);#endif const char *driver = drm_display_driver(display); snprintf(ddrv, sizeof(ddrv), "display %s", driver); int wv = font_measure_matrix(ver, 1); int wt = font_measure_matrix(bts, 1); int wn = bname[0] ? font_measure_matrix(bname, 1) : 0; int wd = font_measure_matrix(ddrv, 1); int max_w = wv; if (wt > max_w) max_w = wt; if (wn > max_w) max_w = wn; if (wd > max_w) max_w = wd; int panel_w = max_w + 8; int panel_h = (bname[0] ? 28 : 20) + 8; int panel_x = screen->width - panel_w - 3; int panel_y = 3; graph_ink(graph, is_day ? (ACColor){255, 255, 255, (uint8_t)(alpha * 0.7)} : (ACColor){0, 0, 0, (uint8_t)(alpha * 0.82)}); graph_box(graph, panel_x, panel_y, panel_w, panel_h, 1); // Build name (top line) if (bname[0]) { graph_ink(graph, is_day ? (ACColor){140, 100, 0, (uint8_t)alpha} : (ACColor){255, 200, 60, (uint8_t)alpha}); font_draw_matrix(graph, bname, panel_x + 4, panel_y + 3, 1); } int line_y = panel_y + (bname[0] ? 11 : 3); graph_ink(graph, is_day ? (ACColor){60, 60, 60, (uint8_t)alpha} : (ACColor){255, 255, 255, (uint8_t)alpha}); font_draw_matrix(graph, ver, panel_x + 4, line_y, 1); graph_ink(graph, is_day ? (ACColor){80, 100, 90, (uint8_t)alpha} : (ACColor){210, 235, 220, (uint8_t)alpha}); font_draw_matrix(graph, bts, panel_x + 4, line_y + 8, 1); // Display driver line graph_ink(graph, is_day ? (ACColor){90, 60, 120, (uint8_t)alpha} : (ACColor){180, 160, 255, (uint8_t)alpha}); font_draw_matrix(graph, ddrv, panel_x + 4, line_y + 16, 1); // "FRESH" badge when first boot of this version if (is_new_version) { graph_ink(graph, is_day ? (ACColor){0, 140, 60, (uint8_t)alpha} : (ACColor){80, 255, 120, (uint8_t)alpha}); font_draw_matrix(graph, "FRESH", panel_x - font_measure_matrix("FRESH", 1) - 4, panel_y + 6, 1); } }#endif
// Subtitle: prefer the user's stored mood (boot_mood) over the // default "enjoy <city>!" line. Mood comes from /mnt/last-mood // (refreshed on the previous boot's wifi-connect) or the // inscription bake. Fade in over the same window either way. if (f > 80) { double sub_t = (double)(f - 80) / 20.0; if (sub_t > 1.0) sub_t = 1.0; int sub_alpha = (int)(180.0 * sub_t); graph_ink(graph, is_day ? (ACColor){120, 100, 80, (uint8_t)sub_alpha} : (ACColor){220, 180, 140, (uint8_t)sub_alpha}); char subtitle[256]; if (boot_mood[0]) { snprintf(subtitle, sizeof subtitle, "%s", boot_mood); } else { snprintf(subtitle, sizeof subtitle, "enjoy %s!", greet_city); } int sw = font_measure_matrix(subtitle, 1); font_draw_matrix(graph, subtitle, (screen->width - sw) / 2, screen->height / 2 + 10, 1); }
// Pals logo (top-left): pixelified rasterization of pals.svg. // 36x22 grid, drawn at 2x scale so it reads as ~72x44 px on the // splash. Pink ink (#cd5c9b) matches the SVG fill; fades in with // the rest of the splash via the shared `alpha`. if (alpha > 30) { static const char pals[22][37] = { ".........#...........####...........", ".......#####........######...####...", "......#######.......##..##.######...", "......##...##.......##..#####.###...", "......###..######.#####.########....", ".......##..############.##.###......", "......###.......###.......###.......", ".....###.....#########....##........", "....#####....#########....##........", "...##.####...##.....##....##........", ".###.#####...##.....##....###.......", ".########....##....###..#..##..###..", ".#####.##....###...###.###.########.", "..##...##.###.##...##.#####.####.##.", "......#######.##...##.##.###..#####.", "......##.##.####...##.##..#######...", "......#####.####..######...###......", ".....##.##..####..##.##.............", ".....##.##..####..##.##.............", "....######..####..##.##.............", ".....####...####..####..............", "......##.....##....##...............", }; int pa = (int)((alpha > 200 ? 200 : alpha) * 1.0); int origin_x = 8; int origin_y = 8; for (int py = 0; py < 22; py++) { for (int px = 0; px < 36; px++) { if (pals[py][px] != '#') continue; graph_ink(graph, (ACColor){205, 92, 155, (uint8_t)pa}); graph_box(graph, origin_x + px*2, origin_y + py*2, 2, 2, 1); } } }
// Auth badges (bottom-left): pixel crab = Claude, pixel octocat = GitHub // Badges — scaled to appear earlier in the shorter animation. if (f > 40 && alpha > 80) { int badge_x = 6; int badge_y = screen->height - 22; double badge_t = (double)(f - 40) / 30.0; if (badge_t > 1.0) badge_t = 1.0; int ba = (int)(220.0 * badge_t); // badge alpha
// 11x9 pixel crab (Claude/Anthropic) if (access("/claude-token", F_OK) == 0 || getenv("CLAUDE_CODE_OAUTH_TOKEN")) { static const char crab[9][12] = { " . . ", " . . ", " ..##.##.. ", ".# #### #.", ". ####### .", " ####### ", " ## . ## ", " . . ", " . . ", }; for (int cy = 0; cy < 9; cy++) for (int cx = 0; cx < 11; cx++) { char c = crab[cy][cx]; if (c == '#') graph_ink(graph, (ACColor){255, 120, 50, (uint8_t)ba}); else if (c == '.') graph_ink(graph, (ACColor){200, 90, 30, (uint8_t)(ba*2/3)}); else continue; graph_box(graph, badge_x + cx*2, badge_y + cy*2, 2, 2, 1); } badge_x += 28; } // 11x11 pixel octocat (GitHub) if (access("/github-pat", F_OK) == 0 || getenv("GH_TOKEN")) { static const char octo[11][12] = { " .###. ", " ####### ", " ## o#o ## ", " ######### ", " ## ### ## ", " ####### ", " ##### ", " .# . #. ", " .# . #. ", " . . . ", ". . .", }; for (int cy = 0; cy < 11; cy++) for (int cx = 0; cx < 11; cx++) { char c = octo[cy][cx]; if (c == '#') graph_ink(graph, (ACColor){180, 210, 255, (uint8_t)ba}); else if (c == 'o') graph_ink(graph, (ACColor){60, 80, 120, (uint8_t)ba}); else if (c == '.') graph_ink(graph, (ACColor){120, 150, 200, (uint8_t)(ba*2/3)}); else continue; graph_box(graph, badge_x + cx*2, badge_y + cy*2, 2, 2, 1); } badge_x += 28; } // 11x11 pixel "(λ)" — Common Lisp / Tangled push access. // Shown when a tangled SSH key was baked into the initramfs so on- // device git can push to knot.aesthetic.computer alongside GitHub. if (access("/tangled-key", F_OK) == 0) { static const char lambda[11][12] = { " .#####. ", " #.......# ", "#....#....#", "#....#....#", "#...#.#...#", "#..#...#..#", "#.#.....#.#", "#....#.#..#", "#...#...#.#", " #.......# ", " .#####. ", }; for (int cy = 0; cy < 11; cy++) for (int cx = 0; cx < 11; cx++) { char c = lambda[cy][cx]; if (c == '#') graph_ink(graph, (ACColor){200, 150, 240, (uint8_t)ba}); else if (c == '.') graph_ink(graph, (ACColor){140, 100, 200, (uint8_t)(ba*2/3)}); else continue; graph_box(graph, badge_x + cx*2, badge_y + cy*2, 2, 2, 1); } badge_x += 28; } }
// Animated triangles — geometric decoration if (alpha > 30) { int tri_alpha = (int)(alpha * 0.15); int W = screen->width; int H = screen->height; // Drifting triangles based on frame counter for (int ti = 0; ti < 6; ti++) { double phase = (double)f * 0.02 + ti * 1.047; // 60° apart int cx = (int)(W * 0.5 + W * 0.35 * sin(phase + 1.5708)); int cy = (int)(H * 0.5 + H * 0.3 * sin(phase * 0.7)); int sz = 8 + ti * 3 + (int)(4.0 * sin(f * 0.05 + ti)); ACColor tc = is_day ? (ACColor){180 - ti*15, 140 - ti*10, 120, (uint8_t)tri_alpha} : (ACColor){80 + ti*20, 60 + ti*15, 120 + ti*10, (uint8_t)tri_alpha}; graph_ink(graph, tc); // Draw triangle as 3 lines int x0 = cx, y0 = cy - sz; int x1 = cx - sz, y1 = cy + sz/2; int x2 = cx + sz, y2 = cy + sz/2; graph_line(graph, x0, y0, x1, y1); graph_line(graph, x1, y1, x2, y2); graph_line(graph, x2, y2, x0, y0); } }
// Bottom: shrinking time bar int bar_full = screen->width - 40; int bar_remaining = (int)((1.0 - t) * bar_full); if (bar_remaining > 0) { graph_ink(graph, (ACColor){200, 150, 180, (uint8_t)(80 * (1.0 - t))}); graph_box(graph, 20, screen->height - 6, bar_remaining, 3, 1); }
// Animated W install prompt if (alpha > 100 && show_install) { // Pulsing box behind the text double pulse = 0.5 + 0.5 * sin(f * 0.1); int pa = (int)(40 + 30 * pulse); const char *hint = is_installed_on_hd() ? "W: update" : "W: install to disk"; int hw = font_measure_matrix(hint, 1); int hx = (screen->width - hw) / 2; int hy = screen->height - 20; // Pulsing background pill graph_ink(graph, is_day ? (ACColor){200, 160, 120, (uint8_t)pa} : (ACColor){60, 40, 80, (uint8_t)pa}); graph_box(graph, hx - 4, hy - 2, hw + 8, 12, 1); // Triangle arrow pointing down at the text int ax = hx - 10; int ay = hy + 3; graph_ink(graph, is_day ? (ACColor){180, 120, 60, (uint8_t)(alpha / 2)} : (ACColor){200, 150, 255, (uint8_t)(alpha / 2)}); graph_line(graph, ax, ay - 3, ax, ay + 3); graph_line(graph, ax, ay + 3, ax - 3, ay); // Text with higher contrast graph_ink(graph, is_day ? (ACColor){120, 60, 0, (uint8_t)(alpha * 2 / 3)} : (ACColor){220, 180, 255, (uint8_t)(alpha * 2 / 3)}); font_draw_matrix(graph, hint, hx, hy, 1); }
ac_display_present(display, screen, pixel_scale); frame_sync_60fps(&anim_time); }
// If W was pressed, show y/n confirmation int result = 0; if (w_pressed) { result = draw_install_confirm(graph, screen, display, key_fds, key_fd_count, tts, audio, pixel_scale); }
for (int i = 0; i < key_fd_count; i++) close(key_fds[i]); return result;}
// Draw a status frame during boot (white bg, bouncy title + status text)// Renders multiple frames with a bounce animationstatic void draw_boot_status(ACGraph *graph, ACFramebuffer *screen, ACDisplay *display, const char *status, int pixel_scale) { static int boot_frame = 0; // Time-of-day themed background (light for day, dark for night) int la_hour = get_la_hour(); int is_day = (la_hour >= 7 && la_hour < 18); uint8_t bg_r, bg_g, bg_b; if (is_day) { if (la_hour < 12) { bg_r = 235; bg_g = 230; bg_b = 220; // morning cream } else { bg_r = 240; bg_g = 235; bg_b = 215; // afternoon warm white } } else { if (la_hour >= 5 && la_hour < 7) { bg_r = 100; bg_g = 45; bg_b = 20; // sunrise orange } else if (la_hour >= 18 && la_hour < 20) { bg_r = 80; bg_g = 25; bg_b = 60; // sunset purple } else { bg_r = 15; bg_g = 15; bg_b = 40; // night deep blue } }
struct timespec anim_time; clock_gettime(CLOCK_MONOTONIC, &anim_time);
// Animate for 20 frames (~333ms) per status change for (int af = 0; af < 20; af++) { boot_frame++; graph_wipe(graph, (ACColor){bg_r, bg_g, bg_b, 255});
// Rainbow stripes at top and bottom { int stripe_h = 3; int num_stripes = 8; int band = num_stripes * stripe_h; for (int s = 0; s < num_stripes; s++) { double hue = fmod((double)s / num_stripes * 360.0 + boot_frame * 3.0, 360.0); double h6 = hue / 60.0; int hi = (int)h6 % 6; double fr = h6 - (int)h6; double cr, cg, cb; switch (hi) { case 0: cr = 1; cg = fr; cb = 0; break; case 1: cr = 1-fr; cg = 1; cb = 0; break; case 2: cr = 0; cg = 1; cb = fr; break; case 3: cr = 0; cg = 1-fr; cb = 1; break; case 4: cr = fr; cg = 0; cb = 1; break; default: cr = 1; cg = 0; cb = 1-fr; break; } int y_top = s * stripe_h; int y_bot = screen->height - band + s * stripe_h; graph_ink(graph, (ACColor){(uint8_t)(cr*200), (uint8_t)(cg*200), (uint8_t)(cb*200), 80}); graph_box(graph, 0, y_top, screen->width, stripe_h, 1); graph_box(graph, 0, y_bot, screen->width, stripe_h, 1); } }
// Bounce: title oscillates with a decaying sine double bounce_t = (double)af / 20.0; int bounce_y = (int)(6.0 * sin(bounce_t * 3.14159 * 2) * (1.0 - bounce_t));
// Title: boot_title with per-handle colors int tw = font_measure_matrix(boot_title, 3); int tx = (screen->width - tw) / 2; int ty = screen->height / 2 - 20 + bounce_y; for (int ci = 0; boot_title[ci]; ci++) { ACColor cc = title_char_color(ci, boot_frame, 255); graph_ink(graph, cc); char ch[2] = { boot_title[ci], 0 }; tx = font_draw_matrix(graph, ch, tx, ty, 3); }
// Subtitle (slight counter-bounce) uint8_t sub = is_day ? 100 : 140; graph_ink(graph, (ACColor){sub, sub, sub, 255}); int sw = font_measure_matrix("aesthetic.computer", 1); font_draw_matrix(graph, "aesthetic.computer", (screen->width - sw) / 2, screen->height / 2 + 10 - bounce_y / 3, 1);
// Status text — slides in from right if (status) { int slide = (int)((1.0 - bounce_t) * 40); if (slide < 0) slide = 0; uint8_t sc = is_day ? 80 : 120; graph_ink(graph, (ACColor){sc, sc, sc, (uint8_t)(255 * bounce_t)}); int stw = font_measure_matrix(status, 1); font_draw_matrix(graph, status, (screen->width - stw) / 2 + slide, screen->height / 2 + 26, 1); }
// Spinning dot indicator (rotates each boot_frame) { int cx = screen->width / 2; int cy = screen->height / 2 + 42; double angle = boot_frame * 0.15; for (int d = 0; d < 4; d++) { double a = angle + d * 1.5708; // 90° apart int dx = (int)(6.0 * cos(a)); int dy = (int)(3.0 * sin(a)); uint8_t bright = (d == 0) ? 200 : 80; graph_ink(graph, (ACColor){bright, bright, bright, 255}); graph_box(graph, cx + dx - 1, cy + dy - 1, 2, 2, 1); } }
ac_display_present(display, screen, pixel_scale); frame_sync_60fps(&anim_time); }}
// ─── SHUTDOWN ANIMATION ──────────────────────────────────────────────────// The canonical "bye @handle" farewell — a 1.5s chaotic red/white strobe// with jittery title and final black frame. Extracted from the inline// version that used to live in the power-button handler so that every// poweroff path (main-loop power button, ac_poweroff() called from JS,// ac_poweroff() called from crash/OTA paths) produces the same sign-off.//// Skipped (no-op) if the display context isn't ready yet — e.g. an// emergency poweroff during early boot before main() has initialized// graphics.//// Intentionally does NOT play TTS / shutdown chime. Those are pre-roll// hooks (they start before the visual, so the farewell audio already has// a beat of head-room by the time the strobe begins). Callers that have// tts/audio handles in scope (the power-button path) trigger those// directly before calling this function.static void draw_shutdown_anim(void) { // Single-shot guard: the main-loop power-button handler calls this // inline, then sets running=0 and falls through to cleanup, which // ends in ac_poweroff() — which would call this again post-cleanup // on freed screen/display memory. Run only once per process. static int already_played = 0; if (already_played) return;
ACGraph *graph = g_shutdown_graph; ACFramebuffer *screen = g_shutdown_screen; ACDisplay *display = (ACDisplay *)g_display; int pixel_scale = g_shutdown_pixel_scale; if (!graph || !screen || !display) return; already_played = 1;
// Build the title from the current boot_title. // "good morning @jeffrey" → "bye @jeffrey" // "notepat" (no handle) → "bye" char bye_title[80]; { const char *at = strchr(boot_title, '@'); if (at) snprintf(bye_title, sizeof(bye_title), "bye @%s", at + 1); else snprintf(bye_title, sizeof(bye_title), "bye"); }
struct timespec anim_time; clock_gettime(CLOCK_MONOTONIC, &anim_time);
for (int f = 0; f < 90; f++) { // 90 frames @ 60fps = 1.5s double t = (double)f / 90.0;
// Chaotic strobe: alternate red/white/black with a pseudo-random cycle int phase = (f * 7 + f / 3) % 6; uint8_t br, bg_g, bb; if (phase < 2) { br = 220; bg_g = 20; bb = 20; } // red else if (phase < 3) { br = 255; bg_g = 255; bb = 255; } // white else if (phase < 5) { br = 180; bg_g = 0; bb = 0; } // dark red else { br = 10; bg_g = 10; bb = 10; } // near black
// Fade intensity toward end double fade = 1.0 - t * t; br = (uint8_t)(br * fade); bg_g = (uint8_t)(bg_g * fade); bb = (uint8_t)(bb * fade); graph_wipe(graph, (ACColor){br, bg_g, bb, 255});
// Title text — jitter position, flicker between red and white if (t < 0.85) { int alpha = (int)(255.0 * (1.0 - t / 0.85)); int jx = (f * 13 % 7) - 3; // -3 to +3 pixel jitter int jy = (f * 17 % 5) - 2; // -2 to +2 uint8_t tr = (f % 3 == 0) ? 255 : 200; uint8_t tg = (f % 3 == 0) ? 255 : 40; uint8_t tb = (f % 3 == 0) ? 255 : 40; graph_ink(graph, (ACColor){tr, tg, tb, (uint8_t)alpha}); int tw = font_measure_matrix(bye_title, 3); font_draw_matrix(graph, bye_title, (screen->width - tw) / 2 + jx, screen->height / 2 - 20 + jy, 3); graph_ink(graph, (ACColor){(uint8_t)(120 * fade), 40, 40, (uint8_t)(alpha / 2)}); int sw = font_measure_matrix("aesthetic.computer", 1); font_draw_matrix(graph, "aesthetic.computer", (screen->width - sw) / 2 + jx / 2, screen->height / 2 + 10 + jy / 2, 1); }
ac_display_present(display, screen, pixel_scale); frame_sync_60fps(&anim_time); }
// Final black frame + suppress kernel console output so halt lands clean. graph_wipe(graph, (ACColor){0, 0, 0, 255}); ac_display_present(display, screen, pixel_scale); { FILE *pl = fopen("/proc/sys/kernel/printk", "w"); if (pl) { fputs("0 0 0 0", pl); fclose(pl); } FILE *vc = fopen("/dev/tty0", "w"); if (vc) { fputs("\033[?25l\033[2J", vc); fclose(vc); } }}
// Audio recording tap callback (adapts rec_callback signature to recorder_submit_audio)static void rec_audio_tap(const int16_t *pcm, int frames, void *userdata) { recorder_submit_audio((ACRecorder *)userdata, pcm, frames);}
// Tape recording state — the red "rolling" overlay in the top-left and// the elapsed-time counter both read from these globals, which are set// by the PrintScreen key handler when a tape starts.static volatile int g_tape_recording = 0; // 1 while actively recordingstatic char g_tape_current_path[256] = {0}; // /mnt/tapes/<slug>.mp4static time_t g_tape_start_sec = 0; // wall-clock start for elapsed display
// Fire-and-forget tape upload: shells out a curl script that// 1. requests a presigned PUT URL from /api/presigned-upload-url/mp4/<slug>/user// 2. PUTs the MP4 directly to DO Spaces// 3. POSTs /api/track-media with ext=mp4 + metadata// Runs in a child process so the audio+render loop never blocks on network.// The script reads config.json for {handle, token}.static void tape_upload_async(const char *tape_path) { if (!tape_path || !tape_path[0]) return; // Sanity: only upload MP4 tapes, only if config.json has a token FILE *cf = fopen("/mnt/config.json", "r"); if (!cf) { ac_log("[tape] upload skipped — no /mnt/config.json\n"); return; } char cbuf[4096] = {0}; fread(cbuf, 1, sizeof(cbuf) - 1, cf); fclose(cf); char handle[64] = {0}, actoken[1024] = {0}; parse_config_string(cbuf, "\"handle\"", handle, sizeof(handle)); parse_config_string(cbuf, "\"token\"", actoken, sizeof(actoken)); if (!handle[0] || !actoken[0]) { ac_log("[tape] upload skipped — no handle/token in config.json\n"); return; } // Extract slug: "/mnt/tapes/2026.04.11.12.34.56.789.mp4" → "2026.04.11.12.34.56.789" const char *base = strrchr(tape_path, '/'); base = base ? base + 1 : tape_path; char slug[128] = {0}; strncpy(slug, base, sizeof(slug) - 1); char *dot = strrchr(slug, '.'); if (dot) *dot = '\0'; ac_log("[tape] uploading %s as slug %s for @%s\n", tape_path, slug, handle); // Shell out to curl. Runs in background (&) so we don't block. // Step 1: GET presigned URL. Step 2: PUT mp4. Step 3: POST track-media. char cmd[4096]; snprintf(cmd, sizeof(cmd), "( " "URL=$(curl -fsSL -H 'Authorization: Bearer %s' " " 'https://aesthetic.computer/api/presigned-upload-url/mp4/%s/user' " " 2>/dev/null | jq -r '.uploadURL' 2>/dev/null); " "[ -z \"$URL\" ] && echo '[tape] no presigned url' && exit 1; " "curl -fsSL -X PUT -H 'Content-Type: video/mp4' -H 'x-amz-acl: public-read' " " --data-binary @'%s' \"$URL\" 2>/dev/null; " "curl -fsSL -X POST -H 'Authorization: Bearer %s' -H 'Content-Type: application/json' " " 'https://aesthetic.computer/api/track-tape' " " -d '{\"slug\":\"%s\",\"ext\":\"mp4\",\"metadata\":{\"totalDuration\":30,\"audioOnly\":false,\"device\":\"ac-native\"}}' " " >/dev/null 2>&1; " "echo '[tape] upload finished %s'; " ") >> /tmp/tape-upload.log 2>&1 &", actoken, slug, tape_path, actoken, slug, slug); system(cmd);}
// Shared tape start/stop — called by the PrintScreen key handler below and// by the JS system.tapeStart()/tapeStop() bindings (js-bindings.c) that the// 'cap' camera piece uses for hold-to-record. The pointers are latched in// main() once the recorder exists. `quiet` skips the TTS announce + audible// cues so camera clips don't open with "tape rolling" baked into the song's// audio track.static ACRecorder *g_tape_recorder = NULL;static ACAudio *g_tape_audio = NULL;static ACTts *g_tape_tts = NULL;
#ifdef HAVE_AVCODEC// First loaded DJ deck (decks persist across piece switches, so a song// started in `dj` keeps playing inside `cap`). Its live position feeds the// song timecode metadata baked into each clip for editor resync.static ACDeckDecoder *tape_active_deck(void) { if (!g_tape_audio) return NULL; for (int d = 0; d < AUDIO_MAX_DECKS; d++) { ACDeck *dk = &g_tape_audio->decks[d]; if (dk->active && dk->decoder && dk->decoder->loaded) return dk->decoder; } return NULL;}#endif
int ac_tape_start(int quiet) { if (!g_tape_recorder) return -1; if (recorder_is_recording(g_tape_recorder)) return 0; mkdir("/mnt/tapes", 0755); time_t now = time(NULL); struct tm *tm = gmtime(&now); // Milliseconds for slug uniqueness struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); int ms = (int)(ts.tv_nsec / 1000000); char rec_path[256]; snprintf(rec_path, sizeof(rec_path), "/mnt/tapes/%04d.%02d.%02d.%02d.%02d.%02d.%03d.mp4", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, ms);#ifdef HAVE_AVCODEC // Song timecode: if a deck is loaded, stamp the clip with the track + // where in it recording began (songEnd follows at the cut). { ACDeckDecoder *deck = tape_active_deck(); if (deck) recorder_set_song(g_tape_recorder, deck->title, deck->artist, deck->path, deck->position, deck->duration, deck->speed); }#endif if (recorder_start(g_tape_recorder, rec_path) != 0) return -1; // Wire up audio tap if (g_tape_audio) { g_tape_audio->rec_userdata = g_tape_recorder; g_tape_audio->rec_callback = rec_audio_tap; } // Latch the path + flag for the on-screen overlay strncpy(g_tape_current_path, rec_path, sizeof(g_tape_current_path) - 1); g_tape_current_path[sizeof(g_tape_current_path) - 1] = '\0'; g_tape_recording = 1; g_tape_start_sec = time(NULL); if (!quiet) { if (g_tape_tts) tts_speak(g_tape_tts, "tape rolling"); if (g_tape_audio) { audio_synth(g_tape_audio, WAVE_SINE, 660.0, 0.12, 0.2, 0.001, 0.10, 0.0); audio_synth(g_tape_audio, WAVE_SINE, 880.0, 0.12, 0.15, 0.03, 0.09, 0.0); } } return 0;}
int ac_tape_stop(int quiet) { if (!g_tape_recorder || !recorder_is_recording(g_tape_recorder)) return -1; // Remove audio tap before finalizing the file if (g_tape_audio) { g_tape_audio->rec_callback = NULL; g_tape_audio->rec_userdata = NULL; } // Snapshot the path before stop (recorder_stop may clear it) char saved_tape_path[256] = {0}; strncpy(saved_tape_path, g_tape_current_path, sizeof(saved_tape_path) - 1);#ifdef HAVE_AVCODEC // Song position at the cut — completes the songStart/songEnd pair. { ACDeckDecoder *deck = tape_active_deck(); if (deck) recorder_set_song_end(g_tape_recorder, deck->position); }#endif recorder_stop(g_tape_recorder); g_tape_recording = 0; g_tape_current_path[0] = '\0'; if (!quiet) { if (g_tape_tts) tts_speak(g_tape_tts, "tape stopped"); if (g_tape_audio) { audio_synth(g_tape_audio, WAVE_SINE, 880.0, 0.12, 0.2, 0.001, 0.10, 0.0); audio_synth(g_tape_audio, WAVE_SINE, 660.0, 0.12, 0.15, 0.03, 0.09, 0.0); } } // Kick off background upload if we captured a path if (saved_tape_path[0]) tape_upload_async(saved_tape_path); return 0;}
int main(int argc, char *argv[]) { struct timespec boot_start; clock_gettime(CLOCK_MONOTONIC, &boot_start);
// Mount filesystems if PID 1 (direct DRM boot) if (getpid() == 1) { mount_minimal_fs(); // Ensure PATH includes all standard binary directories setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1); // Keep curl/OpenSSL trust lookup stable in initramfs. setenv("SSL_CERT_FILE", "/etc/pki/tls/certs/ca-bundle.crt", 1); setenv("CURL_CA_BUNDLE", "/etc/pki/tls/certs/ca-bundle.crt", 1); setenv("SSL_CERT_DIR", "/etc/ssl/certs", 1); } else { // Under cage: filesystems already mounted by init script, // but still need USB log mount and PATH if (!getenv("PATH")) setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1); setenv("SSL_CERT_FILE", "/etc/pki/tls/certs/ca-bundle.crt", 1); setenv("CURL_CA_BUNDLE", "/etc/pki/tls/certs/ca-bundle.crt", 1); setenv("SSL_CERT_DIR", "/etc/ssl/certs", 1); // Log to USB directly (parent has /mnt mounted, we inherit it) // Use a separate file so we don't truncate parent's log logfile = fopen("/mnt/cage-child.log", "w"); if (!logfile) logfile = fopen("/tmp/ac-native-cage.log", "w"); if (logfile) { fprintf(logfile, "[ac-native] Running under cage (pid=%d)\n", getpid()); fflush(logfile); } }
signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGBUS, signal_handler); signal(SIGABRT, signal_handler); signal(SIGFPE, signal_handler); // Ignore SIGPIPE so writes to a closed socket (WebSocket, UDP peer, // session-server log uploads) return EPIPE instead of killing the // process. Without this, any server-side close during an in-flight // SSL_write kills ac-native with exit=141 (observed in crash logs). signal(SIGPIPE, SIG_IGN);
// 🔢 Device fingerprint: read DMI + cached slot synchronously so the // splash can render the badge before wifi comes up. Cheap (~3 file // reads + one sha256 hash on a tiny string). compute_device_fingerprint(); // Hand the fingerprint to lanserv so each machine gets a unique mDNS // name (<adjective>-<animal>.local) until a curated slot is assigned. lanserv_set_fingerprint(ac_device_fp);
// 📸 Bootpic snapshot: detached thread fires asap. Internally // waits 300ms then retries camera_open with backoff so it doesn't // hammer before the kernel enumerates /dev/video*. Runs in parallel // with the rest of boot — never blocks the splash, never blocks // input, never blocks audio. bootpic_capture_boot_async();
#ifdef USE_WAYLAND // Under Wayland: no DRM handoff signals needed (browser is sibling client) if (!getenv("WAYLAND_DISPLAY"))#endif { signal(SIGUSR1, sigusr_handler); signal(SIGUSR2, sigusr_handler); signal(SIGTERM, sigterm_handler); }
// Determine piece path (ignore kernel cmdline args passed to init) const char *piece_path = "/piece.mjs"; int headless = 0; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--headless") == 0) headless = 1; else if (argv[i][0] == '/' || argv[i][0] == '.') piece_path = argv[i]; }
ACDisplay *display = NULL; extern void *g_display; // expose to js-bindings for browser DRM handoff ACFramebuffer *screen = NULL; ACInput *input = NULL; int pixel_scale = 3; // Default: computed below to target ~300px wide#ifdef USE_WAYLAND ACWaylandDisplay *wayland_display = NULL; int is_wayland = 0;#endif
if (!headless) {#ifdef USE_WAYLAND // Prefer Wayland if running under cage compositor ac_log("[ac-native] WAYLAND_DISPLAY=%s XDG_RUNTIME_DIR=%s\n", getenv("WAYLAND_DISPLAY") ? getenv("WAYLAND_DISPLAY") : "(null)", getenv("XDG_RUNTIME_DIR") ? getenv("XDG_RUNTIME_DIR") : "(null)"); if (getenv("WAYLAND_DISPLAY")) { wayland_display = wayland_display_init(); if (wayland_display) { is_wayland = 1; g_wayland_display = wayland_display; // Create a minimal ACDisplay for code that needs width/height display = calloc(1, sizeof(ACDisplay)); display->width = wayland_display->width; display->height = wayland_display->height; g_display = display; fprintf(stderr, "[ac-native] Wayland display: %dx%d\n", display->width, display->height); } else { ac_log("[ac-native] Wayland init failed — exiting (cage will restart or fallback)\n"); return 1; // exit so cage exits and init falls through to DRM } } if (!is_wayland)#endif { display = drm_init(); g_display = display; } if (!display) { fprintf(stderr, "[ac-native] FATAL: No display\n"); // Dump device diagnostics for debugging fprintf(stderr, "[ac-native] /dev/dri contents:\n"); system("ls -la /dev/dri/ 2>&1 || echo ' /dev/dri does not exist'"); fprintf(stderr, "[ac-native] /dev/fb contents:\n"); system("ls -la /dev/fb* 2>&1 || echo ' no framebuffer devices'"); fprintf(stderr, "[ac-native] PCI GPU devices:\n"); system("cat /sys/bus/pci/devices/*/class 2>/dev/null | grep -c 0x03 || echo ' 0'"); system("for d in /sys/bus/pci/devices/*; do c=$(cat $d/class 2>/dev/null); [ \"${c#0x03}\" != \"$c\" ] && echo \" $(basename $d) $c $(cat $d/vendor $d/device 2>/dev/null)\"; done"); fprintf(stderr, "[ac-native] dmesg DRM/i915:\n"); system("dmesg 2>/dev/null | grep -i 'drm\\|i915\\|display\\|error' | tail -20"); // Write diagnostics to USB too system("cat /proc/cmdline >> /mnt/ac-init.log 2>/dev/null"); system("dmesg 2>/dev/null | grep -i 'drm\\|i915\\|display' >> /mnt/ac-init.log 2>/dev/null"); sleep(30); ac_poweroff(); return 1; }
// Target ~300px wide — just divide and let fb_copy_scaled handle the remainder { int target = display->width / 300; if (target < 1) target = 1; if (target > 16) target = 16; // Prefer clean divisors, but accept any scale pixel_scale = target; for (int delta = 0; delta <= 3; delta++) { int s = target + delta; if (s >= 1 && s <= 16 && display->width % s == 0 && display->height % s == 0) { pixel_scale = s; break; } s = target - delta; if (s >= 1 && display->width % s == 0 && display->height % s == 0) { pixel_scale = s; break; } } } ac_log("pixel_scale=%d (display %dx%d -> screen %dx%d)\n", pixel_scale, display->width, display->height, display->width / pixel_scale, display->height / pixel_scale); screen = fb_create(display->width / pixel_scale, display->height / pixel_scale); if (!screen) {#ifdef USE_WAYLAND if (is_wayland) wayland_display_destroy(wayland_display); else#endif drm_destroy(display); return 1; } } else { screen = fb_create(320, 200); }
// Init graphics + font ACGraph graph; graph_init(&graph, screen); if (display) graph_init_gpu(&graph, display); font_init();
// Expose graph + screen + pixel_scale to ac_poweroff() so the shutdown // animation ("bye @handle") can render before the kernel halts. g_shutdown_graph = &graph; g_shutdown_screen = screen; g_shutdown_pixel_scale = pixel_scale;
// Cursor overlay buffer — drawn separately so KidLisp effects don't smear it ACFramebuffer *cursor_fb = fb_create(screen->width, screen->height);
// Mount USB log early so boot animation can detect USB boot#ifdef USE_WAYLAND if (!is_wayland) // Under cage: parent already has USB mounted at /mnt#endif try_mount_log();
// Read boot visuals (handle + optional per-char colors) from /mnt/config.json load_boot_visual_config();
// Init audio + TTS early (needed for boot animation speech) ACAudio *audio = audio_init(); ACTts *tts = NULL; ACWifi *wifi = NULL; ACSecondaryDisplay *hdmi = NULL; char bl_path[128] = ""; int bl_max = 0;
#ifdef USE_WAYLAND if (is_wayland) { // ── Cage session ── ac_log("[ac-native] Wayland session\n");
// Input (Wayland path) if (!headless) input = input_init_wayland(wayland_display, display->width, display->height, pixel_scale);
// Boot animation with install prompt (same as DRM path) audio_boot_beep(audio); tts = tts_init(audio); int want_install = 0; if (!headless) { want_install = draw_startup_fade(&graph, screen, display, tts, audio, pixel_scale); if (!want_install) draw_boot_status(&graph, screen, display, "starting...", pixel_scale); } if (want_install) { int install_ok = auto_install_to_hd(&graph, screen, display, pixel_scale); if (display) { int should_reboot = draw_install_reboot_prompt(&graph, screen, display, input, tts, audio, install_ok, pixel_scale); if (should_reboot) { if (tts) tts_wait(tts); // let TTS finish before reboot // Use the shared ac_reboot() path — it tries reboot(2) // syscall first (reboot=efi,cold cmdline steers this // to UEFI ResetSystem which is the only consistently // working path on coreboot Chromebooks), falls back // to systemctl / busybox reboot -f, then finally // _exit(2) so init.sh sees "reboot requested" and // re-enters its own multi-tiered reboot loop // (reboot -f → sysrq-b → halt -f → sysrq-c panic). // The old path here did `_exit(0)` which triggered // init.sh's POWEROFF branch, so a failing reboot(2) // would hang waiting to power off instead of retrying. ac_reboot(); } } }
// WiFi is already running from parent — just connect to it if (!wifi_disabled) wifi = wifi_init(); } else#endif { // ── DRM boot: full boot sequence ── // Load persisted sample (overrides default seed if file exists) if (audio && audio_sample_load(audio, "/mnt/ac-sample.raw") > 0) { ac_log("[audio] loaded persisted sample (%d samples)\n", audio->sample_len); } audio_boot_beep(audio); tts = tts_init(audio); // Skip tts_precache here — defer to background after piece loads
// Startup fade animation (black → white, hides kernel text) ac_log("[ac-native] pre-fade: display=%p screen=%p w=%d h=%d\n", (void*)display, (void*)screen, display ? display->width : -1, display ? display->height : -1); int want_install = 0; if (!headless) { want_install = draw_startup_fade(&graph, screen, display, tts, audio, pixel_scale); if (!want_install) draw_boot_status(&graph, screen, display, "starting input...", pixel_scale); }
// Init input (DRM path) if (!headless) input = input_init(display->width, display->height, pixel_scale);
// Find backlight path { DIR *bldir = opendir("/sys/class/backlight"); if (bldir) { struct dirent *ent; while ((ent = readdir(bldir)) && !bl_path[0]) { if (ent->d_name[0] == '.') continue; char tmp[160]; snprintf(tmp, sizeof(tmp), "/sys/class/backlight/%s/max_brightness", ent->d_name); FILE *f = fopen(tmp, "r"); if (f) { if (fscanf(f, "%d", &bl_max) == 1 && bl_max > 0) snprintf(bl_path, sizeof(bl_path), "/sys/class/backlight/%s", ent->d_name); fclose(f); } } closedir(bldir); } }
// Install kernel to internal drive (only if user pressed W during boot) if (want_install) { int install_ok = auto_install_to_hd(&graph, screen, display, pixel_scale); int should_reboot = 1; if (!headless && display) should_reboot = draw_install_reboot_prompt(&graph, screen, display, input, tts, audio, install_ok, pixel_scale); if (install_ok) should_reboot = 1; if (should_reboot) { if (tts) { tts_speak(tts, "rebooting"); tts_wait(tts); } audio_shutdown_sound(audio); usleep(500000); sync(); ac_reboot(); while (running) sleep(1); } }
// Init WiFi if (!wifi_disabled) { if (!headless && display) draw_boot_status(&graph, screen, display, "starting wifi...", pixel_scale); wifi = wifi_init(); // Don't autoconnect from C — notepat.mjs handles scanning // at 30s intervals (reduced from 2s to avoid 65ms frame drops). } else { if (!headless && display) draw_boot_status(&graph, screen, display, "wifi disabled", pixel_scale); ac_log("[ac-native] WiFi disabled by config\n"); }
// Init secondary HDMI display (if connected) if (display && !display->is_fbdev) { hdmi = drm_init_secondary(display); if (hdmi) ac_log("[ac-native] HDMI secondary: %dx%d\n", hdmi->width, hdmi->height); } }
// Init JS ACRuntime *rt = js_init(&graph, input, audio, wifi, tts); if (!rt) { fprintf(stderr, "[ac-native] FATAL: Cannot init JS\n"); wifi_destroy(wifi); audio_destroy(audio); fb_destroy(screen); if (display) drm_destroy(display); return 1; } rt->hdmi = hdmi;
// Read user config from EFI partition (/mnt/config.json) { FILE *cfg = fopen("/mnt/config.json", "r"); if (cfg) { char buf[32768] = {0}; size_t n = fread(buf, 1, sizeof(buf) - 1, cfg); buf[n] = '\0'; fclose(cfg);
// Skip identity block marker line if present char *json = buf; if (strncmp(buf, "AC_IDENTITY_BLOCK_V1", 20) == 0) { char *nl = strchr(buf, '\n'); if (nl) json = nl + 1; }
parse_config_string(json, "\"handle\"", rt->handle, sizeof(rt->handle)); parse_config_string(json, "\"piece\"", rt->piece, sizeof(rt->piece)); ac_log("[ac-native] Config: handle=%s piece=%s\n", rt->handle[0] ? rt->handle : "(none)", rt->piece[0] ? rt->piece : "(none)");
// Check voice config (voice: "off" disables keystroke TTS) char voice_cfg[16] = {0}; if (parse_config_string(json, "\"voice\"", voice_cfg, sizeof(voice_cfg))) { voice_off = (strcmp(voice_cfg, "off") == 0); ac_log("[config] voice: %s\n", voice_cfg); }
// (Tokens are baked early in load_boot_visual_config() so the // boot-fade badges can render — see notes there.) } }
// Check for previous crash (written by signal handler) { FILE *cf = fopen("/mnt/crash.json", "r"); if (cf) { char cbuf[512] = {0}; fread(cbuf, 1, sizeof(cbuf) - 1, cf); fclose(cf); char sig[32] = {0}; parse_config_string(cbuf, "\"signal\"", sig, sizeof(sig)); if (sig[0]) { rt->crash_active = 1; rt->crash_frame = 0; snprintf(rt->crash_msg, sizeof(rt->crash_msg), "previous crash: %s", sig); ac_log("[ac-native] Previous crash detected: %s\n", sig); } // Remove crash file so we don't re-show it remove("/mnt/crash.json"); } }
// Override piece_path from config.json boot piece (pieces bundled at /pieces/) // Resolve aliases: "claude" and "cc" → terminal (with param "claude") if (strcmp(rt->piece, "claude") == 0 || strcmp(rt->piece, "cc") == 0) { strcpy(rt->piece, "terminal"); // Set initial jump params so terminal.mjs gets "claude" as param strcpy(rt->jump_params[0], "claude"); rt->jump_param_count = 1; } char piece_path_buf[256]; if (rt->piece[0]) { snprintf(piece_path_buf, sizeof(piece_path_buf), "/pieces/%s.mjs", rt->piece); if (access(piece_path_buf, R_OK) == 0) { piece_path = piece_path_buf; ac_log("[ac-native] Boot piece from config: %s\n", piece_path); } else { ac_log("[ac-native] Config piece '%s' not found, falling back to %s\n", rt->piece, piece_path); } }
// Load piece if (js_load_piece(rt, piece_path) < 0) { fprintf(stderr, "[ac-native] FATAL: Cannot load %s\n", piece_path); if (!headless) { graph_wipe(&graph, (ACColor){200, 0, 0, 255}); graph_ink(&graph, (ACColor){255, 255, 255, 255}); char msg[256]; snprintf(msg, sizeof(msg), "Cannot load: %s", piece_path); font_draw(&graph, msg, 20, 20, 2); ac_display_present(display, screen, pixel_scale); sleep(10); } js_destroy(rt); audio_destroy(audio); fb_destroy(screen); if (display) drm_destroy(display); if (logfile) { fclose(logfile); logfile = NULL; } sync(); ac_poweroff(); return 1; }
struct timespec boot_end; clock_gettime(CLOCK_MONOTONIC, &boot_end); double boot_ms = (boot_end.tv_sec - boot_start.tv_sec) * 1000.0 + (boot_end.tv_nsec - boot_start.tv_nsec) / 1000000.0; ac_log("[ac-native] Booted in %.1fms\n", boot_ms);
// Log audio and backlight status ac_log("[ac-native] Audio: %s\n", audio && audio->pcm ? "OK" : "NO PCM"); ac_log("[ac-native] Backlight: %s (max=%d)\n", bl_path[0] ? bl_path : "none", bl_max); ac_log("[ac-native] Input devices: %d\n", input ? input->count : 0); // Log each input device name for debugging keyboard issues if (input) { for (int i = 0; i < input->count; i++) { char dname[256] = "?"; ioctl(input->fds[i], EVIOCGNAME(sizeof(dname)), dname); ac_log("[input] dev%d: %s\n", i, dname); } } ac_log("[ac-native] NuPhy: has_analog=%d hidraw=%d\n", input ? input->has_analog : -1, input ? input->hidraw_count : -1); ac_log("[ac-native] JS: boot=%d paint=%d act=%d sim=%d\n", JS_IsFunction(rt->ctx, rt->boot_fn), JS_IsFunction(rt->ctx, rt->paint_fn), JS_IsFunction(rt->ctx, rt->act_fn), JS_IsFunction(rt->ctx, rt->sim_fn)); if (logfile) { fflush(logfile); }
// Prewarm audio engine so first keypress has zero latency audio_prewarm(audio);
// Drain any queued input events from boot animation (prevents first-key stick) input_poll(input); input->event_count = 0;
// Call boot() immediately — melody plays over the already-running piece js_call_boot(rt);
// Precache TTS in background — user won't type for several seconds pthread_t tts_precache_thread; int tts_precache_spawned = 0; if (tts) { tts_precache_spawned = (pthread_create(&tts_precache_thread, NULL, (void *(*)(void *))tts_precache, tts) == 0); }
// Let TTS finish and play ready melody (non-blocking to piece) if (tts) { tts_wait(tts); usleep(300000); // Let TTS ring buffer drain } audio_ready_melody(audio);
// Join TTS precache thread before entering main loop if (tts_precache_spawned) pthread_join(tts_precache_thread, NULL);
// Init performance logger perf_init();
// ── Graceful DRM → cage transition ── // After boot completes in DRM mode, try to launch cage compositor // and re-exec ac-native under it. This gives us a Wayland session // for browser popups (Firefox, OAuth) while keeping fast DRM boot.#ifdef USE_WAYLAND if (!is_wayland && !headless && getpid() == 1 && access("/bin/cage", X_OK) == 0 && access("/dev/dri/card0", F_OK) == 0) { ac_log("[cage-transition] Starting cage compositor...\n");
// Close audio so cage child can open ALSA audio_destroy(audio); audio = NULL;
// Release DRM master so cage can take it drm_release_master(display); ac_log("[cage-transition] Released DRM master\n");
pid_t cage_pid = fork(); if (cage_pid == 0) { // === CHILD: start seatd + cage + ac-native === setenv("HOME", "/tmp", 1); setenv("XDG_RUNTIME_DIR", "/tmp/xdg", 1); setenv("WLR_RENDERER", "pixman", 1); setenv("WLR_BACKENDS", "drm", 1); setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); setenv("WLR_LIBINPUT_NO_DEVICES", "1", 1); mkdir("/tmp/xdg", 0700);
// Log /dev/input state for debugging (write to USB) { int dfd = open("/mnt/cage-diag.log", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (dfd >= 0) { char dbuf[1024]; int dlen = 0; dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "pid=%d\n", getpid()); DIR *d = opendir("/dev/input"); if (d) { struct dirent *e; dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "dev-input:"); while ((e = readdir(d))) { if (e->d_name[0] != '.') dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, " %s", e->d_name); } dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "\n"); closedir(d); } else { dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "dev-input: MISSING errno=%d\n", errno); } // Also list /dev/dri d = opendir("/dev/dri"); if (d) { struct dirent *e; dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "dev-dri:"); while ((e = readdir(d))) { if (e->d_name[0] != '.') dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, " %s", e->d_name); } dlen += snprintf(dbuf + dlen, sizeof(dbuf) - dlen, "\n"); closedir(d); } write(dfd, dbuf, dlen); fsync(dfd); close(dfd); } }
// Ensure /run exists (may be missing if mount_minimal_fs re-mounted rootfs) mkdir("/run", 0755); mount("tmpfs", "/run", "tmpfs", 0, NULL);
// Start seatd system("seatd -g root > /tmp/seatd.log 2>&1 &"); for (int si = 0; si < 30; si++) { usleep(100000); if (access("/run/seatd.sock", F_OK) == 0) break; }
if (access("/run/seatd.sock", F_OK) != 0) { FILE *ef = fopen("/tmp/cage-stderr.log", "w"); if (ef) { fprintf(ef, "seatd failed to start after 3s\n"); fprintf(ef, "run-dir-exists=%d run-writable=%d\n", access("/run", F_OK) == 0, access("/run", W_OK) == 0); // Dump seatd log FILE *sl = fopen("/tmp/seatd.log", "r"); if (sl) { char sbuf[256]; fprintf(ef, "--- seatd.log ---\n"); while (fgets(sbuf, sizeof(sbuf), sl)) fputs(sbuf, ef); fclose(sl); } fclose(ef); } _exit(1); }
// Redirect stderr to file so parent can read cage errors FILE *cage_log = fopen("/tmp/cage-stderr.log", "w"); if (cage_log) { dup2(fileno(cage_log), STDERR_FILENO); fclose(cage_log); } fprintf(stderr, "[cage-transition] seatd ok, launching cage...\n");
execlp("cage", "cage", "-s", "--", "/ac-native", "/piece.mjs", NULL); fprintf(stderr, "[cage-transition] exec cage failed: %s\n", strerror(errno)); _exit(127); }
if (cage_pid > 0) { // === PARENT: wait for cage session to end === ac_log("[cage-transition] cage pid=%d, waiting...\n", cage_pid); int status = 0; waitpid(cage_pid, &status, 0); int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; ac_log("[cage-transition] cage exited: %d\n", rc);
// Copy cage stderr to USB log FILE *cage_err = fopen("/tmp/cage-stderr.log", "r"); if (cage_err) { char buf[256]; while (fgets(buf, sizeof(buf), cage_err)) ac_log("[cage-err] %s", buf); fclose(cage_err); } // Copy child ac-native log to USB log FILE *cage_child = fopen("/tmp/ac-native-cage.log", "r"); if (cage_child) { char buf[256]; while (fgets(buf, sizeof(buf), cage_child)) ac_log("[cage-child] %s", buf); fclose(cage_child); }
// Cleanup seatd system("killall seatd 2>/dev/null"); } else { ac_log("[cage-transition] fork failed: %s\n", strerror(errno)); }
// Check if cage child requested reboot/poweroff if (reboot_requested) { ac_log("[cage-transition] Reboot requested by cage child\n"); ac_log_flush(); ac_reboot(); } if (poweroff_requested) { ac_log("[cage-transition] Poweroff requested by cage child\n"); ac_log_flush(); ac_poweroff(); }
// Reclaim DRM and continue in DRM mode (fallback) drm_acquire_master(display); audio = audio_init(); ac_log("[cage-transition] Reclaimed DRM, continuing in DRM mode\n"); }#endif
// Video recorder (F9 to toggle) — declared before headless branch so cleanup is in scope ACRecorder *recorder = NULL;
if (headless) { for (int i = 0; i < 10 && running; i++) { js_call_sim(rt); js_call_paint(rt); } } else {#ifdef HAVE_AVCODEC if (screen && audio) { recorder = recorder_create(screen->width, screen->height, 60, audio->actual_rate ? audio->actual_rate : 192000); if (recorder) { ac_log("[ac-native] recorder ready (%dx%d)\n", screen->width, screen->height); // Expose to JS via sound.tape.* bindings if (rt) rt->recorder = recorder; // Latch pointers for the shared ac_tape_start/stop helpers g_tape_recorder = recorder; g_tape_audio = audio; g_tape_tts = tts; } }#endif
// Main loop struct timespec frame_time; clock_gettime(CLOCK_MONOTONIC, &frame_time);
int main_frame = 0; while (running) {#ifdef USE_WAYLAND // Under Wayland: input_poll handles all Wayland event dispatch // (reading from socket + firing listeners in correct order) if (is_wayland) { // nothing — input_poll does full dispatch } else#endif { // DRM handoff: xdg-open sends SIGUSR1 to release, SIGUSR2 to reclaim if (drm_handoff_release && display) { drm_handoff_release = 0;
char browser_url[2048] = ""; FILE *uf = fopen("/tmp/.browser-url", "r"); if (uf) { if (fgets(browser_url, sizeof(browser_url), uf)) browser_url[strcspn(browser_url, "\n")] = 0; fclose(uf); unlink("/tmp/.browser-url"); }
if (browser_url[0]) { ac_log("[browser] URL: %s", browser_url); drm_release_master(display); ac_log("[browser] Released DRM master");
// Fork a child for the entire browser session. // Child sets env, starts seatd+cage+firefox, then exits. // Parent waits, then reclaims DRM. If child crashes, parent survives. pid_t bpid = fork(); if (bpid == 0) { // === CHILD PROCESS === mkdir("/tmp/xdg-browser", 0700); mkdir("/tmp/.mozilla", 0755); mkdir("/run", 0755);
setenv("HOME", "/tmp", 1); setenv("XDG_RUNTIME_DIR", "/tmp/xdg-browser", 1); setenv("WLR_BACKENDS", "drm", 1); setenv("WLR_RENDERER", "pixman", 1); setenv("LD_LIBRARY_PATH", "/lib64:/opt/firefox", 1); setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); setenv("MOZ_ENABLE_WAYLAND", "1", 1); setenv("GDK_BACKEND", "wayland", 1); setenv("MOZ_APP_LAUNCHER", "/opt/firefox/firefox", 1); setenv("GRE_HOME", "/opt/firefox", 1); setenv("DBUS_SESSION_BUS_ADDRESS", "disabled:", 1); setenv("MOZ_DBUS_REMOTE", "0", 1); setenv("MOZ_DISABLE_CONTENT_SANDBOX", "1", 1);
// Ensure /etc/group exists for seatd { FILE *grp = fopen("/etc/group", "a"); if (grp) { fseek(grp, 0, SEEK_END); if (ftell(grp) == 0) fprintf(grp, "root:x:0:\n"); fclose(grp); } }
// Start seatd, wait for socket system("seatd -g root > /tmp/seatd.log 2>&1 &"); for (int si = 0; si < 15; si++) { usleep(200000); if (access("/run/seatd.sock", F_OK) == 0) break; }
// Run cage+firefox (blocks until browser exits) char cmd[4096]; snprintf(cmd, sizeof(cmd), "cd /opt/firefox && cage -s -- ./firefox --kiosk --no-remote " "--new-instance '%s' > /tmp/cage-out.log 2>&1", browser_url); int rc = system(cmd);
// Cleanup system("killall seatd 2>/dev/null"); // Copy logs to USB FILE *lf = fopen("/tmp/cage-out.log", "r"); if (lf) { FILE *mf = fopen("/mnt/cage.log", "w"); if (mf) { char buf[512]; while (fgets(buf, sizeof(buf), lf)) fputs(buf, mf); fclose(mf); sync(); } fclose(lf); } _exit(rc); }
// === PARENT PROCESS === if (bpid > 0) { int bstatus = 0; ac_log("[browser] child pid=%d, waiting...", bpid); waitpid(bpid, &bstatus, 0); if (WIFEXITED(bstatus)) ac_log("[browser] child exited: %d", WEXITSTATUS(bstatus)); else if (WIFSIGNALED(bstatus)) ac_log("[browser] child killed by signal %d", WTERMSIG(bstatus)); } else { ac_log("[browser] fork failed: %s", strerror(errno)); }
// Always reclaim DRM, even if child crashed drm_acquire_master(display); ac_log("[browser] Reclaimed DRM master"); } else { ac_log("[browser] SIGUSR1 but no URL in /tmp/.browser-url"); } } }
input_poll(input); main_frame++; // Check for device token API response (from wifi-connect fetch) if (main_frame % 300 == 0) { FILE *rf = fopen("/tmp/claude-api-resp.json", "r"); if (rf) { char rbuf[2048] = {0}; fread(rbuf, 1, sizeof(rbuf) - 1, rf); fclose(rf); unlink("/tmp/claude-api-resp.json"); // Extract Claude token: {"token":"sk-ant-...","githubPat":"ghp_..."} const char *tk = strstr(rbuf, "\"token\""); if (tk) { const char *tv = strchr(tk + 7, ':'); if (tv) { tv++; while (*tv == ' ' || *tv == '"') tv++; const char *te = strchr(tv, '"'); if (te && te > tv && (te - tv) > 10) { FILE *tf = fopen("/claude-token", "w"); if (tf) { fwrite(tv, 1, te - tv, tf); fclose(tf); } ac_log("[tokens] claude token from API (%d bytes)\n", (int)(te - tv)); } } } // Extract GitHub PAT const char *gk = strstr(rbuf, "\"githubPat\""); if (gk) { const char *gv = strchr(gk + 11, ':'); if (gv) { gv++; while (*gv == ' ' || *gv == '"') gv++; const char *ge = strchr(gv, '"'); if (ge && ge > gv && (ge - gv) > 5) { FILE *gf = fopen("/github-pat", "w"); if (gf) { fwrite(gv, 1, ge - gv, gf); fclose(gf); } ac_log("[tokens] github pat from API (%d bytes)\n", (int)(ge - gv)); } } } } // Device-id response — written by the wifi-connect curl above. // Extract .slot ("ac3" etc.), persist to /mnt/.ac-device-slot // for the next boot's splash. Updates the in-memory slot too // so subsequent renders within this boot can show the badge. FILE *df = fopen("/tmp/ac-device-resp.json", "r"); if (df) { char dbuf[1024] = {0}; fread(dbuf, 1, sizeof(dbuf) - 1, df); fclose(df); unlink("/tmp/ac-device-resp.json"); const char *sk = strstr(dbuf, "\"slot\""); if (sk) { const char *sv = strchr(sk + 6, ':'); if (sv) { sv++; while (*sv == ' ' || *sv == '"') sv++; const char *se = strchr(sv, '"'); if (se && se > sv && (se - sv) < (int)sizeof(ac_device_slot)) { int len = (int)(se - sv); memcpy(ac_device_slot, sv, len); ac_device_slot[len] = '\0'; FILE *out = fopen("/mnt/.ac-device-slot", "w"); if (out) { fwrite(ac_device_slot, 1, len, out); fclose(out); ac_log("[ac-device] slot=%s persisted\n", ac_device_slot); } } } } } // Mood response — written by the wifi-connect curl above. // Parse the .mood string out of {"_id":"...","mood":"...",...} // and persist to /mnt/last-mood for the NEXT boot's subtitle. FILE *mf = fopen("/tmp/mood-resp.json", "r"); if (mf) { char mbuf[2048] = {0}; fread(mbuf, 1, sizeof(mbuf) - 1, mf); fclose(mf); unlink("/tmp/mood-resp.json"); const char *mk = strstr(mbuf, "\"mood\""); if (mk) { const char *mv = strchr(mk + 6, ':'); if (mv) { mv++; while (*mv == ' ' || *mv == '"') mv++; // Find the closing quote, skipping over JSON- // escaped quotes (\") inside the mood string. const char *me = mv; while (*me) { if (*me == '\\' && me[1]) { me += 2; continue; } if (*me == '"') break; me++; } if (me > mv && (me - mv) > 0 && (me - mv) < 240) { FILE *lm = fopen("/mnt/last-mood", "w"); if (lm) { fwrite(mv, 1, me - mv, lm); fclose(lm); ac_log("[mood] persisted (%d bytes) for next boot\n", (int)(me - mv)); } } } } } } // Copy tmpfs debug logs to USB periodically (every 5 sec) if (logfile && main_frame % 300 == 0) { FILE *xf = fopen("/tmp/xdg-open.log", "r"); if (xf) { char xbuf[512]; while (fgets(xbuf, sizeof(xbuf), xf)) ac_log("[xdg] %s", xbuf); fclose(xf); unlink("/tmp/xdg-open.log"); // only report once } FILE *ff = fopen("/tmp/firefox-debug.log", "r"); if (ff) { char fbuf[512]; while (fgets(fbuf, sizeof(fbuf), ff)) ac_log("[firefox] %s", fbuf); fclose(ff); unlink("/tmp/firefox-debug.log"); } } // Log input state periodically (every 5 sec) if (logfile && main_frame % 300 == 1) { int analog_active = 0; for (int k = 0; k < MAX_ANALOG_KEYS; k++) if (input->analog_keys[k].active) analog_active++; ac_log("[input] frame=%d events=%d has_analog=%d hidraw=%d analog_active=%d evdev=%d\n", main_frame, input->event_count, input->has_analog, input->hidraw_count, analog_active, input->count); } // Log key events to USB + instant TTS for key presses for (int i = 0; i < input->event_count; i++) { if (input->events[i].type == AC_EVENT_KEYBOARD_DOWN) { if (logfile) { ac_log("[key] DOWN code=%d name=%s pressure=%.3f\n", input->events[i].key_code, input->events[i].key_name, input->events[i].pressure); } // Instant TTS: play cached sound immediately (bypasses JS frame delay) // Only when running prompt.mjs (other pieces should not voice keystrokes) if (tts && !voice_off && strcmp(rt->piece, "prompt") == 0) { const char *kn = input->events[i].key_name; if (kn && kn[0] && kn[1] == 0) { // Single printable character tts_speak_cached(tts, kn); } else if (kn && strcmp(kn, "space") == 0) { tts_speak_cached(tts, "space"); } else if (kn && strcmp(kn, "backspace") == 0) { tts_speak_cached(tts, "back"); } else if (kn && (strcmp(kn, "enter") == 0 || strcmp(kn, "return") == 0)) { tts_speak_cached(tts, "enter"); } else if (kn && strcmp(kn, "escape") == 0) { tts_speak_cached(tts, "clear"); } else if (kn && strcmp(kn, "tab") == 0) { tts_speak_cached(tts, "tab"); } } } else if (input->events[i].type == AC_EVENT_KEYBOARD_UP && logfile) { ac_log("[key] UP code=%d name=%s pressure=%.3f\n", input->events[i].key_code, input->events[i].key_name, input->events[i].pressure); } } // Hardware keys static int ctrl_held = 0; int power_pressed = 0; int scale_change = 0; // -1 = decrease density (bigger pixels), +1 = increase // Global escape → prompt fallback. If the current piece doesn't // jump() or otherwise consume an escape key-down this frame, we // transparently route the user back to the prompt after act() // returns. Notepat has its own triple-escape-to-exit behavior and // the prompt itself uses escape to clear input, so both are // exempt from the fallback. int escape_pressed_this_frame = 0; for (int i = 0; i < input->event_count; i++) { if (input->events[i].type == AC_EVENT_KEYBOARD_DOWN && input->events[i].key_code == KEY_ESC) { if (strcmp(rt->piece, "notepat") != 0 && strcmp(rt->piece, "prompt") != 0) { escape_pressed_this_frame = 1; } break; } } for (int i = 0; i < input->event_count; i++) { // Track ctrl modifier if (input->events[i].key_code == KEY_LEFTCTRL || input->events[i].key_code == KEY_RIGHTCTRL) { ctrl_held = (input->events[i].type == AC_EVENT_KEYBOARD_DOWN) ? 1 : 0; } if (input->events[i].type == AC_EVENT_KEYBOARD_DOWN) { // Ctrl+= (or Ctrl++) → bigger pixels (increase scale number) // Ctrl+- → smaller pixels (decrease scale number) if (ctrl_held && (input->events[i].key_code == KEY_EQUAL || input->events[i].key_code == KEY_KPPLUS)) { scale_change = -1; // bigger pixels (lower res) input->events[i].type = 0; // suppress from JS } else if (ctrl_held && (input->events[i].key_code == KEY_MINUS || input->events[i].key_code == KEY_KPMINUS)) { scale_change = 1; // smaller pixels (higher res) input->events[i].type = 0; // suppress from JS } else if (ctrl_held && input->events[i].key_code == KEY_0) { scale_change = 99; // reset to default (3) input->events[i].type = 0; // suppress from JS } // Volume: KEY_VOLUMEUP/DOWN/MUTE or F1/F2/F3 as fallback else if (strcmp(input->events[i].key_name, "audiovolumeup") == 0 || strcmp(input->events[i].key_name, "f3") == 0) { audio_volume_adjust(audio, 1); audio_synth(audio, WAVE_SINE, 1200.0, 0.04, 0.15, 0.001, 0.03, 0.0); } else if (strcmp(input->events[i].key_name, "audiovolumedown") == 0 || strcmp(input->events[i].key_name, "f2") == 0) { audio_volume_adjust(audio, -1); audio_synth(audio, WAVE_SINE, 800.0, 0.04, 0.15, 0.001, 0.03, 0.0); } else if (strcmp(input->events[i].key_name, "audiomute") == 0 || strcmp(input->events[i].key_name, "f1") == 0) { audio_volume_adjust(audio, 0); audio_synth(audio, WAVE_SINE, 440.0, 0.08, 0.15, 0.001, 0.07, 0.0); // Brightness: KEY_BRIGHTNESS or F5/F6 as fallback } else if ((strcmp(input->events[i].key_name, "brightnessup") == 0 || strcmp(input->events[i].key_name, "brightnessdown") == 0 || strcmp(input->events[i].key_name, "f6") == 0 || strcmp(input->events[i].key_name, "f5") == 0) && bl_path[0]) { int up = (strcmp(input->events[i].key_name, "brightnessup") == 0 || strcmp(input->events[i].key_name, "f6") == 0); char tmp[160]; snprintf(tmp, sizeof(tmp), "%s/brightness", bl_path); FILE *f = fopen(tmp, "r"); int cur = bl_max / 2; if (f) { fscanf(f, "%d", &cur); fclose(f); } int step = bl_max / 20; // 5% steps if (step < 1) step = 1; cur += up ? step : -step; if (cur < 1) cur = 1; // never fully off if (cur > bl_max) cur = bl_max; f = fopen(tmp, "w"); if (f) { fprintf(f, "%d", cur); fclose(f); } // Click sound: higher = brighter, lower = dimmer audio_synth(audio, WAVE_SINE, up ? 1000.0 : 600.0, 0.04, 0.12, 0.001, 0.03, 0.0); } // Tape recording: PrintScreen (or Insert/Pause as fallbacks // on laptops where PrtSc doesn't fire standalone) toggles // MP4 tape recording. Saves to /mnt/tapes/<slug>.mp4 where // slug follows the same YYYY.MM.DD.HH.MM.SS.mmm format that // web AC uses for tapes. On stop, auto-uploads to the // cloud via a background curl shell-out. else if ((strcmp(input->events[i].key_name, "printscreen") == 0 || strcmp(input->events[i].key_name, "insert") == 0 || strcmp(input->events[i].key_name, "pause") == 0) && recorder) { if (recorder_is_recording(recorder)) { ac_tape_stop(0); } else { ac_tape_start(0); } } else if (strcmp(input->events[i].key_name, "power") == 0 || input->events[i].key_code == KEY_POWER) power_pressed = 1; } }
// Pixel density scale change if (scale_change && display) { int new_scale = pixel_scale; if (scale_change == 99) { new_scale = 3; // reset to default } else if (scale_change == 1) { // Increase density (smaller pixels): 12→10→8→6→4→3→2→1 if (pixel_scale > 6) new_scale = pixel_scale - 2; else if (pixel_scale > 3) new_scale = pixel_scale - 2; else if (pixel_scale > 1) new_scale = pixel_scale - 1; } else if (scale_change == -1) { // Decrease density (bigger pixels): 1→2→3→4→6→8→10→12 if (pixel_scale < 3) new_scale = pixel_scale + 1; else if (pixel_scale < 6) new_scale = pixel_scale + 2; else new_scale = pixel_scale + 2; } if (new_scale != pixel_scale && new_scale >= 1 && new_scale <= 12) { pixel_scale = new_scale; // Recreate framebuffer at new resolution ACFramebuffer *new_screen = fb_create(display->width / pixel_scale, display->height / pixel_scale); if (new_screen) { fb_destroy(screen); screen = new_screen; graph_init(&graph, screen); if (display) graph_init_gpu(&graph, display); // Keep shutdown-animation globals in sync with new screen + scale g_shutdown_screen = screen; g_shutdown_pixel_scale = pixel_scale; input->scale = pixel_scale; // Update JS runtime's graph reference (holds framebuffer) if (rt) { rt->graph = &graph; } // Recreate cursor overlay at new resolution if (cursor_fb) fb_destroy(cursor_fb); cursor_fb = fb_create(screen->width, screen->height); ac_log("[scale] pixel_scale=%d resolution=%dx%d", pixel_scale, screen->width, screen->height); audio_synth(audio, WAVE_SINE, 440.0 + (6 - pixel_scale) * 150.0, 0.06, 0.15, 0.002, 0.05, 0.0); } } }
if (power_pressed || poweroff_requested) { poweroff_requested = 0; // consume the flag // Say bye IMMEDIATELY — TTS + shutdown chime lead-in the // animation so the farewell audio has a beat of head-room // before the red/white strobe hits. { const char *at = strchr(boot_title, '@'); char bye_speech[96]; if (at) snprintf(bye_speech, sizeof(bye_speech), "bye %s", at + 1); else snprintf(bye_speech, sizeof(bye_speech), "bye"); if (tts) tts_speak(tts, bye_speech); audio_shutdown_sound(audio); }
// Shutdown visual — chaotic red/white strobe with bye title. // Defined near draw_boot_status so every poweroff path (main // loop, ac_poweroff() from JS, ac_poweroff() on crash/OTA) // shares the same farewell. draw_shutdown_anim();
running = 0; break; }
struct timespec _pf_start, _pf_act0, _pf_act1; clock_gettime(CLOCK_MONOTONIC, &_pf_start); clock_gettime(CLOCK_MONOTONIC, &_pf_act0); js_call_act(rt); clock_gettime(CLOCK_MONOTONIC, &_pf_act1);
// Global escape fallback: if the piece didn't consume the escape // key-down (i.e. didn't call system.jump() or otherwise stop the // event), send the user back to the prompt automatically. if (escape_pressed_this_frame && !rt->jump_requested) { ac_log("[ac-native] escape fallback: %s → prompt\n", rt->piece); strncpy(rt->jump_target, "prompt", sizeof(rt->jump_target) - 1); rt->jump_target[sizeof(rt->jump_target) - 1] = 0; rt->jump_requested = 1; }
// Handle piece jump requests from system.jump() if (rt->jump_requested) { rt->jump_requested = 0;
// Parse colon-separated params: "chat:clock" → name="chat", params=["clock"] rt->jump_param_count = 0; { char *colon = strchr(rt->jump_target, ':'); if (colon) { *colon = 0; // terminate piece name at first colon char *rest = colon + 1; while (rest && *rest && rt->jump_param_count < 8) { char *next = strchr(rest, ':'); if (next) *next = 0; strncpy(rt->jump_params[rt->jump_param_count], rest, 63); rt->jump_params[rt->jump_param_count][63] = 0; rt->jump_param_count++; rest = next ? next + 1 : NULL; } } }
ac_log("[ac-native] Jumping to piece: %s (%d params)\n", rt->jump_target, rt->jump_param_count);
// Call leave() on current piece js_call_leave(rt);
// Free old lifecycle functions JS_FreeValue(rt->ctx, rt->boot_fn); rt->boot_fn = JS_UNDEFINED; JS_FreeValue(rt->ctx, rt->paint_fn); rt->paint_fn = JS_UNDEFINED; JS_FreeValue(rt->ctx, rt->act_fn); rt->act_fn = JS_UNDEFINED; JS_FreeValue(rt->ctx, rt->sim_fn); rt->sim_fn = JS_UNDEFINED; JS_FreeValue(rt->ctx, rt->leave_fn); rt->leave_fn = JS_UNDEFINED; JS_FreeValue(rt->ctx, rt->beat_fn); rt->beat_fn = JS_UNDEFINED;
// Clear globalThis lifecycle refs so new piece starts clean JSValue global = JS_GetGlobalObject(rt->ctx); const char *lc_names[] = {"boot","paint","act","sim","leave","beat","configureAutopat",NULL}; for (int i = 0; lc_names[i]; i++) { JSAtom a = JS_NewAtom(rt->ctx, lc_names[i]); JS_DeleteProperty(rt->ctx, global, a, 0); JS_FreeAtom(rt->ctx, a); } JS_FreeValue(rt->ctx, global);
// Reset counters and crash state rt->paint_count = 0; rt->sim_count = 0; rt->crash_active = 0; rt->crash_count = 0; rt->crash_frame = 0; rt->crash_msg[0] = 0;
// NOTE: "claude" and "cc" aliases removed — claude.mjs handles auth curtain // before jumping to terminal:claude itself.
// Check for .lisp piece — hand off to SBCL { char lisp_path[256]; // Strip .lisp suffix if present char lisp_name[128]; strncpy(lisp_name, rt->jump_target, sizeof(lisp_name) - 1); lisp_name[sizeof(lisp_name) - 1] = 0; char *dot = strstr(lisp_name, ".lisp"); if (dot) *dot = 0; snprintf(lisp_path, sizeof(lisp_path), "/pieces/%s.lisp", lisp_name); if (access(lisp_path, F_OK) == 0 && access("/ac-swank", X_OK) == 0) { ac_log("[ac-native] Launching CL piece: %s\n", lisp_name); // Clean up before exec if (logfile) { fflush(logfile); fsync(fileno(logfile)); } sync(); execl("/ac-swank", "ac-swank", "--piece", lisp_name, NULL); // execl failed — fall through to JS path ac_log("[ac-native] execl /ac-swank failed: %s\n", strerror(errno)); } }
// Construct piece path: /pieces/<name>.mjs char jump_path[256]; snprintf(jump_path, sizeof(jump_path), "/pieces/%s.mjs", rt->jump_target);
// Load new piece if (js_load_piece(rt, jump_path) < 0) { ac_log("[ac-native] Failed to load %s, falling back to /piece.mjs\n", jump_path); // Fall back to default piece if (js_load_piece(rt, "/piece.mjs") < 0) { ac_log("[ac-native] FATAL: Cannot reload default piece\n"); running = 0; break; } }
// Flush log on piece transition so USB pull captures it if (logfile) { fflush(logfile); fsync(fileno(logfile)); }
// Clear screen and call boot() on new piece graph_wipe(&graph, (ACColor){0, 0, 0, 255}); js_call_boot(rt); }
struct timespec _pf_sim0, _pf_sim1, _pf_paint0, _pf_paint1, _pf_pres0, _pf_pres1; if (audio_beat_check(audio)) js_call_beat(rt); clock_gettime(CLOCK_MONOTONIC, &_pf_sim0); js_call_sim(rt); clock_gettime(CLOCK_MONOTONIC, &_pf_sim1); clock_gettime(CLOCK_MONOTONIC, &_pf_paint0); js_call_paint(rt);
clock_gettime(CLOCK_MONOTONIC, &_pf_paint1);
// Crash overlay — red bar with error message when JS throws if (rt->crash_active) { rt->crash_frame++; int bar_h = 24; int flash = (rt->crash_frame < 30) ? (rt->crash_frame % 6 < 3 ? 255 : 180) : 200; graph_ink(&graph, (ACColor){flash, 0, 0, 240}); graph_box(&graph, 0, 0, screen->width, bar_h, 1); graph_ink(&graph, (ACColor){255, 255, 255, 255}); font_draw_matrix(&graph, "CRASH", 4, 4, 2); // Truncate message to fit screen char crash_display[128]; snprintf(crash_display, sizeof(crash_display), "%s", rt->crash_msg); graph_ink(&graph, (ACColor){255, 200, 200, 255}); font_draw_matrix(&graph, crash_display, 60, 8, 1); // Auto-dismiss after 5 seconds (300 frames at 60fps) if (rt->crash_frame > 300) { rt->crash_active = 0; } }
// Software cursor on its own overlay buffer (unaffected by KidLisp effects) if (cursor_fb && input && (input->pointer_x || input->pointer_y)) { fb_clear(cursor_fb, 0x00000000); // transparent graph_page(&graph, cursor_fb); int cx = input->pointer_x / pixel_scale, cy = input->pointer_y / pixel_scale; // Shadow (black, offset +1,+1) graph_ink(&graph, (ACColor){0, 0, 0, 180}); graph_line(&graph, cx+1, cy-9, cx+1, cy-4); graph_line(&graph, cx+1, cy+6, cx+1, cy+11); graph_line(&graph, cx-9, cy+1, cx-4, cy+1); graph_line(&graph, cx+6, cy+1, cx+11, cy+1); graph_plot(&graph, cx+1, cy+1); // Crosshair color reflects WiFi state { ACColor cross_color = {128, 128, 128, 255}; // gray = no wifi if (wifi) { switch (wifi->state) { case WIFI_STATE_CONNECTED: cross_color = (ACColor){0, 255, 255, 255}; // cyan break; case WIFI_STATE_SCANNING: case WIFI_STATE_CONNECTING: cross_color = (ACColor){255, 255, 0, 255}; // yellow break; case WIFI_STATE_FAILED: cross_color = (ACColor){255, 60, 60, 255}; // red break; default: cross_color = (ACColor){128, 128, 128, 255}; // gray break; } } graph_ink(&graph, cross_color); } graph_line(&graph, cx, cy-10, cx, cy-5); graph_line(&graph, cx, cy+5, cx, cy+10); graph_line(&graph, cx-10, cy, cx-5, cy); graph_line(&graph, cx+5, cy, cx+10, cy); // White center dot graph_ink(&graph, (ACColor){255, 255, 255, 255}); graph_plot(&graph, cx, cy); // Composite cursor region onto screen (only the area around cursor) graph_page(&graph, screen); int bx = cx - 12, by = cy - 12, bw = 25, bh = 25; if (bx < 0) bx = 0; if (by < 0) by = 0; if (bx + bw > cursor_fb->width) bw = cursor_fb->width - bx; if (by + bh > cursor_fb->height) bh = cursor_fb->height - by; for (int py = by; py < by + bh; py++) { for (int px = bx; px < bx + bw; px++) { uint32_t pixel = cursor_fb->pixels[py * cursor_fb->stride + px]; if (pixel >> 24) // only blit non-transparent fb_blend_pixel(screen, px, py, pixel); } } }
// WiFi "online" TTS announcement { static int was_connected = 0; int is_connected = (wifi && wifi->state == WIFI_STATE_CONNECTED); if (is_connected && !was_connected) { if (tts) { char wifi_msg[128]; if (wifi->connected_ssid[0]) snprintf(wifi_msg, sizeof(wifi_msg), "connected to %s", wifi->connected_ssid); else snprintf(wifi_msg, sizeof(wifi_msg), "online"); tts_speak(tts, wifi_msg); } ac_log("[wifi-tts] connected to %s", wifi->connected_ssid[0] ? wifi->connected_ssid : "(unknown)");
// Fetch device tokens (Claude + GitHub) from API (authenticated) { FILE *cf = fopen("/mnt/config.json", "r"); if (cf) { char cbuf[4096] = {0}; fread(cbuf, 1, sizeof(cbuf) - 1, cf); fclose(cf); char handle[64] = {0}, actoken[1024] = {0}; parse_config_string(cbuf, "\"handle\"", handle, sizeof(handle)); parse_config_string(cbuf, "\"token\"", actoken, sizeof(actoken)); if (handle[0] && actoken[0]) { char cmd[2048]; snprintf(cmd, sizeof(cmd), "curl -fsSL -H 'Authorization: Bearer %s' " "'https://aesthetic.computer/.netlify/functions/claude-token' " "-o /tmp/claude-api-resp.json 2>/dev/null &", actoken); system(cmd); ac_log("[tokens] fetching for @%s\n", handle); } // Mood refresh: GET /api/mood/@<handle>. Public, no auth. // Result is parsed in the main loop and persisted to // /mnt/last-mood for the NEXT boot to pick up — we // don't try to update the in-flight splash subtitle. if (handle[0]) { char mcmd[1024]; snprintf(mcmd, sizeof(mcmd), "curl -fsSL --max-time 10 " "'https://aesthetic.computer/api/mood/@%s' " "-o /tmp/mood-resp.json 2>/dev/null &", handle); system(mcmd); ac_log("[mood] fetching for @%s\n", handle); } } } // Device-id refresh: GET /api/ac-device?fp=<fp>. Public read. // Response is { slot, model, ... } or 404 (we ignore 404 → // unassigned, splash shows nothing). Parsed in the main // loop and persisted to /mnt/.ac-device-slot for the next // boot's badge. if (ac_device_fp[0]) { char dcmd[1024]; snprintf(dcmd, sizeof(dcmd), "curl -fsSL --max-time 10 " "'https://aesthetic.computer/api/ac-device?fp=%s' " "-o /tmp/ac-device-resp.json 2>/dev/null &", ac_device_fp); system(dcmd); ac_log("[ac-device] fetching for fp=%s\n", ac_device_fp); } // Clone (or pull) the aesthetic-computer repo to // /mnt/ac-repo so Claude Code has a real project to // work in. Fetch stays on the GitHub HTTPS mirror for // simple read access; if a Tangled SSH key was baked, // the repo also gets origin pushurls for knot + GitHub // and a dedicated `tangled` remote. Runs in the // background so boot isn't delayed. We use a shallow // clone (--depth=50) to keep the size manageable // (~200 MB) while still giving Claude enough history // for basic blame/log work. // // Idempotent: if /mnt/ac-repo/.git already exists, // `git pull` runs instead (bounded by 30s timeout). // Logs go to /tmp/ac-repo-clone.log. { char clone_cmd[3072]; snprintf(clone_cmd, sizeof(clone_cmd), "( REPO=/mnt/ac-repo; " " FETCH_URL='https://github.com/whistlegraph/aesthetic-computer.git'; " " TANGLED_URL='git@knot.aesthetic.computer:aesthetic.computer/core'; " " if [ -d \"$REPO/.git\" ]; then " " echo '[ac-repo] pulling latest' && " " cd \"$REPO\" && timeout 30 git pull --ff-only 2>&1; " " else " " echo '[ac-repo] cloning (shallow)' && " " timeout 300 git clone --depth=50 --branch=main \"$FETCH_URL\" \"$REPO\" 2>&1; " " fi; " " if [ -d \"$REPO/.git\" ]; then " " cd \"$REPO\"; " " git remote set-url origin \"$FETCH_URL\"; " " git config --unset-all remote.origin.pushurl 2>/dev/null || true; " " if [ -f /tmp/.ssh/tangled ]; then " " git config --add remote.origin.pushurl \"$TANGLED_URL\"; " " git config --add remote.origin.pushurl \"$FETCH_URL\"; " " if git remote | grep -qx tangled; then " " git remote set-url tangled \"$TANGLED_URL\"; " " else " " git remote add tangled \"$TANGLED_URL\"; " " fi; " " echo '[ac-repo] origin pushurl: tangled + github'; " " else " " git config --add remote.origin.pushurl \"$FETCH_URL\"; " " git remote remove tangled 2>/dev/null || true; " " echo '[ac-repo] origin pushurl: github only (no tangled key)'; " " fi; " " fi " ") >> /tmp/ac-repo-clone.log 2>&1 &"); system(clone_cmd); ac_log("[ac-repo] background clone/pull started\n"); } ac_log_flush(); } was_connected = is_connected; }
// Machines monitoring daemon (connects, heartbeats, handles commands) { static int fps_counter = 0, fps_display = 0; static struct timespec fps_last = {0}; fps_counter++; struct timespec fps_now; clock_gettime(CLOCK_MONOTONIC, &fps_now); if (fps_now.tv_sec > fps_last.tv_sec) { fps_display = fps_counter; fps_counter = 0; fps_last = fps_now; } machines_tick(&g_machines, wifi, main_frame, fps_display, rt->jump_target[0] ? rt->jump_target : rt->piece);
// Handle commands forwarded from machines daemon if (g_machines.cmd_pending) { g_machines.cmd_pending = 0; if (strcmp(g_machines.cmd_type, "jump") == 0 && g_machines.cmd_target[0]) { strncpy(rt->jump_target, g_machines.cmd_target, sizeof(rt->jump_target) - 1); rt->jump_requested = 1; ac_log("[machines] jump → %s\n", g_machines.cmd_target); } else if ((strcmp(g_machines.cmd_type, "prompt") == 0 || strcmp(g_machines.cmd_type, "prompt-bg") == 0) && g_machines.cmd_target[0]) { // Stage text + id for prompt.mjs to consume on boot, // then route the runtime through the prompt piece. // For prompt-bg we also remember the current piece so // prompt.mjs can return there after running execute(). strncpy(rt->pending_prompt_text, g_machines.cmd_target, sizeof(rt->pending_prompt_text) - 1); rt->pending_prompt_text[sizeof(rt->pending_prompt_text) - 1] = 0; strncpy(rt->pending_prompt_id, g_machines.cmd_id, sizeof(rt->pending_prompt_id) - 1); rt->pending_prompt_id[sizeof(rt->pending_prompt_id) - 1] = 0; rt->pending_prompt_bg = (strcmp(g_machines.cmd_type, "prompt-bg") == 0) ? 1 : 0; if (rt->pending_prompt_bg) { // Capture the *current* piece (post-jump value of // jump_target if set, else the boot piece) before // overwriting jump_target with "prompt" below. const char *return_to = rt->jump_target[0] ? rt->jump_target : (rt->piece[0] ? rt->piece : "prompt"); strncpy(rt->pending_prompt_return_to, return_to, sizeof(rt->pending_prompt_return_to) - 1); rt->pending_prompt_return_to[sizeof(rt->pending_prompt_return_to) - 1] = 0; } else { rt->pending_prompt_return_to[0] = 0; } rt->pending_prompt_cmd = 1; strncpy(rt->jump_target, "prompt", sizeof(rt->jump_target) - 1); rt->jump_requested = 1; ac_log("[machines] %s → %.80s (return=%s)\n", g_machines.cmd_type, g_machines.cmd_target, rt->pending_prompt_return_to[0] ? rt->pending_prompt_return_to : "(none)"); } else if (strcmp(g_machines.cmd_type, "update") == 0) { // Jump to notepat which handles OTA updates strncpy(rt->jump_target, "notepat", sizeof(rt->jump_target) - 1); rt->jump_requested = 1; ac_log("[machines] update requested, jumping to notepat\n"); } } }
// LAN dev server: publish piece/ip for the status endpoint and // mDNS announcer, then watch /tmp/ac-jump — the hot-reload // trigger shared by lanserv HTTP and ssh // (`echo notepat > /tmp/ac-jump`). { lanserv_update(rt->jump_target[0] ? rt->jump_target : rt->piece, (wifi && wifi->state == WIFI_STATE_CONNECTED) ? wifi->ip_address : ""); if (access("/tmp/ac-jump", F_OK) == 0) { char jbuf[64] = {0}; FILE *jf = fopen("/tmp/ac-jump", "r"); if (jf) { if (fgets(jbuf, sizeof(jbuf), jf)) jbuf[strcspn(jbuf, " \t\r\n")] = 0; fclose(jf); } unlink("/tmp/ac-jump"); if (jbuf[0]) { strncpy(rt->jump_target, jbuf, sizeof(rt->jump_target) - 1); rt->jump_target[sizeof(rt->jump_target) - 1] = 0; rt->jump_requested = 1; ac_log("[lanserv] jump → %s\n", jbuf); } } }
// Submit frame to recorder (after cursor overlay, before display) if (recorder_is_recording(recorder)) recorder_submit_video(recorder, screen->pixels, screen->stride);
// Tape recording overlay — drawn AFTER the recorder submit so // the live screen shows recording state but the MP4 stays clean // (matters for 'cap' camera footage headed to an edit bay). if (g_tape_recording) { graph_page(&graph, screen); long elapsed = (long)(time(NULL) - g_tape_start_sec); int blink = ((main_frame / 30) & 1); // toggle ~every 0.5s // Red dot if (blink) { graph_ink(&graph, (ACColor){230, 40, 40, 240}); graph_box(&graph, 6, 6, 8, 8, 1); } // "TAPE 0:23" label char rec_label[32]; snprintf(rec_label, sizeof(rec_label), "TAPE %ld:%02ld", elapsed / 60, elapsed % 60); graph_ink(&graph, (ACColor){0, 0, 0, 180}); graph_box(&graph, 16, 4, (int)strlen(rec_label) * 6 + 4, 12, 1); graph_ink(&graph, (ACColor){255, 220, 220, 255}); font_draw_matrix(&graph, rec_label, 18, 6, 1); }
// Draw recording indicator (red dot + duration) if (recorder_is_recording(recorder)) { graph_page(&graph, screen); graph_ink(&graph, (ACColor){255, 40, 40, 255}); // Red dot at top-right (4px circle) int rx = screen->width - 8, ry = 4; graph_circle(&graph, rx, ry, 3, 1); }
clock_gettime(CLOCK_MONOTONIC, &_pf_pres0); ac_display_present(display, screen, pixel_scale); clock_gettime(CLOCK_MONOTONIC, &_pf_pres1);
// HDMI: render waveform at ~7.5Hz (every 8 frames) — 4K dumb-buf is slow if (hdmi && audio && main_frame % 8 == 0) { drm_secondary_present_waveform(hdmi, &graph, audio->waveform_left, AUDIO_WAVEFORM_SIZE, audio->waveform_pos); }
// HDMI hotplug detection every ~180 frames (~3s) if (main_frame % 180 == 0 && display && !display->is_fbdev) { int hdmi_connected = drm_secondary_is_connected(display); if (hdmi_connected && !hdmi) { hdmi = drm_init_secondary(display); rt->hdmi = hdmi; if (hdmi) { ac_log("[ac-native] HDMI plugged in: %dx%d\n", hdmi->width, hdmi->height); // HDMI on: rising chord if (audio) { audio_synth(audio, WAVE_SINE, 523.25, 0.15, 0.2, 0.005, 0.12, 0.0); audio_synth(audio, WAVE_SINE, 783.99, 0.15, 0.15, 0.02, 0.11, 0.0); } } } else if (!hdmi_connected && hdmi) { ac_log("[ac-native] HDMI unplugged\n"); // HDMI off: descending two-tone if (audio) { audio_synth(audio, WAVE_SINE, 523.25, 0.12, 0.18, 0.005, 0.10, 0.0); audio_synth(audio, WAVE_SINE, 392.00, 0.12, 0.14, 0.02, 0.09, 0.0); } drm_secondary_destroy(hdmi); hdmi = NULL; rt->hdmi = NULL; } }
// Sync the USB log in batches instead of on every event. if (logfile && log_dirty && main_frame % 300 == 0) { ac_log_flush(); }
// ── Record frame perf ── { struct timespec _pf_end; clock_gettime(CLOCK_MONOTONIC, &_pf_end); #define TS_US(a, b) (uint16_t)({ \ long _d = ((b).tv_sec - (a).tv_sec) * 1000000L + \ ((b).tv_nsec - (a).tv_nsec) / 1000L; \ _d < 0 ? 0 : (_d > 65535 ? 65535 : _d); }) int _voices = 0; if (audio) { for (int _v = 0; _v < AUDIO_MAX_VOICES; _v++) if (audio->voices[_v].state != VOICE_INACTIVE) _voices++; } PerfRecord pr = { .frame = (uint32_t)main_frame, .total_us = TS_US(_pf_start, _pf_end), .act_us = TS_US(_pf_act0, _pf_act1), .sim_us = TS_US(_pf_sim0, _pf_sim1), .paint_us = TS_US(_pf_paint0, _pf_paint1), .present_us = TS_US(_pf_pres0, _pf_pres1), .voices = (uint8_t)(_voices > 255 ? 255 : _voices), .events = (uint8_t)(input->event_count > 255 ? 255 : input->event_count), .js_heap_mb = 0, .flags = (uint8_t)((input->pointer_x || input->pointer_y ? 2 : 0)), }; perf_record(&pr);
// Log perf summary every 5 seconds (300 frames @ 60fps) { static uint32_t perf_log_max = 0, perf_log_sum = 0; static uint16_t perf_log_paint_max = 0, perf_log_pres_max = 0; static int perf_log_slow = 0; // frames > 20ms if (pr.total_us > perf_log_max) perf_log_max = pr.total_us; if (pr.paint_us > perf_log_paint_max) perf_log_paint_max = pr.paint_us; if (pr.present_us > perf_log_pres_max) perf_log_pres_max = pr.present_us; perf_log_sum += pr.total_us; if (pr.total_us > 20000) perf_log_slow++; if (main_frame % 300 == 299) { ac_log("[perf] f=%d avg=%.1fms max=%.1fms paint_max=%.1fms pres_max=%.1fms slow=%d voices=%d\n", (int)main_frame, perf_log_sum / 300.0f / 1000.0f, perf_log_max / 1000.0f, perf_log_paint_max / 1000.0f, perf_log_pres_max / 1000.0f, perf_log_slow, pr.voices); perf_log_max = 0; perf_log_sum = 0; perf_log_paint_max = 0; perf_log_pres_max = 0; perf_log_slow = 0; } }
// Write frame marker to ftrace ring buffer for BPF correlation. // Only active when /tmp/.trace-active exists (zero cost otherwise). { static int trace_marker_fd = -2; // -2 = unchecked if (trace_marker_fd == -2) { struct stat _tst; if (stat("/tmp/.trace-active", &_tst) == 0) trace_marker_fd = open("/sys/kernel/tracing/trace_marker", O_WRONLY | O_CLOEXEC); else trace_marker_fd = -1; } if (trace_marker_fd >= 0) { char _tbuf[64]; int _tlen = snprintf(_tbuf, sizeof(_tbuf), "frame:%u total:%u\n", (uint32_t)main_frame, pr.total_us); (void)write(trace_marker_fd, _tbuf, _tlen); } }
// Flush chunk to disk every 30 seconds (fsync'd, crash-safe) if (main_frame - perf_flush_frame >= PERF_CHUNK_FRAMES) { perf_flush(); perf_flush_frame = main_frame; } #undef TS_US }
// DRM page flip already syncs to vblank (~16ms). Only use // frame_sync for fbdev/non-vsync paths to avoid double-wait. if (display && display->is_fbdev) frame_sync_60fps(&frame_time); } }
// Stop recording if active if (recorder) { if (audio) { audio->rec_callback = NULL; audio->rec_userdata = NULL; } recorder_destroy(recorder); recorder = NULL; }
// Flush final perf data perf_destroy();
// Cleanup (TTS bye + shutdown chime already fired at power-press time) ac_log("[ac-native] Shutting down\n"); // Upload the complete boot-to-shutdown log before tearing down machines_flush_logs(&g_machines); machines_destroy(&g_machines); lanserv_stop();
if (logfile) { fclose(logfile); logfile = NULL; } sync(); // Unmount USB log umount("/mnt");
js_call_leave(rt); js_destroy(rt); wifi_destroy(wifi); if (tts) { tts_wait(tts); usleep(300000); // Let TTS ring buffer drain } usleep(600000); // Let shutdown chime ring out tts_destroy(tts); audio_destroy(audio); if (input) input_destroy(input); if (hdmi) drm_secondary_destroy(hdmi); fb_destroy(screen); if (display) drm_destroy(display);
ac_poweroff();
return 0;}