| tacme.c - plan9port - [fork] Plan 9 from user space | |
| git clone git://src.adamsgaard.dk/plan9port | |
| Log | |
| Files | |
| Refs | |
| README | |
| LICENSE | |
| --- | |
| tacme.c (24330B) | |
| --- | |
| 1 #include <u.h> | |
| 2 #include <libc.h> | |
| 3 #include <draw.h> | |
| 4 #include <thread.h> | |
| 5 #include <cursor.h> | |
| 6 #include <mouse.h> | |
| 7 #include <keyboard.h> | |
| 8 #include <frame.h> | |
| 9 #include <fcall.h> | |
| 10 #include <plumb.h> | |
| 11 #include <libsec.h> | |
| 12 #include "dat.h" | |
| 13 #include "fns.h" | |
| 14 /* for generating syms in mkfile only: */ | |
| 15 #include <bio.h> | |
| 16 #include "edit.h" | |
| 17 | |
| 18 void mousethread(void*); | |
| 19 void keyboardthread(void*); | |
| 20 void waitthread(void*); | |
| 21 void xfidallocthread(void*); | |
| 22 void newwindowthread(void*); | |
| 23 void plumbproc(void*); | |
| 24 int timefmt(Fmt*); | |
| 25 | |
| 26 Reffont **fontcache; | |
| 27 int nfontcache; | |
| 28 char wdir[512] = "."; | |
| 29 Reffont *reffonts[2]; | |
| 30 int snarffd = -1; | |
| 31 int mainpid; | |
| 32 int swapscrollbuttons = FALSE; | |
| 33 char *mtpt; | |
| 34 | |
| 35 enum{ | |
| 36 NSnarf = 1000 /* less than 1024, I/O buffer size */ | |
| 37 }; | |
| 38 Rune snarfrune[NSnarf+1]; | |
| 39 | |
| 40 char *fontnames[2] = | |
| 41 { | |
| 42 "/lib/font/bit/lucsans/euro.8.font", | |
| 43 "/lib/font/bit/lucm/unicode.9.font" | |
| 44 }; | |
| 45 | |
| 46 Command *command; | |
| 47 | |
| 48 void shutdownthread(void*); | |
| 49 void acmeerrorinit(void); | |
| 50 void readfile(Column*, char*); | |
| 51 static int shutdown(void*, char*); | |
| 52 | |
| 53 void | |
| 54 derror(Display *d, char *errorstr) | |
| 55 { | |
| 56 USED(d); | |
| 57 error(errorstr); | |
| 58 } | |
| 59 | |
| 60 void | |
| 61 threadmain(int argc, char *argv[]) | |
| 62 { | |
| 63 int i; | |
| 64 char *p, *loadfile; | |
| 65 Column *c; | |
| 66 int ncol; | |
| 67 Display *d; | |
| 68 | |
| 69 rfork(RFENVG|RFNAMEG); | |
| 70 | |
| 71 ncol = -1; | |
| 72 | |
| 73 loadfile = nil; | |
| 74 ARGBEGIN{ | |
| 75 case 'D': | |
| 76 {extern int _threaddebuglevel; | |
| 77 _threaddebuglevel = ~0; | |
| 78 } | |
| 79 break; | |
| 80 case 'a': | |
| 81 globalautoindent = TRUE; | |
| 82 break; | |
| 83 case 'b': | |
| 84 bartflag = TRUE; | |
| 85 break; | |
| 86 case 'c': | |
| 87 p = ARGF(); | |
| 88 if(p == nil) | |
| 89 goto Usage; | |
| 90 ncol = atoi(p); | |
| 91 if(ncol <= 0) | |
| 92 goto Usage; | |
| 93 break; | |
| 94 case 'f': | |
| 95 fontnames[0] = ARGF(); | |
| 96 if(fontnames[0] == nil) | |
| 97 goto Usage; | |
| 98 break; | |
| 99 case 'F': | |
| 100 fontnames[1] = ARGF(); | |
| 101 if(fontnames[1] == nil) | |
| 102 goto Usage; | |
| 103 break; | |
| 104 case 'l': | |
| 105 loadfile = ARGF(); | |
| 106 if(loadfile == nil) | |
| 107 goto Usage; | |
| 108 break; | |
| 109 case 'm': | |
| 110 mtpt = ARGF(); | |
| 111 if(mtpt == nil) | |
| 112 goto Usage; | |
| 113 break; | |
| 114 case 'r': | |
| 115 swapscrollbuttons = TRUE; | |
| 116 break; | |
| 117 case 'W': | |
| 118 winsize = ARGF(); | |
| 119 if(winsize == nil) | |
| 120 goto Usage; | |
| 121 break; | |
| 122 default: | |
| 123 Usage: | |
| 124 fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwi… | |
| 125 threadexitsall("usage"); | |
| 126 }ARGEND | |
| 127 | |
| 128 fontnames[0] = estrdup(fontnames[0]); | |
| 129 fontnames[1] = estrdup(fontnames[1]); | |
| 130 | |
| 131 quotefmtinstall(); | |
| 132 fmtinstall('t', timefmt); | |
| 133 | |
| 134 cputype = getenv("cputype"); | |
| 135 objtype = getenv("objtype"); | |
| 136 home = getenv("HOME"); | |
| 137 acmeshell = getenv("acmeshell"); | |
| 138 if(acmeshell && *acmeshell == '\0') | |
| 139 acmeshell = nil; | |
| 140 p = getenv("tabstop"); | |
| 141 if(p != nil){ | |
| 142 maxtab = strtoul(p, nil, 0); | |
| 143 free(p); | |
| 144 } | |
| 145 if(maxtab == 0) | |
| 146 maxtab = 4; | |
| 147 if(loadfile) | |
| 148 rowloadfonts(loadfile); | |
| 149 putenv("font", fontnames[0]); | |
| 150 snarffd = open("/dev/snarf", OREAD|OCEXEC); | |
| 151 /* | |
| 152 if(cputype){ | |
| 153 sprint(buf, "/acme/bin/%s", cputype); | |
| 154 bind(buf, "/bin", MBEFORE); | |
| 155 } | |
| 156 bind("/acme/bin", "/bin", MBEFORE); | |
| 157 */ | |
| 158 getwd(wdir, sizeof wdir); | |
| 159 | |
| 160 /* | |
| 161 if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) … | |
| 162 fprint(2, "acme: can't open display: %r\n"); | |
| 163 threadexitsall("geninitdraw"); | |
| 164 } | |
| 165 */ | |
| 166 if(initdraw(derror, fontnames[0], "acme") < 0){ | |
| 167 fprint(2, "acme: can't open display: %r\n"); | |
| 168 threadexitsall("initdraw"); | |
| 169 } | |
| 170 | |
| 171 d = display; | |
| 172 font = d->defaultfont; | |
| 173 /*assert(font); */ | |
| 174 | |
| 175 reffont.f = font; | |
| 176 reffonts[0] = &reffont; | |
| 177 incref(&reffont.ref); /* one to hold up 'font' variable */ | |
| 178 incref(&reffont.ref); /* one to hold up reffonts[0] */ | |
| 179 fontcache = emalloc(sizeof(Reffont*)); | |
| 180 nfontcache = 1; | |
| 181 fontcache[0] = &reffont; | |
| 182 | |
| 183 iconinit(); | |
| 184 timerinit(); | |
| 185 rxinit(); | |
| 186 | |
| 187 cwait = threadwaitchan(); | |
| 188 ccommand = chancreate(sizeof(Command**), 0); | |
| 189 ckill = chancreate(sizeof(Rune*), 0); | |
| 190 cxfidalloc = chancreate(sizeof(Xfid*), 0); | |
| 191 cxfidfree = chancreate(sizeof(Xfid*), 0); | |
| 192 cnewwindow = chancreate(sizeof(Channel*), 0); | |
| 193 cerr = chancreate(sizeof(char*), 0); | |
| 194 cedit = chancreate(sizeof(int), 0); | |
| 195 cexit = chancreate(sizeof(int), 0); | |
| 196 cwarn = chancreate(sizeof(void*), 1); | |
| 197 if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil … | |
| 198 fprint(2, "acme: can't create initial channels: %r\n"); | |
| 199 threadexitsall("channels"); | |
| 200 } | |
| 201 chansetname(ccommand, "ccommand"); | |
| 202 chansetname(ckill, "ckill"); | |
| 203 chansetname(cxfidalloc, "cxfidalloc"); | |
| 204 chansetname(cxfidfree, "cxfidfree"); | |
| 205 chansetname(cnewwindow, "cnewwindow"); | |
| 206 chansetname(cerr, "cerr"); | |
| 207 chansetname(cedit, "cedit"); | |
| 208 chansetname(cexit, "cexit"); | |
| 209 chansetname(cwarn, "cwarn"); | |
| 210 | |
| 211 mousectl = initmouse(nil, screen); | |
| 212 if(mousectl == nil){ | |
| 213 fprint(2, "acme: can't initialize mouse: %r\n"); | |
| 214 threadexitsall("mouse"); | |
| 215 } | |
| 216 mouse = &mousectl->m; | |
| 217 keyboardctl = initkeyboard(nil); | |
| 218 if(keyboardctl == nil){ | |
| 219 fprint(2, "acme: can't initialize keyboard: %r\n"); | |
| 220 threadexitsall("keyboard"); | |
| 221 } | |
| 222 mainpid = getpid(); | |
| 223 startplumbing(); | |
| 224 /* | |
| 225 plumbeditfd = plumbopen("edit", OREAD|OCEXEC); | |
| 226 if(plumbeditfd < 0) | |
| 227 fprint(2, "acme: can't initialize plumber: %r\n"); | |
| 228 else{ | |
| 229 cplumb = chancreate(sizeof(Plumbmsg*), 0); | |
| 230 threadcreate(plumbproc, nil, STACK); | |
| 231 } | |
| 232 plumbsendfd = plumbopen("send", OWRITE|OCEXEC); | |
| 233 */ | |
| 234 | |
| 235 fsysinit(); | |
| 236 | |
| 237 #define WPERCOL 8 | |
| 238 disk = diskinit(); | |
| 239 if(!loadfile || !rowload(&row, loadfile, TRUE)){ | |
| 240 rowinit(&row, screen->clipr); | |
| 241 if(ncol < 0){ | |
| 242 if(argc == 0) | |
| 243 ncol = 2; | |
| 244 else{ | |
| 245 ncol = (argc+(WPERCOL-1))/WPERCOL; | |
| 246 if(ncol < 2) | |
| 247 ncol = 2; | |
| 248 } | |
| 249 } | |
| 250 if(ncol == 0) | |
| 251 ncol = 2; | |
| 252 for(i=0; i<ncol; i++){ | |
| 253 c = rowadd(&row, nil, -1); | |
| 254 if(c==nil && i==0) | |
| 255 error("initializing columns"); | |
| 256 } | |
| 257 c = row.col[row.ncol-1]; | |
| 258 if(argc == 0) | |
| 259 readfile(c, wdir); | |
| 260 else | |
| 261 for(i=0; i<argc; i++){ | |
| 262 p = utfrrune(argv[i], '/'); | |
| 263 if((p!=nil && strcmp(p, "/guide")==0) ||… | |
| 264 readfile(c, argv[i]); | |
| 265 else | |
| 266 readfile(row.col[i/WPERCOL], arg… | |
| 267 } | |
| 268 } | |
| 269 flushimage(display, 1); | |
| 270 | |
| 271 acmeerrorinit(); | |
| 272 threadcreate(keyboardthread, nil, STACK); | |
| 273 threadcreate(mousethread, nil, STACK); | |
| 274 threadcreate(waitthread, nil, STACK); | |
| 275 threadcreate(xfidallocthread, nil, STACK); | |
| 276 threadcreate(newwindowthread, nil, STACK); | |
| 277 /* threadcreate(shutdownthread, nil, STACK); */ | |
| 278 threadnotify(shutdown, 1); | |
| 279 recvul(cexit); | |
| 280 killprocs(); | |
| 281 threadexitsall(nil); | |
| 282 } | |
| 283 | |
| 284 void | |
| 285 readfile(Column *c, char *s) | |
| 286 { | |
| 287 Window *w; | |
| 288 Rune rb[256]; | |
| 289 int nr; | |
| 290 Runestr rs; | |
| 291 | |
| 292 w = coladd(c, nil, nil, -1); | |
| 293 if(s[0] != '/') | |
| 294 runesnprint(rb, sizeof rb, "%s/%s", wdir, s); | |
| 295 else | |
| 296 runesnprint(rb, sizeof rb, "%s", s); | |
| 297 nr = runestrlen(rb); | |
| 298 rs = cleanrname(runestr(rb, nr)); | |
| 299 winsetname(w, rs.r, rs.nr); | |
| 300 textload(&w->body, 0, s, 1); | |
| 301 w->body.file->mod = FALSE; | |
| 302 w->dirty = FALSE; | |
| 303 winsettag(w); | |
| 304 winresize(w, w->r, FALSE, TRUE); | |
| 305 textscrdraw(&w->body); | |
| 306 textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc); | |
| 307 xfidlog(w, "new"); | |
| 308 } | |
| 309 | |
| 310 char *ignotes[] = { | |
| 311 "sys: write on closed pipe", | |
| 312 "sys: ttin", | |
| 313 "sys: ttou", | |
| 314 "sys: tstp", | |
| 315 nil | |
| 316 }; | |
| 317 | |
| 318 char *oknotes[] ={ | |
| 319 "delete", | |
| 320 "hangup", | |
| 321 "kill", | |
| 322 "exit", | |
| 323 nil | |
| 324 }; | |
| 325 | |
| 326 int dumping; | |
| 327 | |
| 328 static int | |
| 329 shutdown(void *v, char *msg) | |
| 330 { | |
| 331 int i; | |
| 332 | |
| 333 USED(v); | |
| 334 | |
| 335 for(i=0; ignotes[i]; i++) | |
| 336 if(strncmp(ignotes[i], msg, strlen(ignotes[i])) == 0) | |
| 337 return 1; | |
| 338 | |
| 339 killprocs(); | |
| 340 if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 … | |
| 341 dumping = TRUE; | |
| 342 rowdump(&row, nil); | |
| 343 } | |
| 344 for(i=0; oknotes[i]; i++) | |
| 345 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0) | |
| 346 threadexitsall(msg); | |
| 347 print("acme: %s\n", msg); | |
| 348 return 0; | |
| 349 } | |
| 350 | |
| 351 /* | |
| 352 void | |
| 353 shutdownthread(void *v) | |
| 354 { | |
| 355 char *msg; | |
| 356 Channel *c; | |
| 357 | |
| 358 USED(v); | |
| 359 | |
| 360 threadsetname("shutdown"); | |
| 361 c = threadnotechan(); | |
| 362 while((msg = recvp(c)) != nil) | |
| 363 shutdown(nil, msg); | |
| 364 } | |
| 365 */ | |
| 366 | |
| 367 void | |
| 368 killprocs(void) | |
| 369 { | |
| 370 Command *c; | |
| 371 | |
| 372 fsysclose(); | |
| 373 /* if(display) */ | |
| 374 /* flushimage(display, 1); */ | |
| 375 | |
| 376 for(c=command; c; c=c->next) | |
| 377 postnote(PNGROUP, c->pid, "hangup"); | |
| 378 } | |
| 379 | |
| 380 static int errorfd; | |
| 381 int erroutfd; | |
| 382 | |
| 383 void | |
| 384 acmeerrorproc(void *v) | |
| 385 { | |
| 386 char *buf; | |
| 387 int n; | |
| 388 | |
| 389 USED(v); | |
| 390 threadsetname("acmeerrorproc"); | |
| 391 buf = emalloc(8192+1); | |
| 392 while((n=read(errorfd, buf, 8192)) >= 0){ | |
| 393 buf[n] = '\0'; | |
| 394 sendp(cerr, estrdup(buf)); | |
| 395 } | |
| 396 free(buf); | |
| 397 } | |
| 398 | |
| 399 void | |
| 400 acmeerrorinit(void) | |
| 401 { | |
| 402 int pfd[2]; | |
| 403 | |
| 404 if(pipe(pfd) < 0) | |
| 405 error("can't create pipe"); | |
| 406 #if 0 | |
| 407 sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid); | |
| 408 fd = create(acmeerrorfile, OWRITE, 0666); | |
| 409 if(fd < 0){ | |
| 410 remove(acmeerrorfile); | |
| 411 fd = create(acmeerrorfile, OWRITE, 0666); | |
| 412 if(fd < 0) | |
| 413 error("can't create acmeerror file"); | |
| 414 } | |
| 415 sprint(buf, "%d", pfd[0]); | |
| 416 write(fd, buf, strlen(buf)); | |
| 417 close(fd); | |
| 418 /* reopen pfd[1] close on exec */ | |
| 419 sprint(buf, "/fd/%d", pfd[1]); | |
| 420 errorfd = open(buf, OREAD|OCEXEC); | |
| 421 #endif | |
| 422 fcntl(pfd[0], F_SETFD, FD_CLOEXEC); | |
| 423 fcntl(pfd[1], F_SETFD, FD_CLOEXEC); | |
| 424 erroutfd = pfd[0]; | |
| 425 errorfd = pfd[1]; | |
| 426 if(errorfd < 0) | |
| 427 error("can't re-open acmeerror file"); | |
| 428 proccreate(acmeerrorproc, nil, STACK); | |
| 429 } | |
| 430 | |
| 431 /* | |
| 432 void | |
| 433 plumbproc(void *v) | |
| 434 { | |
| 435 Plumbmsg *m; | |
| 436 | |
| 437 USED(v); | |
| 438 threadsetname("plumbproc"); | |
| 439 for(;;){ | |
| 440 m = threadplumbrecv(plumbeditfd); | |
| 441 if(m == nil) | |
| 442 threadexits(nil); | |
| 443 sendp(cplumb, m); | |
| 444 } | |
| 445 } | |
| 446 */ | |
| 447 | |
| 448 void | |
| 449 keyboardthread(void *v) | |
| 450 { | |
| 451 Rune r; | |
| 452 Timer *timer; | |
| 453 Text *t; | |
| 454 enum { KTimer, KKey, NKALT }; | |
| 455 static Alt alts[NKALT+1]; | |
| 456 | |
| 457 USED(v); | |
| 458 alts[KTimer].c = nil; | |
| 459 alts[KTimer].v = nil; | |
| 460 alts[KTimer].op = CHANNOP; | |
| 461 alts[KKey].c = keyboardctl->c; | |
| 462 alts[KKey].v = &r; | |
| 463 alts[KKey].op = CHANRCV; | |
| 464 alts[NKALT].op = CHANEND; | |
| 465 | |
| 466 timer = nil; | |
| 467 typetext = nil; | |
| 468 threadsetname("keyboardthread"); | |
| 469 for(;;){ | |
| 470 switch(alt(alts)){ | |
| 471 case KTimer: | |
| 472 timerstop(timer); | |
| 473 t = typetext; | |
| 474 if(t!=nil && t->what==Tag){ | |
| 475 winlock(t->w, 'K'); | |
| 476 wincommit(t->w, t); | |
| 477 winunlock(t->w); | |
| 478 flushimage(display, 1); | |
| 479 } | |
| 480 alts[KTimer].c = nil; | |
| 481 alts[KTimer].op = CHANNOP; | |
| 482 break; | |
| 483 case KKey: | |
| 484 casekeyboard: | |
| 485 typetext = rowtype(&row, r, mouse->xy); | |
| 486 t = typetext; | |
| 487 if(t!=nil && t->col!=nil && !(r==Kdown || r==Kle… | |
| 488 activecol = t->col; | |
| 489 if(t!=nil && t->w!=nil) | |
| 490 t->w->body.file->curtext = &t->w->body; | |
| 491 if(timer != nil) | |
| 492 timercancel(timer); | |
| 493 if(t!=nil && t->what==Tag) { | |
| 494 timer = timerstart(500); | |
| 495 alts[KTimer].c = timer->c; | |
| 496 alts[KTimer].op = CHANRCV; | |
| 497 }else{ | |
| 498 timer = nil; | |
| 499 alts[KTimer].c = nil; | |
| 500 alts[KTimer].op = CHANNOP; | |
| 501 } | |
| 502 if(nbrecv(keyboardctl->c, &r) > 0) | |
| 503 goto casekeyboard; | |
| 504 flushimage(display, 1); | |
| 505 break; | |
| 506 } | |
| 507 } | |
| 508 } | |
| 509 | |
| 510 void | |
| 511 mousethread(void *v) | |
| 512 { | |
| 513 Text *t, *argt; | |
| 514 int but; | |
| 515 uint q0, q1; | |
| 516 Window *w; | |
| 517 Plumbmsg *pm; | |
| 518 Mouse m; | |
| 519 char *act; | |
| 520 enum { MResize, MMouse, MPlumb, MWarnings, NMALT }; | |
| 521 static Alt alts[NMALT+1]; | |
| 522 | |
| 523 USED(v); | |
| 524 threadsetname("mousethread"); | |
| 525 alts[MResize].c = mousectl->resizec; | |
| 526 alts[MResize].v = nil; | |
| 527 alts[MResize].op = CHANRCV; | |
| 528 alts[MMouse].c = mousectl->c; | |
| 529 alts[MMouse].v = &mousectl->m; | |
| 530 alts[MMouse].op = CHANRCV; | |
| 531 alts[MPlumb].c = cplumb; | |
| 532 alts[MPlumb].v = ± | |
| 533 alts[MPlumb].op = CHANRCV; | |
| 534 alts[MWarnings].c = cwarn; | |
| 535 alts[MWarnings].v = nil; | |
| 536 alts[MWarnings].op = CHANRCV; | |
| 537 if(cplumb == nil) | |
| 538 alts[MPlumb].op = CHANNOP; | |
| 539 alts[NMALT].op = CHANEND; | |
| 540 | |
| 541 for(;;){ | |
| 542 qlock(&row.lk); | |
| 543 flushwarnings(); | |
| 544 qunlock(&row.lk); | |
| 545 flushimage(display, 1); | |
| 546 switch(alt(alts)){ | |
| 547 case MResize: | |
| 548 if(getwindow(display, Refnone) < 0) | |
| 549 error("attach to window"); | |
| 550 draw(screen, screen->r, display->white, nil, ZP); | |
| 551 iconinit(); | |
| 552 scrlresize(); | |
| 553 rowresize(&row, screen->clipr); | |
| 554 break; | |
| 555 case MPlumb: | |
| 556 if(strcmp(pm->type, "text") == 0){ | |
| 557 act = plumblookup(pm->attr, "action"); | |
| 558 if(act==nil || strcmp(act, "showfile")==… | |
| 559 plumblook(pm); | |
| 560 else if(strcmp(act, "showdata")==0) | |
| 561 plumbshow(pm); | |
| 562 } | |
| 563 plumbfree(pm); | |
| 564 break; | |
| 565 case MWarnings: | |
| 566 break; | |
| 567 case MMouse: | |
| 568 /* | |
| 569 * Make a copy so decisions are consistent; mous… | |
| 570 * underfoot. Can't just receive into m because… | |
| 571 * another race; see /sys/src/libdraw/mouse.c. | |
| 572 */ | |
| 573 m = mousectl->m; | |
| 574 qlock(&row.lk); | |
| 575 t = rowwhich(&row, m.xy); | |
| 576 | |
| 577 if((t!=mousetext && t!=nil && t->w!=nil) && | |
| 578 (mousetext==nil || mousetext->w==nil || … | |
| 579 xfidlog(t->w, "focus"); | |
| 580 } | |
| 581 | |
| 582 if(t!=mousetext && mousetext!=nil && mousetext->… | |
| 583 winlock(mousetext->w, 'M'); | |
| 584 mousetext->eq0 = ~0; | |
| 585 wincommit(mousetext->w, mousetext); | |
| 586 winunlock(mousetext->w); | |
| 587 } | |
| 588 mousetext = t; | |
| 589 if(t == nil) | |
| 590 goto Continue; | |
| 591 w = t->w; | |
| 592 if(t==nil || m.buttons==0) | |
| 593 goto Continue; | |
| 594 but = 0; | |
| 595 if(m.buttons == 1) | |
| 596 but = 1; | |
| 597 else if(m.buttons == 2) | |
| 598 but = 2; | |
| 599 else if(m.buttons == 4) | |
| 600 but = 3; | |
| 601 barttext = t; | |
| 602 if(t->what==Body && ptinrect(m.xy, t->scrollr)){ | |
| 603 if(but){ | |
| 604 if(swapscrollbuttons){ | |
| 605 if(but == 1) | |
| 606 but = 3; | |
| 607 else if(but == 3) | |
| 608 but = 1; | |
| 609 } | |
| 610 winlock(w, 'M'); | |
| 611 t->eq0 = ~0; | |
| 612 textscroll(t, but); | |
| 613 winunlock(w); | |
| 614 } | |
| 615 goto Continue; | |
| 616 } | |
| 617 /* scroll buttons, wheels, etc. */ | |
| 618 if(w != nil && (m.buttons & (8|16))){ | |
| 619 if(m.buttons & 8) | |
| 620 but = Kscrolloneup; | |
| 621 else | |
| 622 but = Kscrollonedown; | |
| 623 winlock(w, 'M'); | |
| 624 t->eq0 = ~0; | |
| 625 texttype(t, but); | |
| 626 winunlock(w); | |
| 627 goto Continue; | |
| 628 } | |
| 629 if(ptinrect(m.xy, t->scrollr)){ | |
| 630 if(but){ | |
| 631 if(t->what == Columntag) | |
| 632 rowdragcol(&row, t->col,… | |
| 633 else if(t->what == Tag){ | |
| 634 coldragwin(t->col, t->w,… | |
| 635 if(t->w) | |
| 636 barttext = &t->w… | |
| 637 } | |
| 638 if(t->col) | |
| 639 activecol = t->col; | |
| 640 } | |
| 641 goto Continue; | |
| 642 } | |
| 643 if(m.buttons){ | |
| 644 if(w) | |
| 645 winlock(w, 'M'); | |
| 646 t->eq0 = ~0; | |
| 647 if(w) | |
| 648 wincommit(w, t); | |
| 649 else | |
| 650 textcommit(t, TRUE); | |
| 651 if(m.buttons & 1){ | |
| 652 textselect(t); | |
| 653 if(w) | |
| 654 winsettag(w); | |
| 655 argtext = t; | |
| 656 seltext = t; | |
| 657 if(t->col) | |
| 658 activecol = t->col; … | |
| 659 if(t->w!=nil && t==&t->w->body) | |
| 660 activewin = t->w; | |
| 661 }else if(m.buttons & 2){ | |
| 662 if(textselect2(t, &q0, &q1, &arg… | |
| 663 execute(t, q0, q1, FALSE… | |
| 664 }else if(m.buttons & 4){ | |
| 665 if(textselect3(t, &q0, &q1)) | |
| 666 look3(t, q0, q1, FALSE); | |
| 667 } | |
| 668 if(w) | |
| 669 winunlock(w); | |
| 670 goto Continue; | |
| 671 } | |
| 672 Continue: | |
| 673 qunlock(&row.lk); | |
| 674 break; | |
| 675 } | |
| 676 } | |
| 677 } | |
| 678 | |
| 679 /* | |
| 680 * There is a race between process exiting and our finding out it was ev… | |
| 681 * This structure keeps a list of processes that have exited we haven't … | |
| 682 */ | |
| 683 typedef struct Pid Pid; | |
| 684 struct Pid | |
| 685 { | |
| 686 int pid; | |
| 687 char msg[ERRMAX]; | |
| 688 Pid *next; | |
| 689 }; | |
| 690 | |
| 691 void | |
| 692 waitthread(void *v) | |
| 693 { | |
| 694 Waitmsg *w; | |
| 695 Command *c, *lc; | |
| 696 uint pid; | |
| 697 int found, ncmd; | |
| 698 Rune *cmd; | |
| 699 char *err; | |
| 700 Text *t; | |
| 701 Pid *pids, *p, *lastp; | |
| 702 enum { WErr, WKill, WWait, WCmd, NWALT }; | |
| 703 Alt alts[NWALT+1]; | |
| 704 | |
| 705 USED(v); | |
| 706 threadsetname("waitthread"); | |
| 707 pids = nil; | |
| 708 alts[WErr].c = cerr; | |
| 709 alts[WErr].v = &err; | |
| 710 alts[WErr].op = CHANRCV; | |
| 711 alts[WKill].c = ckill; | |
| 712 alts[WKill].v = &cmd; | |
| 713 alts[WKill].op = CHANRCV; | |
| 714 alts[WWait].c = cwait; | |
| 715 alts[WWait].v = &w; | |
| 716 alts[WWait].op = CHANRCV; | |
| 717 alts[WCmd].c = ccommand; | |
| 718 alts[WCmd].v = &c; | |
| 719 alts[WCmd].op = CHANRCV; | |
| 720 alts[NWALT].op = CHANEND; | |
| 721 | |
| 722 command = nil; | |
| 723 for(;;){ | |
| 724 switch(alt(alts)){ | |
| 725 case WErr: | |
| 726 qlock(&row.lk); | |
| 727 warning(nil, "%s", err); | |
| 728 free(err); | |
| 729 flushimage(display, 1); | |
| 730 qunlock(&row.lk); | |
| 731 break; | |
| 732 case WKill: | |
| 733 found = FALSE; | |
| 734 ncmd = runestrlen(cmd); | |
| 735 for(c=command; c; c=c->next){ | |
| 736 /* -1 for blank */ | |
| 737 if(runeeq(c->name, c->nname-1, cmd, ncmd… | |
| 738 if(postnote(PNGROUP, c->pid, "ki… | |
| 739 warning(nil, "kill %S: %… | |
| 740 found = TRUE; | |
| 741 } | |
| 742 } | |
| 743 if(!found) | |
| 744 warning(nil, "Kill: no process %S\n", cm… | |
| 745 free(cmd); | |
| 746 break; | |
| 747 case WWait: | |
| 748 pid = w->pid; | |
| 749 lc = nil; | |
| 750 for(c=command; c; c=c->next){ | |
| 751 if(c->pid == pid){ | |
| 752 if(lc) | |
| 753 lc->next = c->next; | |
| 754 else | |
| 755 command = c->next; | |
| 756 break; | |
| 757 } | |
| 758 lc = c; | |
| 759 } | |
| 760 qlock(&row.lk); | |
| 761 t = &row.tag; | |
| 762 textcommit(t, TRUE); | |
| 763 if(c == nil){ | |
| 764 /* helper processes use this exit status… | |
| 765 if(strncmp(w->msg, "libthread", 9) != 0){ | |
| 766 p = emalloc(sizeof(Pid)); | |
| 767 p->pid = pid; | |
| 768 strncpy(p->msg, w->msg, sizeof(p… | |
| 769 p->next = pids; | |
| 770 pids = p; | |
| 771 } | |
| 772 }else{ | |
| 773 if(search(t, c->name, c->nname)){ | |
| 774 textdelete(t, t->q0, t->q1, TRUE… | |
| 775 textsetselect(t, 0, 0); | |
| 776 } | |
| 777 if(w->msg[0]) | |
| 778 warning(c->md, "%.*S: exit %s\n"… | |
| 779 flushimage(display, 1); | |
| 780 } | |
| 781 qunlock(&row.lk); | |
| 782 free(w); | |
| 783 Freecmd: | |
| 784 if(c){ | |
| 785 if(c->iseditcmd) | |
| 786 sendul(cedit, 0); | |
| 787 free(c->text); | |
| 788 free(c->name); | |
| 789 fsysdelid(c->md); | |
| 790 free(c); | |
| 791 } | |
| 792 break; | |
| 793 case WCmd: | |
| 794 /* has this command already exited? */ | |
| 795 lastp = nil; | |
| 796 for(p=pids; p!=nil; p=p->next){ | |
| 797 if(p->pid == c->pid){ | |
| 798 if(p->msg[0]) | |
| 799 warning(c->md, "%s\n", p… | |
| 800 if(lastp == nil) | |
| 801 pids = p->next; | |
| 802 else | |
| 803 lastp->next = p->next; | |
| 804 free(p); | |
| 805 goto Freecmd; | |
| 806 } | |
| 807 lastp = p; | |
| 808 } | |
| 809 c->next = command; | |
| 810 command = c; | |
| 811 qlock(&row.lk); | |
| 812 t = &row.tag; | |
| 813 textcommit(t, TRUE); | |
| 814 textinsert(t, 0, c->name, c->nname, TRUE); | |
| 815 textsetselect(t, 0, 0); | |
| 816 flushimage(display, 1); | |
| 817 qunlock(&row.lk); | |
| 818 break; | |
| 819 } | |
| 820 } | |
| 821 } | |
| 822 | |
| 823 void | |
| 824 xfidallocthread(void *v) | |
| 825 { | |
| 826 Xfid *xfree, *x; | |
| 827 enum { Alloc, Free, N }; | |
| 828 static Alt alts[N+1]; | |
| 829 | |
| 830 USED(v); | |
| 831 threadsetname("xfidallocthread"); | |
| 832 alts[Alloc].c = cxfidalloc; | |
| 833 alts[Alloc].v = nil; | |
| 834 alts[Alloc].op = CHANRCV; | |
| 835 alts[Free].c = cxfidfree; | |
| 836 alts[Free].v = &x; | |
| 837 alts[Free].op = CHANRCV; | |
| 838 alts[N].op = CHANEND; | |
| 839 | |
| 840 xfree = nil; | |
| 841 for(;;){ | |
| 842 switch(alt(alts)){ | |
| 843 case Alloc: | |
| 844 x = xfree; | |
| 845 if(x) | |
| 846 xfree = x->next; | |
| 847 else{ | |
| 848 x = emalloc(sizeof(Xfid)); | |
| 849 x->c = chancreate(sizeof(void(*)(Xfid*))… | |
| 850 chansetname(x->c, "xc%p", x->c); | |
| 851 x->arg = x; | |
| 852 threadcreate(xfidctl, x->arg, STACK); | |
| 853 } | |
| 854 sendp(cxfidalloc, x); | |
| 855 break; | |
| 856 case Free: | |
| 857 x->next = xfree; | |
| 858 xfree = x; | |
| 859 break; | |
| 860 } | |
| 861 } | |
| 862 } | |
| 863 | |
| 864 /* this thread, in the main proc, allows fsysproc to get a window made w… | |
| 865 void | |
| 866 newwindowthread(void *v) | |
| 867 { | |
| 868 Window *w; | |
| 869 | |
| 870 USED(v); | |
| 871 threadsetname("newwindowthread"); | |
| 872 | |
| 873 for(;;){ | |
| 874 /* only fsysproc is talking to us, so synchronization is… | |
| 875 recvp(cnewwindow); | |
| 876 w = makenewwindow(nil); | |
| 877 winsettag(w); | |
| 878 xfidlog(w, "new"); | |
| 879 sendp(cnewwindow, w); | |
| 880 } | |
| 881 } | |
| 882 | |
| 883 Reffont* | |
| 884 rfget(int fix, int save, int setfont, char *name) | |
| 885 { | |
| 886 Reffont *r; | |
| 887 Font *f; | |
| 888 int i; | |
| 889 | |
| 890 r = nil; | |
| 891 if(name == nil){ | |
| 892 name = fontnames[fix]; | |
| 893 r = reffonts[fix]; | |
| 894 } | |
| 895 if(r == nil){ | |
| 896 for(i=0; i<nfontcache; i++) | |
| 897 if(strcmp(name, fontcache[i]->f->name) == 0){ | |
| 898 r = fontcache[i]; | |
| 899 goto Found; | |
| 900 } | |
| 901 f = openfont(display, name); | |
| 902 if(f == nil){ | |
| 903 warning(nil, "can't open font file %s: %r\n", na… | |
| 904 return nil; | |
| 905 } | |
| 906 r = emalloc(sizeof(Reffont)); | |
| 907 r->f = f; | |
| 908 fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Re… | |
| 909 fontcache[nfontcache++] = r; | |
| 910 } | |
| 911 Found: | |
| 912 if(save){ | |
| 913 incref(&r->ref); | |
| 914 if(reffonts[fix]) | |
| 915 rfclose(reffonts[fix]); | |
| 916 reffonts[fix] = r; | |
| 917 if(name != fontnames[fix]){ | |
| 918 free(fontnames[fix]); | |
| 919 fontnames[fix] = estrdup(name); | |
| 920 } | |
| 921 } | |
| 922 if(setfont){ | |
| 923 reffont.f = r->f; | |
| 924 incref(&r->ref); | |
| 925 rfclose(reffonts[0]); | |
| 926 font = r->f; | |
| 927 reffonts[0] = r; | |
| 928 incref(&r->ref); | |
| 929 iconinit(); | |
| 930 } | |
| 931 incref(&r->ref); | |
| 932 return r; | |
| 933 } | |
| 934 | |
| 935 void | |
| 936 rfclose(Reffont *r) | |
| 937 { | |
| 938 int i; | |
| 939 | |
| 940 if(decref(&r->ref) == 0){ | |
| 941 for(i=0; i<nfontcache; i++) | |
| 942 if(r == fontcache[i]) | |
| 943 break; | |
| 944 if(i >= nfontcache) | |
| 945 warning(nil, "internal error: can't find font in… | |
| 946 else{ | |
| 947 nfontcache--; | |
| 948 memmove(fontcache+i, fontcache+i+1, (nfontcache-… | |
| 949 } | |
| 950 freefont(r->f); | |
| 951 free(r); | |
| 952 } | |
| 953 } | |
| 954 | |
| 955 Cursor boxcursor = { | |
| 956 {-7, -7}, | |
| 957 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 958 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, | |
| 959 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF, | |
| 960 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, | |
| 961 {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, | |
| 962 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, | |
| 963 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, | |
| 964 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00} | |
| 965 }; | |
| 966 | |
| 967 Cursor2 boxcursor2 = { | |
| 968 {-15, -15}, | |
| 969 {0xFF, 0xFF, 0xFF, 0xFF, | |
| 970 0xFF, 0xFF, 0xFF, 0xFF, | |
| 971 0xFF, 0xFF, 0xFF, 0xFF, | |
| 972 0xFF, 0xFF, 0xFF, 0xFF, | |
| 973 0xFF, 0xFF, 0xFF, 0xFF, | |
| 974 0xFF, 0xFF, 0xFF, 0xFF, | |
| 975 0xFF, 0xFF, 0xFF, 0xFF, | |
| 976 0xFF, 0xFF, 0xFF, 0xFF, | |
| 977 0xFF, 0xFF, 0xFF, 0xFF, | |
| 978 0xFF, 0xFF, 0xFF, 0xFF, | |
| 979 0xFF, 0xC0, 0x03, 0xFF, | |
| 980 0xFF, 0xC0, 0x03, 0xFF, | |
| 981 0xFF, 0xC0, 0x03, 0xFF, | |
| 982 0xFF, 0xC0, 0x03, 0xFF, | |
| 983 0xFF, 0xC0, 0x03, 0xFF, | |
| 984 0xFF, 0xC0, 0x03, 0xFF, | |
| 985 0xFF, 0xC0, 0x03, 0xFF, | |
| 986 0xFF, 0xC0, 0x03, 0xFF, | |
| 987 0xFF, 0xC0, 0x03, 0xFF, | |
| 988 0xFF, 0xC0, 0x03, 0xFF, | |
| 989 0xFF, 0xC0, 0x03, 0xFF, | |
| 990 0xFF, 0xC0, 0x03, 0xFF, | |
| 991 0xFF, 0xFF, 0xFF, 0xFF, | |
| 992 0xFF, 0xFF, 0xFF, 0xFF, | |
| 993 0xFF, 0xFF, 0xFF, 0xFF, | |
| 994 0xFF, 0xFF, 0xFF, 0xFF, | |
| 995 0xFF, 0xFF, 0xFF, 0xFF, | |
| 996 0xFF, 0xFF, 0xFF, 0xFF, | |
| 997 0xFF, 0xFF, 0xFF, 0xFF, | |
| 998 0xFF, 0xFF, 0xFF, 0xFF, | |
| 999 0xFF, 0xFF, 0xFF, 0xFF, | |
| 1000 0xFF, 0xFF, 0xFF, 0xFF}, | |
| 1001 {0x00, 0x00, 0x00, 0x00, | |
| 1002 0x00, 0x00, 0x00, 0x00, | |
| 1003 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1004 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1005 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1006 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1007 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1008 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1009 0x3F, 0x00, 0x00, 0xFC, | |
| 1010 0x3F, 0x00, 0x00, 0xFC, | |
| 1011 0x3F, 0x00, 0x00, 0xFC, | |
| 1012 0x3F, 0x00, 0x00, 0xFC, | |
| 1013 0x3F, 0x00, 0x00, 0xFC, | |
| 1014 0x3F, 0x00, 0x00, 0xFC, | |
| 1015 0x3F, 0x00, 0x00, 0xFC, | |
| 1016 0x3F, 0x00, 0x00, 0xFC, | |
| 1017 0x3F, 0x00, 0x00, 0xFC, | |
| 1018 0x3F, 0x00, 0x00, 0xFC, | |
| 1019 0x3F, 0x00, 0x00, 0xFC, | |
| 1020 0x3F, 0x00, 0x00, 0xFC, | |
| 1021 0x3F, 0x00, 0x00, 0xFC, | |
| 1022 0x3F, 0x00, 0x00, 0xFC, | |
| 1023 0x3F, 0x00, 0x00, 0xFC, | |
| 1024 0x3F, 0x00, 0x00, 0xFC, | |
| 1025 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1026 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1027 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1028 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1029 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1030 0x3F, 0xFF, 0xFF, 0xFC, | |
| 1031 0x00, 0x00, 0x00, 0x00, | |
| 1032 0x00, 0x00, 0x00, 0x00} | |
| 1033 }; | |
| 1034 | |
| 1035 void | |
| 1036 iconinit(void) | |
| 1037 { | |
| 1038 Rectangle r; | |
| 1039 Image *tmp; | |
| 1040 | |
| 1041 if(tagcols[BACK] == nil) { | |
| 1042 /* Blue */ | |
| 1043 tagcols[BACK] = allocimagemix(display, DPalebluegreen, D… | |
| 1044 tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), scree… | |
| 1045 tagcols[BORD] = allocimage(display, Rect(0,0,1,1), scree… | |
| 1046 tagcols[TEXT] = display->black; | |
| 1047 tagcols[HTEXT] = display->black; | |
| 1048 | |
| 1049 /* Yellow */ | |
| 1050 textcols[BACK] = allocimagemix(display, DPaleyellow, DWh… | |
| 1051 textcols[HIGH] = allocimage(display, Rect(0,0,1,1), scre… | |
| 1052 textcols[BORD] = allocimage(display, Rect(0,0,1,1), scre… | |
| 1053 textcols[TEXT] = display->black; | |
| 1054 textcols[HTEXT] = display->black; | |
| 1055 } | |
| 1056 | |
| 1057 r = Rect(0, 0, Scrollwid+ButtonBorder, font->height+1); | |
| 1058 if(button && eqrect(r, button->r)) | |
| 1059 return; | |
| 1060 | |
| 1061 if(button){ | |
| 1062 freeimage(button); | |
| 1063 freeimage(modbutton); | |
| 1064 freeimage(colbutton); | |
| 1065 } | |
| 1066 | |
| 1067 button = allocimage(display, r, screen->chan, 0, DNofill); | |
| 1068 draw(button, r, tagcols[BACK], nil, r.min); | |
| 1069 r.max.x -= ButtonBorder; | |
| 1070 border(button, r, ButtonBorder, tagcols[BORD], ZP); | |
| 1071 | |
| 1072 r = button->r; | |
| 1073 modbutton = allocimage(display, r, screen->chan, 0, DNofill); | |
| 1074 draw(modbutton, r, tagcols[BACK], nil, r.min); | |
| 1075 r.max.x -= ButtonBorder; | |
| 1076 border(modbutton, r, ButtonBorder, tagcols[BORD], ZP); | |
| 1077 r = insetrect(r, ButtonBorder); | |
| 1078 tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedbl… | |
| 1079 draw(modbutton, r, tmp, nil, ZP); | |
| 1080 freeimage(tmp); | |
| 1081 | |
| 1082 r = button->r; | |
| 1083 colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue); | |
| 1084 | |
| 1085 but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF); | |
| 1086 but3col = allocimage(display, r, screen->chan, 1, 0x006600FF); | |
| 1087 } | |
| 1088 | |
| 1089 /* | |
| 1090 * /dev/snarf updates when the file is closed, so we must open our own | |
| 1091 * fd here rather than use snarffd | |
| 1092 */ | |
| 1093 | |
| 1094 /* rio truncates larges snarf buffers, so this avoids using the | |
| 1095 * service if the string is huge */ | |
| 1096 | |
| 1097 #define MAXSNARF 100*1024 | |
| 1098 | |
| 1099 void | |
| 1100 acmeputsnarf(void) | |
| 1101 { | |
| 1102 int i, n; | |
| 1103 Fmt f; | |
| 1104 char *s; | |
| 1105 | |
| 1106 if(snarfbuf.nc==0) | |
| 1107 return; | |
| 1108 if(snarfbuf.nc > MAXSNARF) | |
| 1109 return; | |
| 1110 | |
| 1111 fmtstrinit(&f); | |
| 1112 for(i=0; i<snarfbuf.nc; i+=n){ | |
| 1113 n = snarfbuf.nc-i; | |
| 1114 if(n >= NSnarf) | |
| 1115 n = NSnarf; | |
| 1116 bufread(&snarfbuf, i, snarfrune, n); | |
| 1117 if(fmtprint(&f, "%.*S", n, snarfrune) < 0) | |
| 1118 break; | |
| 1119 } | |
| 1120 s = fmtstrflush(&f); | |
| 1121 if(s && s[0]) | |
| 1122 putsnarf(s); | |
| 1123 free(s); | |
| 1124 } | |
| 1125 | |
| 1126 void | |
| 1127 acmegetsnarf(void) | |
| 1128 { | |
| 1129 char *s; | |
| 1130 int nb, nr, nulls, len; | |
| 1131 Rune *r; | |
| 1132 | |
| 1133 s = getsnarf(); | |
| 1134 if(s == nil || s[0]==0){ | |
| 1135 free(s); | |
| 1136 return; | |
| 1137 } | |
| 1138 | |
| 1139 len = strlen(s); | |
| 1140 r = runemalloc(len+1); | |
| 1141 cvttorunes(s, len, r, &nb, &nr, &nulls); | |
| 1142 bufreset(&snarfbuf); | |
| 1143 bufinsert(&snarfbuf, 0, r, nr); | |
| 1144 free(r); | |
| 1145 free(s); | |
| 1146 } | |
| 1147 | |
| 1148 int | |
| 1149 ismtpt(char *file) | |
| 1150 { | |
| 1151 int n; | |
| 1152 | |
| 1153 if(mtpt == nil) | |
| 1154 return 0; | |
| 1155 | |
| 1156 /* This is not foolproof, but it will stop a lot of them. */ | |
| 1157 n = strlen(mtpt); | |
| 1158 return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/… | |
| 1159 } | |
| 1160 | |
| 1161 int | |
| 1162 timefmt(Fmt *f) | |
| 1163 { | |
| 1164 Tm *tm; | |
| 1165 | |
| 1166 tm = localtime(va_arg(f->args, ulong)); | |
| 1167 return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d", | |
| 1168 tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, t… | |
| 1169 } |