nyxwm.c (17183B)
1 #include <X11/Xlib.h> 2 #include <X11/XF86keysym.h> 3 #include <X11/keysym.h> 4 #include <X11/XKBlib.h> 5 #include <X11/Xproto.h> 6 #include <X11/Xft/Xft.h> 7 #include <stdlib.h> 8 #include <signal.h> 9 #include <unistd.h> 10 #include <stdio.h> 11 #include <pthread.h> 12 #include <sys/wait.h> 13 #include <sys/select.h> 14 #include <errno.h> 15 #include <time.h> 16 #include "nyxwm.h" 17 #include "config.h" 18 #include "nyxwmblocks.h" 19 20 #define DEBUG_LOG(msg, ...) do { \ 21 time_t now = time(NULL); \ 22 char timestr[20]; \ 23 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); \ 24 fprintf(stderr, "[%s] DEBUG: " msg "\n", timestr, ##__VA_ARGS__); \ 25 } while(0) 26 27 #define ERROR_LOG(msg, ...) do { \ 28 time_t now = time(NULL); \ 29 char timestr[20]; \ 30 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); \ 31 fprintf(stderr, "[%s] ERROR: " msg ": %s\n", timestr, ##__VA_ARGS__, strerror(errno)); \ 32 } while(0) 33 34 Display *d; 35 Window root; 36 Window bar; 37 XftFont *xft_font; 38 XftColor xft_color; 39 XftDraw *xft_draw; 40 Window systray; 41 Window systray_icons[MAX_SYSTRAY_ICONS]; 42 Atom xembed_atom; 43 Atom manager_atom; 44 Atom system_tray_opcode_atom; 45 Atom system_tray_selection_atom; 46 47 static client *list = {0}, *ws_list[10] = {0}, *cur; 48 static int ws = 1, sw, sh, wx, wy, numlock = 0; 49 static unsigned int ww, wh; 50 static int s; 51 static XButtonEvent mouse; 52 int num_systray_icons; 53 54 static void (*events[LASTEvent])(XEvent *e) = { 55 [ButtonPress] = button_press, 56 [ButtonRelease] = button_release, 57 [ConfigureRequest] = configure_request, 58 [KeyPress] = key_press, 59 [MapRequest] = map_request, 60 [MappingNotify] = mapping_notify, 61 [DestroyNotify] = notify_destroy, 62 [EnterNotify] = notify_enter, 63 [MotionNotify] = notify_motion 64 }; 65 66 unsigned long getcolor(const char *col) { 67 Colormap m = DefaultColormap(d, s); 68 XColor c; 69 return (!XAllocNamedColor(d, m, col, &c, &c))?0:c.pixel; 70 } 71 72 void runAutoStart(void) { 73 system("cd ~/.nyxwm; ./autostart.sh &"); 74 } 75 76 void win_focus(client *c) { 77 cur = c; 78 XSetInputFocus(d, cur->w, RevertToParent, CurrentTime); 79 } 80 81 void notify_destroy(XEvent *e) { 82 win_del(e->xdestroywindow.window); 83 84 if (list) win_focus(list->prev); 85 } 86 87 void notify_enter(XEvent *e) { 88 while(XCheckTypedEvent(d, EnterNotify, e)); 89 while(XCheckTypedWindowEvent(d, mouse.subwindow, MotionNotify, e)); 90 91 for win if (c->w == e->xcrossing.window) win_focus(c); 92 } 93 94 void notify_motion(XEvent *e) { 95 if (!mouse.subwindow || cur->f) return; 96 97 while(XCheckTypedEvent(d, MotionNotify, e)); 98 99 int xd = e->xbutton.x_root - mouse.x_root; 100 int yd = e->xbutton.y_root - mouse.y_root; 101 102 XMoveResizeWindow(d, mouse.subwindow, 103 wx + (mouse.button == 1 ? xd : 0), 104 wy + (mouse.button == 1 ? yd : 0), 105 MAX(1, ww + (mouse.button == 3 ? xd : 0)), 106 MAX(1, wh + (mouse.button == 3 ? yd : 0))); 107 } 108 109 void key_press(XEvent *e) { 110 KeySym keysym = XkbKeycodeToKeysym(d, e->xkey.keycode, 0, 0); 111 112 for (unsigned int i=0; i < sizeof(keys)/sizeof(*keys); ++i) 113 if (keys[i].keysym == keysym && 114 mod_clean(keys[i].mod) == mod_clean(e->xkey.state)) 115 keys[i].function(keys[i].arg); 116 } 117 118 void button_press(XEvent *e) { 119 if (!e->xbutton.subwindow) return; 120 121 win_size(e->xbutton.subwindow, &wx, &wy, &ww, &wh); 122 XRaiseWindow(d, e->xbutton.subwindow); 123 mouse = e->xbutton; 124 } 125 126 void button_release(XEvent *e) { 127 mouse.subwindow = 0; 128 } 129 130 void win_add(Window w) { 131 client *c; 132 133 if (!(c = (client *) calloc(1, sizeof(client)))) 134 exit(1); 135 136 c->w = w; 137 138 if (list) { 139 list->prev->next = c; 140 c->prev = list->prev; 141 list->prev = c; 142 c->next = list; 143 144 } else { 145 list = c; 146 list->prev = list->next = list; 147 } 148 149 ws_save(ws); 150 } 151 152 void win_del(Window w) { 153 client *x = 0; 154 155 for win if (c->w == w) x = c; 156 157 if (!list || !x) return; 158 if (x->prev == x) list = 0; 159 if (list == x) list = x->next; 160 if (x->next) x->next->prev = x->prev; 161 if (x->prev) x->prev->next = x->next; 162 163 free(x); 164 ws_save(ws); 165 } 166 167 void win_kill(const Arg arg) { 168 if (cur) XKillClient(d, cur->w); 169 } 170 171 void win_center(const Arg arg) { 172 if (!cur) return; 173 174 win_size(cur->w, &(int){0}, &(int){0}, &ww, &wh); 175 XMoveWindow(d, cur->w, (sw - ww) / 2, ((sh - BAR_HEIGHT) - wh) / 2 + BAR_HEIGHT); 176 } 177 178 179 void win_fs(const Arg arg) { 180 if (!cur) return; 181 182 if ((cur->f = cur->f ? 0 : 1)) { 183 // Going fullscreen 184 win_size(cur->w, &cur->wx, &cur->wy, &cur->ww, &cur->wh); 185 XMoveResizeWindow(d, cur->w, 0, BAR_HEIGHT, sw, sh - BAR_HEIGHT); 186 XRaiseWindow(d, cur->w); 187 } else { 188 // Exiting fullscreen 189 XMoveResizeWindow(d, cur->w, cur->wx, cur->wy, cur->ww, cur->wh); 190 } 191 update_systray(); 192 update_bar(); 193 } 194 195 void win_to_ws(const Arg arg) { 196 int tmp = ws; 197 198 if (arg.i == tmp) return; 199 200 ws_sel(arg.i); 201 win_add(cur->w); 202 ws_save(arg.i); 203 204 ws_sel(tmp); 205 win_del(cur->w); 206 XUnmapWindow(d, cur->w); 207 ws_save(tmp); 208 209 if (list) win_focus(list); 210 } 211 212 void win_prev(const Arg arg) { 213 if (!cur) return; 214 215 XRaiseWindow(d, cur->prev->w); 216 win_focus(cur->prev); 217 } 218 219 void win_next(const Arg arg) { 220 if (!cur) return; 221 222 XRaiseWindow(d, cur->next->w); 223 win_focus(cur->next); 224 } 225 226 void ws_go(const Arg arg) { 227 int tmp = ws; 228 229 if (arg.i == ws) return; 230 231 ws_save(ws); 232 ws_sel(arg.i); 233 234 for win XMapWindow(d, c->w); 235 236 ws_sel(tmp); 237 238 for win XUnmapWindow(d, c->w); 239 240 ws_sel(arg.i); 241 242 if (list) win_focus(list); else cur = 0; 243 } 244 245 void configure_request(XEvent *e) { 246 XConfigureRequestEvent *ev = &e->xconfigurerequest; 247 248 XConfigureWindow(d, ev->window, ev->value_mask, &(XWindowChanges) { 249 .x = ev->x, 250 .y = ev->y, 251 .width = ev->width, 252 .height = ev->height, 253 .sibling = ev->above, 254 .stack_mode = ev->detail 255 }); 256 } 257 258 void map_request(XEvent *e) { 259 Window w = e->xmaprequest.window; 260 261 XSelectInput(d, w, StructureNotifyMask|EnterWindowMask); 262 win_size(w, &wx, &wy, &ww, &wh); 263 win_add(w); 264 cur = list->prev; 265 XSetWindowBorder(d, w, getcolor(BORDER_COLOR)); 266 XConfigureWindow(d, w, CWBorderWidth, &(XWindowChanges){.border_width = BORDER_WIDTH}); 267 268 if (wx + wy == 0) win_center((Arg){0}); 269 270 XMapWindow(d, w); 271 win_focus(list->prev); 272 } 273 274 void mapping_notify(XEvent *e) { 275 XMappingEvent *ev = &e->xmapping; 276 277 if (ev->request == MappingKeyboard || ev->request == MappingModifier) { 278 XRefreshKeyboardMapping(ev); 279 input_grab(root); 280 } 281 } 282 283 void run(const Arg arg) { 284 if (fork() == 0) { 285 if (d) { 286 close(ConnectionNumber(d)); 287 } 288 setsid(); 289 execvp((char*)arg.com[0], (char**)arg.com); 290 fprintf(stderr, "nyxwm: execvp %s", ((char **)arg.com)[0]); 291 perror(" failed"); 292 exit(0); 293 } 294 } 295 296 void input_grab(Window root) { 297 unsigned int i, j, modifiers[] = {0, LockMask, numlock, numlock|LockMask}; 298 XModifierKeymap *modmap = XGetModifierMapping(d); 299 KeyCode code; 300 301 for (i = 0; i < 8; i++) 302 for (int k = 0; k < modmap->max_keypermod; k++) 303 if (modmap->modifiermap[i * modmap->max_keypermod + k] 304 == XKeysymToKeycode(d, 0xff7f)) 305 numlock = (1 << i); 306 307 XUngrabKey(d, AnyKey, AnyModifier, root); 308 309 for (i = 0; i < sizeof(keys)/sizeof(*keys); i++) 310 if ((code = XKeysymToKeycode(d, keys[i].keysym))) 311 for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++) 312 XGrabKey(d, code, keys[i].mod | modifiers[j], root, 313 True, GrabModeAsync, GrabModeAsync); 314 315 for (i = 1; i < 4; i += 2) 316 for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++) 317 XGrabButton(d, i, MOD | modifiers[j], root, True, 318 ButtonPressMask|ButtonReleaseMask|PointerMotionMask, 319 GrabModeAsync, GrabModeAsync, 0, 0); 320 321 XFreeModifiermap(modmap); 322 } 323 324 void create_bar() { 325 XSetWindowAttributes attr = { 326 .override_redirect = True, 327 .background_pixel = getcolor(BAR_COLOR) 328 }; 329 330 int bar_width = sw - 2 * TRAY_PADDING; // Adjust if needed 331 332 bar = XCreateWindow(d, root, TRAY_PADDING, 0, bar_width, BAR_HEIGHT, 0, 333 DefaultDepth(d, s), CopyFromParent, 334 DefaultVisual(d, s), 335 CWOverrideRedirect | CWBackPixel, &attr);; 336 337 XMapWindow(d, bar); 338 339 xft_font = XftFontOpenName(d, s, FONT); // Use xft_font instead of font 340 XftColorAllocName(d, DefaultVisual(d, s), DefaultColormap(d, s), TEXT_COLOR, &xft_color); 341 xft_draw = XftDrawCreate(d, bar, DefaultVisual(d, s), DefaultColormap(d, s)); 342 } 343 344 void draw_text(const char *text, int x, int y) { 345 XftDrawStringUtf8(xft_draw, &xft_color, xft_font, x, y, (XftChar8*)text, strlen(text)); 346 } 347 348 void create_systray() { 349 XSetWindowAttributes attr; 350 attr.override_redirect = True; 351 attr.background_pixel = getcolor(BAR_COLOR); 352 systray = XCreateWindow(d, root, sw - TRAY_PADDING, 0, TRAY_PADDING * 2, BAR_HEIGHT, 0, 353 DefaultDepth(d, s), CopyFromParent, 354 DefaultVisual(d, s), 355 CWOverrideRedirect | CWBackPixel, &attr); 356 XMapWindow(d, systray); 357 358 xembed_atom = XInternAtom(d, "_XEMBED", False); 359 manager_atom = XInternAtom(d, "MANAGER", False); 360 system_tray_opcode_atom = XInternAtom(d, "_NET_SYSTEM_TRAY_OPCODE", False); 361 system_tray_selection_atom = XInternAtom(d, "_NET_SYSTEM_TRAY_S0", False); 362 363 XSetSelectionOwner(d, system_tray_selection_atom, systray, CurrentTime); 364 if (XGetSelectionOwner(d, system_tray_selection_atom) == systray) { 365 XClientMessageEvent cm; 366 cm.type = ClientMessage; 367 cm.window = root; 368 cm.message_type = manager_atom; 369 cm.format = 32; 370 cm.data.l[0] = CurrentTime; 371 cm.data.l[1] = system_tray_selection_atom; 372 cm.data.l[2] = systray; 373 cm.data.l[3] = 0; 374 cm.data.l[4] = 0; 375 XSendEvent(d, root, False, StructureNotifyMask, (XEvent *)&cm); 376 printf("Systray created and manager message sent\n"); 377 } else { 378 printf("Failed to acquire selection ownership for systray\n"); 379 } 380 } 381 382 void update_systray() { 383 int tray_width = num_systray_icons * (TRAY_ICON_SIZE + TRAY_ICON_SPACING) + TRAY_PADDING * 2; 384 if (num_systray_icons > 0) { 385 tray_width -= TRAY_ICON_SPACING; 386 387 XMapWindow(d, systray); 388 XMoveResizeWindow(d, systray, 389 sw - tray_width, 0, 390 tray_width, BAR_HEIGHT); 391 392 for (int i = 0; i < num_systray_icons; i++) { 393 XMoveResizeWindow(d, systray_icons[i], 394 TRAY_PADDING + i * (TRAY_ICON_SIZE + TRAY_ICON_SPACING), 395 (BAR_HEIGHT - TRAY_ICON_SIZE) / 2, 396 TRAY_ICON_SIZE, TRAY_ICON_SIZE); 397 XMapWindow(d, systray_icons[i]); 398 } 399 } else { 400 XUnmapWindow(d, systray); 401 tray_width = 0; 402 } 403 404 // Adjust the bar size to account for the systray 405 int bar_width = sw - tray_width; 406 XMoveResizeWindow(d, bar, 0, 0, bar_width, BAR_HEIGHT); 407 408 // Raise the bar and systray to ensure they're visible 409 XRaiseWindow(d, bar); 410 if (num_systray_icons > 0) { 411 XRaiseWindow(d, systray); 412 } 413 414 update_bar(); 415 } 416 417 void handle_systray_request(XClientMessageEvent *cme) { 418 if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { 419 Window icon = cme->data.l[2]; 420 if (num_systray_icons < MAX_SYSTRAY_ICONS) { 421 XWindowAttributes wa; 422 if (XGetWindowAttributes(d, icon, &wa)) { 423 systray_icons[num_systray_icons++] = icon; 424 XReparentWindow(d, icon, systray, 0, 0); 425 XMapRaised(d, icon); 426 427 XEvent ev; 428 ev.xclient.type = ClientMessage; 429 ev.xclient.window = icon; 430 ev.xclient.message_type = xembed_atom; 431 ev.xclient.format = 32; 432 ev.xclient.data.l[0] = CurrentTime; 433 ev.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; 434 ev.xclient.data.l[2] = 0; 435 ev.xclient.data.l[3] = systray; 436 ev.xclient.data.l[4] = 0; 437 XSendEvent(d, icon, False, NoEventMask, &ev); 438 439 update_systray(); 440 DEBUG_LOG("Icon docked: %ld", icon); 441 } else { 442 DEBUG_LOG("Failed to get window attributes for icon: %ld", icon); 443 } 444 } else { 445 DEBUG_LOG("Maximum number of systray icons reached"); 446 } 447 } else { 448 DEBUG_LOG("Received unknown systray request: %ld", cme->data.l[1]); 449 } 450 } 451 452 void update_bar() { 453 char status[256]; 454 run_nyxwmblocks(status, sizeof(status)); 455 456 XClearWindow(d, bar); 457 458 int bar_width = sw - (num_systray_icons > 0 ? (num_systray_icons * (TRAY_ICON_SIZE + TRAY_ICON_SPACING) + TRAY_PADDING * 2 - TRAY_ICON_SPACING) : 0); 459 460 XGlyphInfo extents; 461 XftTextExtentsUtf8(d, xft_font, (XftChar8*)status, strlen(status), &extents); 462 463 int x = (bar_width - extents.width) / 2; 464 int y = BAR_HEIGHT / 2 + xft_font->ascent / 2; 465 466 x = (x < 10) ? 10 : x; 467 468 XftDrawStringUtf8(xft_draw, &xft_color, xft_font, x, y, (XftChar8*)status, strlen(status)); 469 XFlush(d); 470 } 471 472 int xerror(Display *dpy, XErrorEvent *ee) { 473 if (ee->error_code == BadAccess && 474 ee->request_code == X_ChangeWindowAttributes) { 475 ERROR_LOG("Another window manager is already running"); 476 exit(1); 477 } 478 479 char error_text[1024]; 480 XGetErrorText(dpy, ee->error_code, error_text, sizeof(error_text)); 481 ERROR_LOG("XError: request_code=%d, error_code=%d, error_text=%s", 482 ee->request_code, ee->error_code, error_text); 483 484 return 0; 485 } 486 487 void handle_destroy_notify(XDestroyWindowEvent *ev) { 488 for (int i = 0; i < num_systray_icons; i++) { 489 if (systray_icons[i] == ev->window) { 490 for (int j = i; j < num_systray_icons - 1; j++) { 491 systray_icons[j] = systray_icons[j+1]; 492 } 493 num_systray_icons--; 494 update_systray(); 495 break; 496 } 497 } 498 } 499 500 int main(void) { 501 DEBUG_LOG("Starting nyxwm"); 502 503 if (!(d = XOpenDisplay(NULL))) { 504 ERROR_LOG("Cannot open display"); 505 return 1; 506 } 507 DEBUG_LOG("Display opened successfully"); 508 509 signal(SIGCHLD, SIG_IGN); 510 int (*prev_error_handler)(Display *, XErrorEvent *); 511 prev_error_handler = XSetErrorHandler(xerror); 512 513 s = DefaultScreen(d); 514 root = RootWindow(d, s); 515 sw = DisplayWidth(d, s); 516 sh = DisplayHeight(d, s); 517 518 DEBUG_LOG("Screen info: s=%d, root=%lu, sw=%d, sh=%d", s, root, sw, sh); 519 520 if (XSelectInput(d, root, SubstructureRedirectMask | SubstructureNotifyMask) == BadWindow) { 521 ERROR_LOG("Failed to select input on root window"); 522 return 1; 523 } 524 DEBUG_LOG("Input selected on root window"); 525 526 XDefineCursor(d, root, XCreateFontCursor(d, 68)); 527 DEBUG_LOG("Cursor defined"); 528 529 input_grab(root); 530 DEBUG_LOG("Input grabbed"); 531 532 create_bar(); 533 DEBUG_LOG("Bar created"); 534 535 create_systray(); 536 DEBUG_LOG("Systray created"); 537 538 runAutoStart(); 539 DEBUG_LOG("Autostart run"); 540 541 XEvent ev; 542 struct timeval tv, last_update; 543 fd_set fds; 544 int xfd = ConnectionNumber(d); 545 546 gettimeofday(&last_update, NULL); 547 548 DEBUG_LOG("Entering main loop"); 549 550 while (1) { 551 while (XPending(d)) { 552 XNextEvent(d, &ev); 553 DEBUG_LOG("Received event: type=%d", ev.type); 554 555 if (events[ev.type]) { 556 events[ev.type](&ev); 557 } 558 559 if (ev.type == ClientMessage && ev.xclient.message_type == system_tray_opcode_atom) { 560 handle_systray_request(&ev.xclient); 561 } else if (ev.type == DestroyNotify) { 562 handle_destroy_notify(&ev.xdestroywindow); 563 update_systray(); 564 } else if (ev.type == MapNotify || ev.type == UnmapNotify) { 565 update_systray(); 566 } 567 } 568 569 FD_ZERO(&fds); 570 FD_SET(xfd, &fds); 571 tv.tv_usec = 100000; // 100ms timeout 572 tv.tv_sec = 0; 573 574 int ready = select(xfd + 1, &fds, 0, 0, &tv); 575 if (ready == -1) { 576 if (errno != EINTR) { 577 ERROR_LOG("select() failed"); 578 } 579 } else if (ready == 0) { 580 // Timeout occurred, check if it's time to update 581 struct timeval now; 582 gettimeofday(&now, NULL); 583 if ((now.tv_sec - last_update.tv_sec) * 1000000 + (now.tv_usec - last_update.tv_usec) >= 1000000) { 584 // Update every second 585 update_bar(); 586 update_systray(); 587 last_update = now; 588 } 589 } 590 } 591 592 // This part will likely never be reached 593 DEBUG_LOG("Exiting main loop"); 594 XSetErrorHandler(prev_error_handler); 595 XCloseDisplay(d); 596 DEBUG_LOG("Display closed"); 597 598 return 0; 599 }