1 module old; 2 import core.memory; 3 4 import core.sys.posix.signal; 5 import core.sys.posix.sys.wait; 6 import core.sys.posix.unistd; 7 8 import std.c.locale; 9 import std.c.string; 10 import std.c.stdlib; 11 12 import std.stdio; 13 import std.string; 14 import std.algorithm; 15 import std.conv; 16 import std.process; 17 import std.traits; 18 19 import deimos.X11.X; 20 import deimos.X11.Xlib; 21 import deimos.X11.keysymdef; 22 import deimos.X11.Xutil; 23 import deimos.X11.Xatom; 24 25 import types; 26 import kernel; 27 import legacy; 28 import utils; 29 import config; 30 import cboxapp; 31 import window; 32 import helper.x11; 33 import gui.cursor; 34 import gui.font; 35 import theme.layout; 36 import theme.manager; 37 import gui.bar; 38 import monitor; 39 40 void updatenumlockmask() 41 { 42 XModifierKeymap *modmap; 43 44 numlockmask = 0; 45 modmap = XGetModifierMapping(AppDisplay.instance().dpy); 46 foreach_reverse(i; 0..7) { 47 if(numlockmask == 0) { 48 break; 49 } 50 foreach_reverse(j; 0..modmap.max_keypermod-1) { 51 if(modmap.modifiermap[i * modmap.max_keypermod + j] == 52 XKeysymToKeycode(AppDisplay.instance().dpy, XK_Num_Lock)) { 53 numlockmask = (1 << i); 54 break; 55 } 56 } 57 } 58 XFreeModifiermap(modmap); 59 } 60 61 void updateclientlist() 62 { 63 64 Client *c; 65 Monitor *m; 66 67 XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetClientList]); 68 for(m = mons; m; m = m.next) 69 for(c = m.clients; c; c = c.next) 70 XChangeProperty(AppDisplay.instance().dpy, rootWin, netatom[NetClientList], 71 XA_WINDOW, 32, PropModeAppend, 72 cast(ubyte *)&(c.win), 1); 73 } 74 75 76 bool getrootptr(int *x, int *y) 77 { 78 int di; 79 uint dui; 80 Window dummy; 81 82 return XQueryPointer(AppDisplay.instance().dpy, rootWin, &dummy, &dummy, x, y, &di, &di, &dui) != 0; 83 } 84 85 void setfocus(Client *c) 86 { 87 88 if(!c.neverfocus) { 89 XSetInputFocus(AppDisplay.instance().dpy, c.win, RevertToPointerRoot, CurrentTime); 90 XChangeProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow], 91 XA_WINDOW, 32, PropModeReplace, 92 cast(ubyte *) &(c.win), 1); 93 } 94 sendevent(c, wmatom[WMTakeFocus]); 95 } 96 97 98 bool sendevent(Client *c, Atom proto) 99 { 100 int n; 101 Atom *protocols; 102 bool exists = false; 103 XEvent ev; 104 105 if(XGetWMProtocols(AppDisplay.instance().dpy, c.win, &protocols, &n)) { 106 while(!exists && n--) 107 exists = protocols[n] == proto; 108 XFree(protocols); 109 } 110 if(exists) { 111 ev.type = ClientMessage; 112 ev.xclient.window = c.win; 113 ev.xclient.message_type = wmatom[WMProtocols]; 114 ev.xclient.format = 32; 115 ev.xclient.data.l[0] = proto; 116 ev.xclient.data.l[1] = CurrentTime; 117 XSendEvent(AppDisplay.instance().dpy, c.win, false, NoEventMask, &ev); 118 } 119 return exists; 120 } 121 122 Client* nexttiled(Client *c) 123 { 124 return c.range!"next".find!(a => !a.isfloating && ISVISIBLE(a)).front; 125 } 126 127 void pop(Client *c) 128 { 129 detach(c); 130 attach(c); 131 focus(c); 132 arrange(c.mon); 133 } 134 135 void setfullscreen(Client *c, bool fullscreen) 136 { 137 if(fullscreen) { 138 XChangeProperty(AppDisplay.instance().dpy, c.win, netatom[NetWMState], XA_ATOM, 32, 139 PropModeReplace, cast(ubyte*)&netatom[NetWMFullscreen], 1); 140 c.isfullscreen = true; 141 c.oldstate = c.isfloating; 142 c.oldbw = c.bw; 143 c.bw = 0; 144 c.isfloating = true; 145 resizeclient(c, c.mon.mx, c.mon.my, c.mon.mw, c.mon.mh); 146 XRaiseWindow(AppDisplay.instance().dpy, c.win); 147 } else { 148 XChangeProperty(AppDisplay.instance().dpy, c.win, netatom[NetWMState], XA_ATOM, 32, 149 PropModeReplace, cast(ubyte*)0, 0); 150 c.isfullscreen = false; 151 c.isfloating = c.oldstate; 152 c.bw = c.oldbw; 153 c.x = c.oldx; 154 c.y = c.oldy; 155 c.w = c.oldw; 156 c.h = c.oldh; 157 resizeclient(c, c.x, c.y, c.w, c.h); 158 arrange(c.mon); 159 } 160 } 161 162 void resize(Client *c, int x, int y, int w, int h, bool interact) 163 { 164 if(applysizehints(c, x, y, w, h, interact)) 165 resizeclient(c, x, y, w, h); 166 } 167 168 void resizeclient(Client *c, int x, int y, int w, int h) 169 { 170 XWindowChanges wc; 171 172 c.oldx = c.x; 173 c.x = wc.x = x; 174 c.oldy = c.y; 175 c.y = wc.y = y; 176 c.oldw = c.w; 177 c.w = wc.width = w; 178 c.oldh = c.h; 179 c.h = wc.height = h; 180 wc.border_width = c.bw; 181 XConfigureWindow(AppDisplay.instance().dpy, c.win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); 182 configure(c); 183 XSync(AppDisplay.instance().dpy, false); 184 } 185 186 void sendmon(Client *c, Monitor *m) 187 { 188 if(c.mon == m) 189 return; 190 unfocus(c, true); 191 detach(c); 192 detachstack(c); 193 c.mon = m; 194 c.tags = m.tagset[m.seltags]; /* assign tags of target monitor */ 195 attach(c); 196 attachstack(c); 197 focus(null); 198 arrange(null); 199 } 200 201 202 bool applysizehints(Client *c, ref int x, ref int y, ref int w, ref int h, bool interact) 203 { 204 bool baseismin; 205 Monitor *m = c.mon; 206 207 /* set minimum possible */ 208 w = max(1, w); 209 h = max(1, h); 210 if(interact) { 211 if(x > sw) 212 x = sw - WIDTH(c); 213 if(y > sh) 214 y = sh - HEIGHT(c); 215 if(x + w + 2 * c.bw < 0) 216 x = 0; 217 if(y + h + 2 * c.bw < 0) 218 y = 0; 219 } else { 220 if(x >= m.wx + m.ww) 221 x = m.wx + m.ww - WIDTH(c); 222 if(y >= m.wy + m.wh) 223 y = m.wy + m.wh - HEIGHT(c); 224 if(x + w + 2 * c.bw <= m.wx) 225 x = m.wx; 226 if(y + h + 2 * c.bw <= m.wy) 227 y = m.wy; 228 } 229 if(h < bh) 230 h = bh; 231 if(w < bh) 232 w = bh; 233 if(resizehints || c.isfloating || !c.mon.lt[c.mon.sellt].arrange) { 234 /* see last two sentences in ICCCM 4.1.2.3 */ 235 baseismin = c.basew == c.minw && c.baseh == c.minh; 236 if(!baseismin) { /* temporarily remove base dimensions */ 237 w -= c.basew; 238 h -= c.baseh; 239 } 240 import std.math : 241 nearbyint; 242 /* adjust for aspect limits */ 243 if(c.mina > 0 && c.maxa > 0) { 244 if(c.maxa < float(w) / h) 245 w = cast(int)(h * c.maxa + 0.5); 246 else if(c.mina < float(h) / w) 247 h = cast(int)(w * c.mina + 0.5); 248 } 249 if(baseismin) { /* increment calculation requires this */ 250 w -= c.basew; 251 h -= c.baseh; 252 } 253 /* adjust for increment value */ 254 if(c.incw) 255 w -= w % c.incw; 256 if(c.inch) 257 h -= h % c.inch; 258 /* restore base dimensions */ 259 w = max(w + c.basew, c.minw); 260 h = max(h + c.baseh, c.minh); 261 if(c.maxw) 262 w = min(w, c.maxw); 263 if(c.maxh) 264 h = min(h, c.maxh); 265 } 266 return x != c.x || y != c.y || w != c.w || h != c.h; 267 } 268 269 void clientmessage(XEvent *e) 270 { 271 XClientMessageEvent *cme = &e.xclient; 272 Client *c = wintoclient(cme.window); 273 274 if(!c) 275 return; 276 if(cme.message_type == netatom[NetWMState]) { 277 if(cme.data.l[1] == netatom[NetWMFullscreen] || cme.data.l[2] == netatom[NetWMFullscreen]) { 278 setfullscreen(c, (cme.data.l[0] == 1 /* _NET_WM_STATE_ADD */ 279 || (cme.data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c.isfullscreen))); 280 } 281 } else if(cme.message_type == netatom[NetActiveWindow]) { 282 if(!ISVISIBLE(c)) { 283 c.mon.seltags ^= 1; 284 c.mon.tagset[c.mon.seltags] = c.tags; 285 } 286 pop(c); 287 } 288 } 289 290 void togglefloating(const Arg *arg) 291 { 292 if(!selmon.sel) 293 return; 294 if(selmon.sel.isfullscreen) /* no support for fullscreen windows */ 295 return; 296 selmon.sel.isfloating = !selmon.sel.isfloating || selmon.sel.isfixed; 297 if(selmon.sel.isfloating) 298 resize(selmon.sel, selmon.sel.x, selmon.sel.y, 299 selmon.sel.w, selmon.sel.h, false); 300 arrange(selmon); 301 } 302 303 void tag(const Arg *arg) { 304 305 if(selmon.sel && arg.ui & TAGMASK) { 306 selmon.sel.tags = arg.ui & TAGMASK; 307 focus(null); 308 arrange(selmon); 309 } 310 } 311 312 void tagmon(const Arg *arg) { 313 314 if(!selmon.sel || !mons.next) 315 return; 316 sendmon(selmon.sel, dirtomon(arg.i)); 317 } 318 319 void toggletag(const Arg *arg) { 320 321 uint newtags; 322 323 if(!selmon.sel) 324 return; 325 newtags = selmon.sel.tags ^ (arg.ui & TAGMASK); 326 if(newtags) { 327 selmon.sel.tags = newtags; 328 focus(null); 329 arrange(selmon); 330 } 331 } 332 333 void toggleview(const Arg *arg) { 334 335 uint newtagset = selmon.tagset[selmon.seltags] ^ (arg.ui & TAGMASK); 336 337 if(newtagset) { 338 selmon.tagset[selmon.seltags] = newtagset; 339 focus(null); 340 arrange(selmon); 341 } 342 } 343 344 void zoom(const Arg *arg) { 345 346 Client *c = selmon.sel; 347 348 if(!selmon.lt[selmon.sellt].arrange || 349 (selmon.sel && selmon.sel.isfloating)) { 350 return; 351 } 352 if(c == nexttiled(selmon.clients)) { 353 if(c) { 354 c = nexttiled(c.next); 355 } 356 if(!c) { 357 return; 358 } 359 } 360 pop(c); 361 } 362 363 364 void view(const Arg *arg) { 365 366 if((arg.ui & TAGMASK) == selmon.tagset[selmon.seltags]) { 367 return; 368 } 369 selmon.seltags ^= 1; /* toggle sel tagset */ 370 if(arg.ui & TAGMASK) { 371 selmon.tagset[selmon.seltags] = arg.ui & TAGMASK; 372 } 373 focus(null); 374 arrange(selmon); 375 } 376 377 void killclient(const Arg *arg) { 378 379 if(!selmon.sel) 380 return; 381 382 if(!sendevent(selmon.sel, wmatom[WMDelete])) { 383 XGrabServer(AppDisplay.instance().dpy); 384 XSetErrorHandler(&xerrordummy); 385 XSetCloseDownMode(AppDisplay.instance().dpy, CloseDownMode.DestroyAll); 386 XKillClient(AppDisplay.instance().dpy, selmon.sel.win); 387 XSync(AppDisplay.instance().dpy, false); 388 XSetErrorHandler(&xerror); 389 XUngrabServer(AppDisplay.instance().dpy); 390 } 391 } 392 393 void incnmaster(const Arg *arg) { 394 395 selmon.nmaster = max(selmon.nmaster + arg.i, 0); 396 arrange(selmon); 397 } 398 399 void setmfact(const Arg *arg) { 400 401 float f; 402 403 if(!arg || !selmon.lt[selmon.sellt].arrange) 404 return; 405 f = arg.f < 1.0 ? arg.f + selmon.mfact : arg.f - 1.0; 406 if(f < 0.1 || f > 0.9) 407 return; 408 selmon.mfact = f; 409 arrange(selmon); 410 } 411 412 413 bool updategeom() 414 { 415 416 bool dirty = false; 417 418 Bool isXineramaActive = false; 419 420 version(XINERAMA) { 421 isXineramaActive = XineramaIsActive(AppDisplay.instance().dpy); 422 } 423 424 if(isXineramaActive) { 425 version(XINERAMA) { 426 import std.range; 427 int nn; 428 Client *c; 429 XineramaScreenInfo *info = XineramaQueryScreens(AppDisplay.instance().dpy, &nn); 430 auto n = mons.range.walkLength; 431 432 XineramaScreenInfo[] unique = new XineramaScreenInfo[nn]; 433 if(!unique.length) { 434 die("fatal: could not malloc() %u bytes\n", XineramaScreenInfo.sizeof * nn); 435 } 436 437 /* only consider unique geometries as separate screens */ 438 int j=0; 439 foreach(i; 0..nn) { 440 if(isuniquegeom(&unique[j], j, &info[i])) { 441 unique[j++] = info[i]; 442 } 443 } 444 445 XFree(info); 446 nn = j; 447 448 if(n <= nn) { 449 foreach(i; 0..(nn-n)) { /* new monitors available */ 450 auto m = mons.range.find!"a.next is null".front; 451 if(m) { 452 m.next = createmon(); 453 } else { 454 mons = createmon(); 455 } 456 } 457 foreach(i, m; iota(nn).lockstep(mons.range)) { 458 if(i >= n || 459 (unique[i].x_org != m.mx || unique[i].y_org != m.my || 460 unique[i].width != m.mw || unique[i].height != m.mh)) { 461 dirty = true; 462 m.num = i; 463 m.mx = m.wx = unique[i].x_org; 464 m.my = m.wy = unique[i].y_org; 465 m.mw = m.ww = unique[i].width; 466 m.mh = m.wh = unique[i].height; 467 updatebarpos(m); 468 } 469 } 470 } else { /* less monitors available nn < n */ 471 foreach(i; nn..n) { 472 auto m = mons.range.find!"a.next is null".front; 473 if(m) { 474 while(m.clients) { 475 dirty = true; 476 c = m.clients; 477 m.clients = c.next; 478 detachstack(c); 479 c.mon = mons; 480 attach(c); 481 attachstack(c); 482 } 483 if(m == selmon) 484 selmon = mons; 485 cleanupmon(m); 486 } 487 } 488 } 489 unique = null; 490 } 491 } else { 492 /* default monitor setup */ 493 if(!mons) { 494 mons = createmon(); 495 } 496 497 if(mons.mw != sw || mons.mh != sh) { 498 dirty = true; 499 mons.mw = mons.ww = sw; 500 mons.mh = mons.wh = sh; 501 updatebarpos(mons); 502 } 503 } 504 if(dirty) { 505 selmon = mons; 506 selmon = wintomon(rootWin); 507 } 508 return dirty; 509 } 510 511 void updatewmhints(Client *c) { 512 513 XWMHints *wmh; 514 wmh = XGetWMHints(AppDisplay.instance().dpy, c.win); 515 if(wmh) { 516 if(c == selmon.sel && wmh.flags & XUrgencyHint) { 517 wmh.flags &= ~XUrgencyHint; 518 XSetWMHints(AppDisplay.instance().dpy, c.win, wmh); 519 } else 520 c.isurgent = (wmh.flags & XUrgencyHint) ? true : false; 521 if(wmh.flags & InputHint) 522 c.neverfocus = !wmh.input; 523 else 524 c.neverfocus = false; 525 XFree(wmh); 526 } 527 } 528 529 void setclientstate(Client *c, long state) { 530 531 long[] data = [ state, None ]; 532 533 XChangeProperty(AppDisplay.instance().dpy, c.win, wmatom[WMState], wmatom[WMState], 32, 534 PropModeReplace, cast(ubyte *)data, 2); 535 } 536 537 void configure(Client *c) { 538 539 XConfigureEvent ce; 540 541 ce.type = ConfigureNotify; 542 ce.display = AppDisplay.instance().dpy; 543 ce.event = c.win; 544 ce.window = c.win; 545 ce.x = c.x; 546 ce.y = c.y; 547 ce.width = c.w; 548 ce.height = c.h; 549 ce.border_width = c.bw; 550 ce.above = None; 551 ce.override_redirect = false; 552 XSendEvent(AppDisplay.instance().dpy, c.win, false, StructureNotifyMask, cast(XEvent *)&ce); 553 } 554 555 556 557 558 void updatesizehints(Client *c) { 559 560 long msize; 561 XSizeHints size; 562 563 if(!XGetWMNormalHints(AppDisplay.instance().dpy, c.win, &size, &msize)) { 564 /* size is uninitialized, ensure that size.flags aren't used */ 565 size.flags = PSize; 566 } 567 if(size.flags & PBaseSize) { 568 c.basew = size.base_width; 569 c.baseh = size.base_height; 570 } else if(size.flags & PMinSize) { 571 c.basew = size.min_width; 572 c.baseh = size.min_height; 573 } else { 574 c.basew = c.baseh = 0; 575 } 576 577 if(size.flags & PResizeInc) { 578 c.incw = size.width_inc; 579 c.inch = size.height_inc; 580 } else { 581 c.incw = c.inch = 0; 582 } 583 if(size.flags & PMaxSize) { 584 c.maxw = size.max_width; 585 c.maxh = size.max_height; 586 } else { 587 c.maxw = c.maxh = 0; 588 } 589 if(size.flags & PMinSize) { 590 c.minw = size.min_width; 591 c.minh = size.min_height; 592 } else if(size.flags & PBaseSize) { 593 c.minw = size.base_width; 594 c.minh = size.base_height; 595 } else { 596 c.minw = c.minh = 0; 597 } 598 if(size.flags & PAspect) { 599 c.mina = cast(float)size.min_aspect.y / size.min_aspect.x; 600 c.maxa = cast(float)size.max_aspect.x / size.max_aspect.y; 601 } else { 602 c.maxa = c.mina = 0.0; 603 } 604 c.isfixed = (c.maxw && c.minw && c.maxh && c.minh 605 && c.maxw == c.minw && c.maxh == c.minh); 606 } 607 608 void focus(Client *c) { 609 if(!c || !ISVISIBLE(c)) { 610 c = selmon.stack.range!"snext".find!(a => ISVISIBLE(a)).front; 611 } 612 /* was if(selmon.sel) */ 613 if(selmon.sel && selmon.sel != c) 614 unfocus(selmon.sel, false); 615 if(c) { 616 if(c.mon != selmon) 617 selmon = c.mon; 618 if(c.isurgent) 619 clearurgent(c); 620 detachstack(c); 621 attachstack(c); 622 mouseEventHandler.grabbuttons(c, true); 623 XSetWindowBorder(AppDisplay.instance().dpy, c.win, ThemeManager.instance().getScheme(SchemeSel).border.rgb); 624 setfocus(c); 625 } else { 626 XSetInputFocus(AppDisplay.instance().dpy, rootWin, RevertToPointerRoot, CurrentTime); 627 XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow]); 628 } 629 selmon.sel = c; 630 drawbars(); 631 } 632 633 void focusmon(const Arg *arg) { 634 635 Monitor *m; 636 637 if(mons && !mons.next) { 638 return; 639 } 640 m = dirtomon(arg.i); 641 if(m == selmon) { 642 return; 643 } 644 unfocus(selmon.sel, false); /* s/true/false/ fixes input focus issues 645 in gedit and anjuta */ 646 selmon = m; 647 focus(null); 648 } 649 650 void focusstack(const Arg *arg) { 651 652 Client *c = null, i; 653 654 if(!selmon.sel) 655 return; 656 if(arg.i > 0) { 657 c = selmon.sel.range!"next".find!(a => ISVISIBLE(a)).front; 658 if(!c) { 659 c = selmon.clients.range!"next".find!(a => ISVISIBLE(a)).front; 660 } 661 } else { 662 for(i = selmon.clients; i != selmon.sel; i = i.next) { 663 if(ISVISIBLE(i)) { 664 c = i; 665 } 666 } 667 if(!c) { 668 for(; i; i = i.next) { 669 if(ISVISIBLE(i)) { 670 c = i; 671 } 672 } 673 } 674 } 675 if(c) { 676 focus(c); 677 restack(selmon); 678 } 679 } 680 681 Client* wintoclient(Window w) { 682 683 foreach(m; mons.range) { 684 auto c = m.clients.range!"next".find!(client => client.win == w).front; 685 if(c) { 686 return c; 687 } 688 } 689 return null; 690 } 691 692 void unfocus(Client *c, bool setfocus) { 693 694 if(!c) 695 return; 696 mouseEventHandler.grabbuttons(c, false); 697 XSetWindowBorder(AppDisplay.instance().dpy, c.win, ThemeManager.instance().getScheme(SchemeNorm).border.rgb); 698 if(setfocus) { 699 XSetInputFocus(AppDisplay.instance().dpy, rootWin, RevertToPointerRoot, CurrentTime); 700 XDeleteProperty(AppDisplay.instance().dpy, rootWin, netatom[NetActiveWindow]); 701 } 702 } 703 704 705 706 void restack(Monitor *m) 707 { 708 XEvent ev; 709 XWindowChanges wc; 710 711 drawbar(m); 712 if(!m.sel) 713 return; 714 if(m.sel.isfloating || !m.lt[m.sellt].arrange) 715 XRaiseWindow(AppDisplay.instance().dpy, m.sel.win); 716 if(m.lt[m.sellt].arrange) { 717 wc.stack_mode = Below; 718 wc.sibling = m.barwin; 719 auto stacks = m.stack.range!"snext".filter!(a => !a.isfloating && ISVISIBLE(a)); 720 foreach(c; stacks) { 721 XConfigureWindow(AppDisplay.instance().dpy, c.win, CWSibling|CWStackMode, &wc); 722 wc.sibling = c.win; 723 } 724 } 725 XSync(AppDisplay.instance().dpy, false); 726 while(XCheckMaskEvent(AppDisplay.instance().dpy, EnterWindowMask, &ev)) {} 727 } 728 729 auto TEXTW(X)(auto ref in X x) 730 if(isSomeString!X) { 731 return drw.font.getexts_width(x) + drw.font.h; 732 } 733 734 void cleanupmon(Monitor *mon) 735 { 736 if(mon && mon == mons) { 737 mons = mons.next; 738 } else { 739 auto m = mons.range.find!(a => a.next == mon).front; 740 if(m) { 741 m.next = mon.next; 742 } 743 } 744 XUnmapWindow(AppDisplay.instance().dpy, mon.barwin); 745 XDestroyWindow(AppDisplay.instance().dpy, mon.barwin); 746 DGC.free(mon); 747 } 748 749 void clearurgent(Client *c) { 750 751 XWMHints *wmh; 752 753 c.isurgent = false; 754 wmh = XGetWMHints(AppDisplay.instance().dpy, c.win); 755 if(wmh is null) { 756 return; 757 } 758 wmh.flags &= ~XUrgencyHint; 759 XSetWMHints(AppDisplay.instance().dpy, c.win, wmh); 760 XFree(wmh); 761 } 762 763 void detachstack(Client *c) 764 { 765 Client **tc; 766 767 768 for(tc = &c.mon.stack; *tc && *tc != c; tc = &(*tc).snext) {} 769 *tc = c.snext; 770 771 if(c && c == c.mon.sel) { 772 auto t = c.mon.stack.range!"snext".find!(a=>ISVISIBLE(a)).front; 773 c.mon.sel = t; 774 } 775 } 776 777 void detach(Client *c) 778 { 779 Client **tc; 780 for(tc = &c.mon.clients; *tc && *tc != c; tc = &(*tc).next) {} 781 *tc = c.next; 782 } 783