nyxwm
Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/nyxwm.git
nyxwm.c
#include <X11/Xlib.h>
#include <X11/XF86keysym.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xproto.h>
#include <X11/Xft/Xft.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <errno.h>
#include <time.h>
#include "nyxwm.h"
#include "config.h"
#include "nyxwmblocks.h"
#define DEBUG_LOG(msg, ...) do { \
time_t now = time(NULL); \
char timestr[20]; \
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); \
fprintf(stderr, "[%s] DEBUG: " msg "\n", timestr, ##__VA_ARGS__); \
} while(0)
#define ERROR_LOG(msg, ...) do { \
time_t now = time(NULL); \
char timestr[20]; \
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); \
fprintf(stderr, "[%s] ERROR: " msg ": %s\n", timestr, ##__VA_ARGS__, strerror(errno)); \
} while(0)
Display *d;
Window root;
Window bar;
XftFont *xft_font;
XftColor xft_color;
XftDraw *xft_draw;
Window systray;
Window systray_icons[MAX_SYSTRAY_ICONS];
Atom xembed_atom;
Atom manager_atom;
Atom system_tray_opcode_atom;
Atom system_tray_selection_atom;
static client *list = {0}, *ws_list[10] = {0}, *cur;
static int ws = 1, sw, sh, wx, wy, numlock = 0;
static unsigned int ww, wh;
static int s;
static XButtonEvent mouse;
int num_systray_icons;
static void (*events[LASTEvent])(XEvent *e) = {
[ButtonPress] = button_press,
[ButtonRelease] = button_release,
[ConfigureRequest] = configure_request,
[KeyPress] = key_press,
[MapRequest] = map_request,
[MappingNotify] = mapping_notify,
[DestroyNotify] = notify_destroy,
[EnterNotify] = notify_enter,
[MotionNotify] = notify_motion
};
unsigned long getcolor(const char *col) {
Colormap m = DefaultColormap(d, s);
XColor c;
return (!XAllocNamedColor(d, m, col, &c, &c))?0:c.pixel;
}
void runAutoStart(void) {
system("cd ~/.nyxwm; ./autostart.sh &");
}
void win_focus(client *c) {
cur = c;
XSetInputFocus(d, cur->w, RevertToParent, CurrentTime);
}
void notify_destroy(XEvent *e) {
win_del(e->xdestroywindow.window);
if (list) win_focus(list->prev);
}
void notify_enter(XEvent *e) {
while(XCheckTypedEvent(d, EnterNotify, e));
while(XCheckTypedWindowEvent(d, mouse.subwindow, MotionNotify, e));
for win if (c->w == e->xcrossing.window) win_focus(c);
}
void notify_motion(XEvent *e) {
if (!mouse.subwindow || cur->f) return;
while(XCheckTypedEvent(d, MotionNotify, e));
int xd = e->xbutton.x_root - mouse.x_root;
int yd = e->xbutton.y_root - mouse.y_root;
XMoveResizeWindow(d, mouse.subwindow,
wx + (mouse.button == 1 ? xd : 0),
wy + (mouse.button == 1 ? yd : 0),
MAX(1, ww + (mouse.button == 3 ? xd : 0)),
MAX(1, wh + (mouse.button == 3 ? yd : 0)));
}
void key_press(XEvent *e) {
KeySym keysym = XkbKeycodeToKeysym(d, e->xkey.keycode, 0, 0);
for (unsigned int i=0; i < sizeof(keys)/sizeof(*keys); ++i)
if (keys[i].keysym == keysym &&
mod_clean(keys[i].mod) == mod_clean(e->xkey.state))
keys[i].function(keys[i].arg);
}
void button_press(XEvent *e) {
if (!e->xbutton.subwindow) return;
win_size(e->xbutton.subwindow, &wx, &wy, &ww, &wh);
XRaiseWindow(d, e->xbutton.subwindow);
mouse = e->xbutton;
}
void button_release(XEvent *e) {
mouse.subwindow = 0;
}
void win_add(Window w) {
client *c;
if (!(c = (client *) calloc(1, sizeof(client))))
exit(1);
c->w = w;
if (list) {
list->prev->next = c;
c->prev = list->prev;
list->prev = c;
c->next = list;
} else {
list = c;
list->prev = list->next = list;
}
ws_save(ws);
}
void win_del(Window w) {
client *x = 0;
for win if (c->w == w) x = c;
if (!list || !x) return;
if (x->prev == x) list = 0;
if (list == x) list = x->next;
if (x->next) x->next->prev = x->prev;
if (x->prev) x->prev->next = x->next;
free(x);
ws_save(ws);
}
void win_kill(const Arg arg) {
if (cur) XKillClient(d, cur->w);
}
void win_center(const Arg arg) {
if (!cur) return;
win_size(cur->w, &(int){0}, &(int){0}, &ww, &wh);
XMoveWindow(d, cur->w, (sw - ww) / 2, ((sh - BAR_HEIGHT) - wh) / 2 + BAR_HEIGHT);
}
void win_fs(const Arg arg) {
if (!cur) return;
if ((cur->f = cur->f ? 0 : 1)) {
// Going fullscreen
win_size(cur->w, &cur->wx, &cur->wy, &cur->ww, &cur->wh);
XMoveResizeWindow(d, cur->w, 0, BAR_HEIGHT, sw, sh - BAR_HEIGHT);
XRaiseWindow(d, cur->w);
} else {
// Exiting fullscreen
XMoveResizeWindow(d, cur->w, cur->wx, cur->wy, cur->ww, cur->wh);
}
update_systray();
update_bar();
}
void win_to_ws(const Arg arg) {
int tmp = ws;
if (arg.i == tmp) return;
ws_sel(arg.i);
win_add(cur->w);
ws_save(arg.i);
ws_sel(tmp);
win_del(cur->w);
XUnmapWindow(d, cur->w);
ws_save(tmp);
if (list) win_focus(list);
}
void win_prev(const Arg arg) {
if (!cur) return;
XRaiseWindow(d, cur->prev->w);
win_focus(cur->prev);
}
void win_next(const Arg arg) {
if (!cur) return;
XRaiseWindow(d, cur->next->w);
win_focus(cur->next);
}
void ws_go(const Arg arg) {
int tmp = ws;
if (arg.i == ws) return;
ws_save(ws);
ws_sel(arg.i);
for win XMapWindow(d, c->w);
ws_sel(tmp);
for win XUnmapWindow(d, c->w);
ws_sel(arg.i);
if (list) win_focus(list); else cur = 0;
}
void configure_request(XEvent *e) {
XConfigureRequestEvent *ev = &e->xconfigurerequest;
XConfigureWindow(d, ev->window, ev->value_mask, &(XWindowChanges) {
.x = ev->x,
.y = ev->y,
.width = ev->width,
.height = ev->height,
.sibling = ev->above,
.stack_mode = ev->detail
});
}
void map_request(XEvent *e) {
Window w = e->xmaprequest.window;
XSelectInput(d, w, StructureNotifyMask|EnterWindowMask);
win_size(w, &wx, &wy, &ww, &wh);
win_add(w);
cur = list->prev;
XSetWindowBorder(d, w, getcolor(BORDER_COLOR));
XConfigureWindow(d, w, CWBorderWidth, &(XWindowChanges){.border_width = BORDER_WIDTH});
if (wx + wy == 0) win_center((Arg){0});
XMapWindow(d, w);
win_focus(list->prev);
}
void mapping_notify(XEvent *e) {
XMappingEvent *ev = &e->xmapping;
if (ev->request == MappingKeyboard || ev->request == MappingModifier) {
XRefreshKeyboardMapping(ev);
input_grab(root);
}
}
void run(const Arg arg) {
if (fork() == 0) {
if (d) {
close(ConnectionNumber(d));
}
setsid();
execvp((char*)arg.com[0], (char**)arg.com);
fprintf(stderr, "nyxwm: execvp %s", ((char **)arg.com)[0]);
perror(" failed");
exit(0);
}
}
void input_grab(Window root) {
unsigned int i, j, modifiers[] = {0, LockMask, numlock, numlock|LockMask};
XModifierKeymap *modmap = XGetModifierMapping(d);
KeyCode code;
for (i = 0; i < 8; i++)
for (int k = 0; k < modmap->max_keypermod; k++)
if (modmap->modifiermap[i * modmap->max_keypermod + k]
== XKeysymToKeycode(d, 0xff7f))
numlock = (1 << i);
XUngrabKey(d, AnyKey, AnyModifier, root);
for (i = 0; i < sizeof(keys)/sizeof(*keys); i++)
if ((code = XKeysymToKeycode(d, keys[i].keysym)))
for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++)
XGrabKey(d, code, keys[i].mod | modifiers[j], root,
True, GrabModeAsync, GrabModeAsync);
for (i = 1; i < 4; i += 2)
for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++)
XGrabButton(d, i, MOD | modifiers[j], root, True,
ButtonPressMask|ButtonReleaseMask|PointerMotionMask,
GrabModeAsync, GrabModeAsync, 0, 0);
XFreeModifiermap(modmap);
}
void create_bar() {
XSetWindowAttributes attr = {
.override_redirect = True,
.background_pixel = getcolor(BAR_COLOR)
};
int bar_width = sw - 2 * TRAY_PADDING; // Adjust if needed
bar = XCreateWindow(d, root, TRAY_PADDING, 0, bar_width, BAR_HEIGHT, 0,
DefaultDepth(d, s), CopyFromParent,
DefaultVisual(d, s),
CWOverrideRedirect | CWBackPixel, &attr);;
XMapWindow(d, bar);
xft_font = XftFontOpenName(d, s, FONT); // Use xft_font instead of font
XftColorAllocName(d, DefaultVisual(d, s), DefaultColormap(d, s), TEXT_COLOR, &xft_color);
xft_draw = XftDrawCreate(d, bar, DefaultVisual(d, s), DefaultColormap(d, s));
}
void draw_text(const char *text, int x, int y) {
XftDrawStringUtf8(xft_draw, &xft_color, xft_font, x, y, (XftChar8*)text, strlen(text));
}
void create_systray() {
XSetWindowAttributes attr;
attr.override_redirect = True;
attr.background_pixel = getcolor(BAR_COLOR);
systray = XCreateWindow(d, root, sw - TRAY_PADDING, 0, TRAY_PADDING * 2, BAR_HEIGHT, 0,
DefaultDepth(d, s), CopyFromParent,
DefaultVisual(d, s),
CWOverrideRedirect | CWBackPixel, &attr);
XMapWindow(d, systray);
xembed_atom = XInternAtom(d, "_XEMBED", False);
manager_atom = XInternAtom(d, "MANAGER", False);
system_tray_opcode_atom = XInternAtom(d, "_NET_SYSTEM_TRAY_OPCODE", False);
system_tray_selection_atom = XInternAtom(d, "_NET_SYSTEM_TRAY_S0", False);
XSetSelectionOwner(d, system_tray_selection_atom, systray, CurrentTime);
if (XGetSelectionOwner(d, system_tray_selection_atom) == systray) {
XClientMessageEvent cm;
cm.type = ClientMessage;
cm.window = root;
cm.message_type = manager_atom;
cm.format = 32;
cm.data.l[0] = CurrentTime;
cm.data.l[1] = system_tray_selection_atom;
cm.data.l[2] = systray;
cm.data.l[3] = 0;
cm.data.l[4] = 0;
XSendEvent(d, root, False, StructureNotifyMask, (XEvent *)&cm);
printf("Systray created and manager message sent\n");
} else {
printf("Failed to acquire selection ownership for systray\n");
}
}
void update_systray() {
int tray_width = num_systray_icons * (TRAY_ICON_SIZE + TRAY_ICON_SPACING) + TRAY_PADDING * 2;
if (num_systray_icons > 0) {
tray_width -= TRAY_ICON_SPACING;
XMapWindow(d, systray);
XMoveResizeWindow(d, systray,
sw - tray_width, 0,
tray_width, BAR_HEIGHT);
for (int i = 0; i < num_systray_icons; i++) {
XMoveResizeWindow(d, systray_icons[i],
TRAY_PADDING + i * (TRAY_ICON_SIZE + TRAY_ICON_SPACING),
(BAR_HEIGHT - TRAY_ICON_SIZE) / 2,
TRAY_ICON_SIZE, TRAY_ICON_SIZE);
XMapWindow(d, systray_icons[i]);
}
} else {
XUnmapWindow(d, systray);
tray_width = 0;
}
// Adjust the bar size to account for the systray
int bar_width = sw - tray_width;
XMoveResizeWindow(d, bar, 0, 0, bar_width, BAR_HEIGHT);
// Raise the bar and systray to ensure they're visible
XRaiseWindow(d, bar);
if (num_systray_icons > 0) {
XRaiseWindow(d, systray);
}
update_bar();
}
void handle_systray_request(XClientMessageEvent *cme) {
if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
Window icon = cme->data.l[2];
if (num_systray_icons < MAX_SYSTRAY_ICONS) {
XWindowAttributes wa;
if (XGetWindowAttributes(d, icon, &wa)) {
systray_icons[num_systray_icons++] = icon;
XReparentWindow(d, icon, systray, 0, 0);
XMapRaised(d, icon);
XEvent ev;
ev.xclient.type = ClientMessage;
ev.xclient.window = icon;
ev.xclient.message_type = xembed_atom;
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
ev.xclient.data.l[2] = 0;
ev.xclient.data.l[3] = systray;
ev.xclient.data.l[4] = 0;
XSendEvent(d, icon, False, NoEventMask, &ev);
update_systray();
DEBUG_LOG("Icon docked: %ld", icon);
} else {
DEBUG_LOG("Failed to get window attributes for icon: %ld", icon);
}
} else {
DEBUG_LOG("Maximum number of systray icons reached");
}
} else {
DEBUG_LOG("Received unknown systray request: %ld", cme->data.l[1]);
}
}
void update_bar() {
char status[256];
run_nyxwmblocks(status, sizeof(status));
XClearWindow(d, bar);
int bar_width = sw - (num_systray_icons > 0 ? (num_systray_icons * (TRAY_ICON_SIZE + TRAY_ICON_SPACING) + TRAY_PADDING * 2 - TRAY_ICON_SPACING) : 0);
XGlyphInfo extents;
XftTextExtentsUtf8(d, xft_font, (XftChar8*)status, strlen(status), &extents);
int x = (bar_width - extents.width) / 2;
int y = BAR_HEIGHT / 2 + xft_font->ascent / 2;
x = (x < 10) ? 10 : x;
XftDrawStringUtf8(xft_draw, &xft_color, xft_font, x, y, (XftChar8*)status, strlen(status));
XFlush(d);
}
int xerror(Display *dpy, XErrorEvent *ee) {
if (ee->error_code == BadAccess &&
ee->request_code == X_ChangeWindowAttributes) {
ERROR_LOG("Another window manager is already running");
exit(1);
}
char error_text[1024];
XGetErrorText(dpy, ee->error_code, error_text, sizeof(error_text));
ERROR_LOG("XError: request_code=%d, error_code=%d, error_text=%s",
ee->request_code, ee->error_code, error_text);
return 0;
}
void handle_destroy_notify(XDestroyWindowEvent *ev) {
for (int i = 0; i < num_systray_icons; i++) {
if (systray_icons[i] == ev->window) {
for (int j = i; j < num_systray_icons - 1; j++) {
systray_icons[j] = systray_icons[j+1];
}
num_systray_icons--;
update_systray();
break;
}
}
}
int main(void) {
DEBUG_LOG("Starting nyxwm");
if (!(d = XOpenDisplay(NULL))) {
ERROR_LOG("Cannot open display");
return 1;
}
DEBUG_LOG("Display opened successfully");
signal(SIGCHLD, SIG_IGN);
int (*prev_error_handler)(Display *, XErrorEvent *);
prev_error_handler = XSetErrorHandler(xerror);
s = DefaultScreen(d);
root = RootWindow(d, s);
sw = DisplayWidth(d, s);
sh = DisplayHeight(d, s);
DEBUG_LOG("Screen info: s=%d, root=%lu, sw=%d, sh=%d", s, root, sw, sh);
if (XSelectInput(d, root, SubstructureRedirectMask | SubstructureNotifyMask) == BadWindow) {
ERROR_LOG("Failed to select input on root window");
return 1;
}
DEBUG_LOG("Input selected on root window");
XDefineCursor(d, root, XCreateFontCursor(d, 68));
DEBUG_LOG("Cursor defined");
input_grab(root);
DEBUG_LOG("Input grabbed");
create_bar();
DEBUG_LOG("Bar created");
create_systray();
DEBUG_LOG("Systray created");
runAutoStart();
DEBUG_LOG("Autostart run");
XEvent ev;
struct timeval tv, last_update;
fd_set fds;
int xfd = ConnectionNumber(d);
gettimeofday(&last_update, NULL);
DEBUG_LOG("Entering main loop");
while (1) {
while (XPending(d)) {
XNextEvent(d, &ev);
DEBUG_LOG("Received event: type=%d", ev.type);
if (events[ev.type]) {
events[ev.type](&ev);
}
if (ev.type == ClientMessage && ev.xclient.message_type == system_tray_opcode_atom) {
handle_systray_request(&ev.xclient);
} else if (ev.type == DestroyNotify) {
handle_destroy_notify(&ev.xdestroywindow);
update_systray();
} else if (ev.type == MapNotify || ev.type == UnmapNotify) {
update_systray();
}
}
FD_ZERO(&fds);
FD_SET(xfd, &fds);
tv.tv_usec = 100000; // 100ms timeout
tv.tv_sec = 0;
int ready = select(xfd + 1, &fds, 0, 0, &tv);
if (ready == -1) {
if (errno != EINTR) {
ERROR_LOG("select() failed");
}
} else if (ready == 0) {
// Timeout occurred, check if it's time to update
struct timeval now;
gettimeofday(&now, NULL);
if ((now.tv_sec - last_update.tv_sec) * 1000000 + (now.tv_usec - last_update.tv_usec) >= 1000000) {
// Update every second
update_bar();
update_systray();
last_update = now;
}
}
}
// This part will likely never be reached
DEBUG_LOG("Exiting main loop");
XSetErrorHandler(prev_error_handler);
XCloseDisplay(d);
DEBUG_LOG("Display closed");
return 0;
}