nyxwm

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }