From 75f815e0198d745768267fc7cfa3a7e1a657805b Mon Sep 17 00:00:00 2001 From: Matthias Kruk Date: Wed, 19 May 2021 09:08:03 +0900 Subject: [PATCH] mwm: Add functions for managing monitors, workspaces, clients, and more This commit makes several enhancements to the mwm type. It adds functions for: - attaching, detaching, and searching monitors - attaching, detaching, and searching workspaces - attaching, detaching, and searching clients - rendering UTF-8 using Pango and Xft - dealing with X11 Graphic Contexts - initializing colors and getting color values - executing MWM commands (for keyboard shortcuts) - dealing with X11 atoms - reading the root window title --- Makefile | 7 +- mwm.c | 1305 ++++++++++++++++++++++++++++++++++++++++++++++++++---- mwm.h | 60 ++- 3 files changed, 1265 insertions(+), 107 deletions(-) diff --git a/Makefile b/Makefile index b53ace8..426a7fb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ OUTPUT = mwm -OBJECTS = main.o x.o monitor.o mwm.o workspace.o loop.o client.o +OBJECTS = main.o x.o monitor.o mwm.o workspace.o loop.o client.o common.o layout.o config.o ifeq ($(PREFIX), ) PREFIX = /usr/local @@ -8,8 +8,9 @@ ifeq ($(MANPREFIX), ) MANPREFIX = $(PREFIX)/share/man endif -CFLAGS = -std=c99 -pedantic -Wall -Wextra -LDFLAGS = -lX11 -lXinerama +PKG_CONFIG_LIBS = xft pango pangoxft +CFLAGS = -std=c99 -pedantic -Wall $(shell pkg-config --cflags $(PKG_CONFIG_LIBS)) +LDFLAGS = -lX11 -lXinerama $(shell pkg-config --libs $(PKG_CONFIG_LIBS)) PHONY = all clean install uninstall diff --git a/mwm.c b/mwm.c index 3836405..23e9ddb 100644 --- a/mwm.c +++ b/mwm.c @@ -2,17 +2,42 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include #include "mwm.h" +#include "keys.h" #include "workspace.h" #include "monitor.h" -#include "array.h" #include "loop.h" #include "x.h" #include "common.h" +#include "client.h" +#include "theme.h" -typedef void (_mwm_xhandler_t)(struct mwm *, XEvent *); +typedef void (_mwm_xhandler_t)(struct mwm*, XEvent*); + +#define FIND_MONITOR_BY_ID ((int(*)(void*, void*))_cmp_monitor_id) +#define FIND_CLIENT_BY_WINDOW ((int(*)(struct client*, void*))_cmp_client_window) +#define FIND_MONITOR_BY_WINDOW ((int(*)(void*, void*))_cmp_monitor_contains_window) +#define FIND_MONITOR_BY_GEOM ((int(*)(void*, void*))_cmp_monitor_contains_geom) +#define FIND_WORKSPACE_BY_VIEWER ((int(*)(void*, void*))_cmp_workspace_viewer) +#define FIND_WORKSPACE_BY_NUMBER ((int(*)(void*, void*))_cmp_workspace_number) + +struct palette { + unsigned long color[MWM_COLOR_MAX]; + XftColor xcolor[MWM_COLOR_MAX]; +}; struct mwm { Display *display; @@ -21,31 +46,181 @@ struct mwm { struct geom root_geom; int running; + int needs_redraw; struct loop *monitors; struct loop *workspaces; + struct monitor *current_monitor; + struct client *focused_client; _mwm_xhandler_t *xhandler[LASTEvent]; - mwm_handler_t *handler[MWM_EVENT_LAST]; + + struct { + PangoLayout *layout; + int ascent; + int descent; + int height; + } font; + + struct palette palette[MWM_PALETTE_MAX]; + + void (*commands[MWM_CMD_MAX])(struct mwm*, void*); + + int (*xerror_default_handler)(Display*, XErrorEvent*); }; +extern struct mwm *__mwm; + +static int _xerror_startup(Display *display, XErrorEvent *event); +static int _xerror_handle(Display *display, XErrorEvent *event); +static int _xerror_nop(Display *display, XErrorEvent *event); + +static int _cmp_client_window(struct client *client, Window *window) +{ + return(client_get_window(client) == *window ? 0 : 1); +} + +static int _cmp_monitor_id(struct monitor *mon, int *id) +{ + return(monitor_get_id(mon) == *id ? 0 : 1); +} + +static int _cmp_monitor_contains_window(struct monitor *monitor, Window *window) +{ + struct geom window_geom; + struct geom monitor_geom; + + if(x_get_geom(monitor_get_display(monitor), *window, &window_geom) < 0 || + monitor_get_geometry(monitor, &monitor_geom) < 0) { + return(-EFAULT); + } + + return(geom_intersects(&monitor_geom, &window_geom) > 0 ? 0 : 1); +} + +static int _cmp_monitor_contains_geom(struct monitor *monitor, struct geom *geom) +{ + struct geom monitor_geom; + + if(monitor_get_geometry(monitor, &monitor_geom) < 0) { + return(-EFAULT); + } + + return(geom_intersects(&monitor_geom, geom) > 0 ? 0 : 1); +} + +static int _cmp_workspace_viewer(struct workspace *workspace, struct monitor *monitor) +{ + return(workspace_get_viewer(workspace) == monitor ? 0 : 1); +} + +static int _cmp_workspace_number(struct workspace *workspace, int *number) +{ + return(workspace_get_number(workspace) == *number ? 0 : 1); +} + static void _mwm_button_press(struct mwm *mwm, XEvent *event) { + XButtonPressedEvent *button_pressed; + struct monitor *event_monitor; + struct client *event_client; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + button_pressed = &event->xbutton; + + if(loop_find(&mwm->monitors, FIND_MONITOR_BY_WINDOW, + &button_pressed->window, (void**)&event_monitor) == 0) { + mwm_focus_monitor(mwm, event_monitor); + } + + /* + * TODO: Handle the event + */ + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &button_pressed->window, &event_client) == 0) { + mwm_focus_client(mwm, event_client); + } + return; } static void _mwm_client_message(struct mwm *mwm, XEvent *event) { + /* TODO: fullscreen toggle */ + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + return; } static void _mwm_configure_request(struct mwm *mwm, XEvent *event) { - return; -} + XConfigureRequestEvent *configure_request; + struct client *client; -static int _cmp_monitor_id(struct monitor *mon, int *id) -{ - return(monitor_get_id(mon) == *id); + /* + * This event is generated whenever the client attempts to resize itself. + * If the client is floating, or the viewer's layout is floating, we will + * accept the resize request. + * Otherwise we will override the request with the values that we have + * stored in the client structure. + */ + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + configure_request = &event->xconfigurerequest; + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &configure_request->window, &client) < 0) { + XWindowChanges changes; + + /* no client associated with that window */ + + changes.x = configure_request->x; + changes.y = configure_request->y; + changes.width = configure_request->width; + changes.height = configure_request->height; + changes.border_width = 0; /* TODO: make border width configurable */ + changes.sibling = configure_request->above; + changes.stack_mode = configure_request->detail; + + XConfigureWindow(mwm->display, configure_request->window, + configure_request->value_mask, &changes); + } else { + struct geom requested_geom; + + /* + * We have a client for that window. Let's see what it is + * that the client requested, and let client_change_geometry() + * do the deciding. + */ + + if(configure_request->value_mask & CWBorderWidth) { + client_set_border(client, configure_request->border_width); + } + + client_get_geometry(client, &requested_geom); + + if(configure_request->value_mask & CWX) { + requested_geom.x = configure_request->x; + } + if(configure_request->value_mask & CWY) { + requested_geom.y = configure_request->y; + } + if(configure_request->value_mask & CWWidth) { + requested_geom.w = configure_request->width; + } + if(configure_request->value_mask & CWHeight) { + requested_geom.h = configure_request->height; + } + + client_change_geometry(client, &requested_geom); + } + + XSync(mwm->display, False); + + return; } static void _mwm_configure_notify(struct mwm *mwm, XEvent *event) @@ -55,6 +230,8 @@ static void _mwm_configure_notify(struct mwm *mwm, XEvent *event) int num_monitors; int i; + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + cevent = &event->xconfigure; if(cevent->window != mwm->root) { @@ -77,12 +254,13 @@ static void _mwm_configure_notify(struct mwm *mwm, XEvent *event) screen_info[i].x_org, screen_info[i].y_org, screen_info[i].width, screen_info[i].height); - if(loop_find(&mwm->monitors, (int(*)(void*,void*))_cmp_monitor_id, + if(loop_find(&mwm->monitors, FIND_MONITOR_BY_ID, &screen_info[i].screen_number, (void**)&mon) < 0) { - if(monitor_new(screen_info[i].screen_number, + if(monitor_new(mwm, screen_info[i].screen_number, screen_info[i].x_org, screen_info[i].y_org, screen_info[i].width, screen_info[i].height, &mon) < 0) { + printf("Could not allocate monitor\n"); /* TODO: Let the user know */ return; } @@ -90,17 +268,19 @@ static void _mwm_configure_notify(struct mwm *mwm, XEvent *event) if(mwm_attach_monitor(mwm, mon) < 0) { monitor_free(&mon); /* TODO: Again, let the user know */ + printf("Could not attach monitor\n"); return; } } else { - /* update geometry */ + struct geom new_geom; - printf("Old monitor\n"); + /* update geometry */ + new_geom.x = screen_info[i].x_org; + new_geom.y = screen_info[i].y_org; + new_geom.w = screen_info[i].width; + new_geom.h = screen_info[i].height; - if(monitor_set_geometry(mon, screen_info[i].x_org, - screen_info[i].y_org, - screen_info[i].width, - screen_info[i].height) < 0) { + if(monitor_set_geometry(mon, &new_geom) < 0) { /* TODO: Let the user know */ } } @@ -115,66 +295,238 @@ static void _mwm_configure_notify(struct mwm *mwm, XEvent *event) return; } -static void _mwm_destroy_notify(struct mwm *mwm, XEvent *event) +static void _mwm_destroy_notify(struct mwm *mwm, XDestroyWindowEvent *event) { - return; -} + struct client *client; -static void _mwm_enter_notify(struct mwm *mwm, XEvent *event) -{ + /* get the client and detach it */ + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &event->window, &client) < 0) { + printf("Couldn't find client\n"); + return; + } + + if(mwm_detach_client(mwm, client) < 0) { + printf("Couldn't detach client\n"); + return; + } + + client_free(&client); return; } -static void _mwm_expose(struct mwm *mwm, XEvent *event) +static void _mwm_enter_notify(struct mwm *mwm, XCrossingEvent *event) { + struct client *client; + struct monitor *monitor; + + client = NULL; + monitor = NULL; + + /* pointer has entered a window - move focus, if it makes sense */ + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + if((event->mode != NotifyNormal || event->detail == NotifyInferior) && + event->window == mwm->root) { + return; + } + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &event->window, &client) == 0) { + mwm_focus_client(mwm, client); + } + + if(loop_find(&mwm->monitors, FIND_MONITOR_BY_WINDOW, + &event->window, (void**)&monitor) == 0) { + mwm_focus_monitor(mwm, monitor); + } + return; } -static void _mwm_focus_in(struct mwm *mwm, XEvent *event) +static void _mwm_expose(struct mwm *mwm, XExposeEvent *event) { + struct monitor *monitor; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + /* redraw the status bar, if we have one */ + + if(event->count > 0) { + return; + } + + if(loop_find(&mwm->monitors, FIND_MONITOR_BY_WINDOW, + &event->window, (void**)&monitor) == 0) { + monitor_needs_redraw(monitor); + } + return; } -static void _mwm_key_press(struct mwm *mwm, XEvent *event) +static void _mwm_focus_in(struct mwm *mwm, XFocusInEvent *event) { + struct client *client; + + /* move focus to the client referenced by the event */ + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &event->window, &client) < 0) { + return; + } + + mwm_focus_client(mwm, client); + return; } -static void _mwm_mapping_notify(struct mwm *mwm, XEvent *event) +static void _mwm_key_press(struct mwm *mwm, XKeyEvent *event) { + extern struct key_binding config_keybindings[]; + struct key_binding *binding; + KeySym keysym; + unsigned int mask; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + +#define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) +#define ALLMODMASK (Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) +#define ALLMASK (ShiftMask | ControlMask | ALLMODMASK) +#define CLEANMASK(mask) (mask & ~LockMask & ALLMASK) + + /* handle keyboard shortcuts */ + + keysym = XkbKeycodeToKeysym(mwm->display, event->keycode, 0, 0); + mask = CLEANMASK(event->state); + + for(binding = config_keybindings; binding->cmd < MWM_CMD_MAX; binding++) { + if(keysym == binding->key && mask == CLEANMASK(binding->mod)) { + mwm_cmd(mwm, binding->cmd, binding->arg); + } + } + +#undef BUTTONMASK +#undef ALLMODMASK +#undef ALLMASK +#undef CLEANMASK return; } -static void _mwm_map_request(struct mwm *mwm, XEvent *event) +static void _mwm_mapping_notify(struct mwm *mwm, XMappingEvent *event) { + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + XRefreshKeyboardMapping(event); + + if(event->request == MappingKeyboard) { + mwm_grab_keys(mwm); + } + return; } -static void _mwm_motion_notify(struct mwm *mwm, XEvent *event) +static void _mwm_map_request(struct mwm *mwm, XMapRequestEvent *event) { + XWindowAttributes attrs; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + if(!XGetWindowAttributes(mwm->display, event->window, &attrs)) { + return; + } + + if(attrs.override_redirect) { + return; + } + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &event->window, NULL) < 0) { + struct client *client; + + if(client_new(event->window, &attrs, &client) < 0) { + /* ENOMEM */ + return; + } + + if(mwm_attach_client(mwm, client) < 0) { + /* ENOMEM */ + client_free(&client); + return; + } + } + return; } -static void _mwm_property_notify(struct mwm *mwm, XEvent *event) +static void _mwm_motion_notify(struct mwm *mwm, XMotionEvent *event) { + struct monitor *monitor; + struct geom pointer_geom; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + /* move focus to the monitor referenced in the event */ + /* printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); */ + + pointer_geom.x = event->x_root; + pointer_geom.y = event->y_root; + pointer_geom.w = 1; + pointer_geom.h = 1; + + if(loop_find(&mwm->monitors, FIND_MONITOR_BY_GEOM, + &pointer_geom, (void**)&monitor) < 0) { + return; + } + + if(mwm_get_focused_monitor(mwm) != monitor) { + mwm_focus_monitor(mwm, monitor); + } + return; } -static void _mwm_unmap_notify(struct mwm *mwm, XEvent *event) +static void _mwm_property_notify(struct mwm *mwm, XPropertyEvent *event) { + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + /* FIXME: Property notification handling must be implemented more thoroughly */ + + if((event->window == mwm->root)) { + /* if(event->atom == XA_WM_NAME) */ + mwm_needs_redraw(mwm); + } + return; } -static void _mwm_notify(struct mwm *mwm, mwm_event_t event, void *data) +static void _mwm_unmap_notify(struct mwm *mwm, XUnmapEvent *event) { - if(!mwm || event >= MWM_EVENT_LAST) { + struct client *client; + + printf("%s(%p, %p)\n", __func__, (void*)mwm, (void*)event); + + if(mwm_find_client(mwm, FIND_CLIENT_BY_WINDOW, + &event->window, &client) < 0) { return; } - if(mwm->handler[event]) { - mwm->handler[event](mwm, event, data); + XGrabServer(mwm->display); + XSetErrorHandler(_xerror_nop); + + client_set_state(client, WithdrawnState); + + if(!event->send_event) { + mwm_detach_client(mwm, client); + client_free(&client); } + XSync(mwm->display, False); + XSetErrorHandler(_xerror_handle); + XUngrabServer(mwm->display); + return; } @@ -208,20 +560,20 @@ int mwm_new(struct mwm **dst) } } - mwm->xhandler[ButtonPress] = _mwm_button_press; - mwm->xhandler[ClientMessage] = _mwm_client_message; - mwm->xhandler[ConfigureRequest] = _mwm_configure_request; - mwm->xhandler[ConfigureNotify] = _mwm_configure_notify; - mwm->xhandler[DestroyNotify] = _mwm_destroy_notify; - mwm->xhandler[EnterNotify] = _mwm_enter_notify; - mwm->xhandler[Expose] = _mwm_expose; - mwm->xhandler[FocusIn] = _mwm_focus_in; - mwm->xhandler[KeyPress] = _mwm_key_press; - mwm->xhandler[MappingNotify] = _mwm_mapping_notify; - mwm->xhandler[MapRequest] = _mwm_map_request; - mwm->xhandler[MotionNotify] = _mwm_motion_notify; - mwm->xhandler[PropertyNotify] = _mwm_property_notify; - mwm->xhandler[UnmapNotify] = _mwm_unmap_notify; + mwm->xhandler[ButtonPress] = (_mwm_xhandler_t*)_mwm_button_press; + mwm->xhandler[ClientMessage] = (_mwm_xhandler_t*)_mwm_client_message; + mwm->xhandler[ConfigureRequest] = (_mwm_xhandler_t*)_mwm_configure_request; + mwm->xhandler[ConfigureNotify] = (_mwm_xhandler_t*)_mwm_configure_notify; + mwm->xhandler[DestroyNotify] = (_mwm_xhandler_t*)_mwm_destroy_notify; + mwm->xhandler[EnterNotify] = (_mwm_xhandler_t*)_mwm_enter_notify; + mwm->xhandler[Expose] = (_mwm_xhandler_t*)_mwm_expose; + mwm->xhandler[FocusIn] = (_mwm_xhandler_t*)_mwm_focus_in; + mwm->xhandler[KeyPress] = (_mwm_xhandler_t*)_mwm_key_press; + mwm->xhandler[MappingNotify] = (_mwm_xhandler_t*)_mwm_mapping_notify; + mwm->xhandler[MapRequest] = (_mwm_xhandler_t*)_mwm_map_request; + mwm->xhandler[MotionNotify] = (_mwm_xhandler_t*)_mwm_motion_notify; + mwm->xhandler[PropertyNotify] = (_mwm_xhandler_t*)_mwm_property_notify; + mwm->xhandler[UnmapNotify] = (_mwm_xhandler_t*)_mwm_unmap_notify; cleanup: if(err < 0) { @@ -256,99 +608,856 @@ int mwm_free(struct mwm **mwm) return(0); } -int mwm_init(struct mwm *mwm) +Display* mwm_get_display(struct mwm *mwm) { - if(!mwm) { - return(-EINVAL); - } + return(mwm->display); +} - mwm->display = XOpenDisplay(NULL); +Window mwm_get_root_window(struct mwm *mwm) +{ + return(mwm->root); +} - if(!mwm->display) { +static int _color_init(struct mwm *mwm, + unsigned long *color, + XftColor *xcolor, + const char *colorspec) +{ + Visual *visual; + Colormap colormap; + + visual = DefaultVisual(mwm->display, mwm->screen); + colormap = DefaultColormap(mwm->display, mwm->screen); + + if(!XftColorAllocName(mwm->display, visual, colormap, + colorspec, xcolor)) { return(-EIO); } - mwm->screen = DefaultScreen(mwm->display); - mwm->root = RootWindow(mwm->display, mwm->screen); + *color = xcolor->pixel; + return(0); +} - XSelectInput(mwm->display, mwm->root, - SubstructureRedirectMask | - SubstructureNotifyMask | - ButtonPressMask | - PointerMotionMask | - EnterWindowMask | - LeaveWindowMask | - StructureNotifyMask | - PropertyChangeMask); +static int _palette_init(struct mwm *mwm, + struct palette *palette, + union colorset *colorset) +{ + int i; - x_configure_notify(mwm->display, mwm->root, NULL, 0); + for(i = 0; i < MWM_COLOR_MAX; i++) { + _color_init(mwm, &palette->color[i], + &palette->xcolor[i], + colorset->indexed[i]); + } return(0); } -int mwm_run(struct mwm *mwm) +void _sigchld(int unused) { - XEvent event; + if(signal(SIGCHLD, _sigchld) == SIG_ERR) { + perror("signal"); + exit(1); + } - XSync(mwm->display, False); - mwm->running = 1; + while(waitpid(-1, NULL, WNOHANG) > 0); - while(mwm->running && !XNextEvent(mwm->display, &event)) { - if(mwm->xhandler[event.type]) { - mwm->xhandler[event.type](mwm, &event); - } + return; +} + +static void _cmd_spawn(struct mwm *mwm, void *arg) +{ + char **argv; + pid_t pid; + + argv = (char**)arg; + pid = fork(); + + if(pid == 0) { + close(ConnectionNumber(mwm->display)); + setsid(); + execvp(*argv, argv); + exit(0); } - return(0); + return; } -int mwm_stop(struct mwm *mwm) +static void _cmd_show_workspace(struct mwm *mwm, void *arg) { - if(!mwm) { - return(-EINVAL); + struct monitor *monitor; + struct workspace *workspace; + long number; + + number = (long)arg; + monitor = mwm_get_focused_monitor(mwm); + + if(loop_find(&mwm->workspaces, FIND_WORKSPACE_BY_NUMBER, + (void*)&number, (void**)&workspace) < 0) { + return; } - if(!mwm->running) { - return(-EALREADY); + monitor_set_workspace(monitor, workspace); + return; +} + +static void _cmd_move_to_workspace(struct mwm *mwm, void *arg) +{ + struct workspace *src_workspace; + struct workspace *dst_workspace; + struct client *client; + long dst_number; + + dst_number = (long)arg; + client = mwm_get_focused_client(mwm); + + if(!client) { + return; } - mwm->running = 0; + if(loop_find(&mwm->workspaces, FIND_WORKSPACE_BY_NUMBER, + (void*)&dst_number, (void**)&dst_workspace) < 0) { + return; + } - return(0); + src_workspace = client_get_workspace(client); + + if(src_workspace != dst_workspace) { + workspace_detach_client(src_workspace, client); + workspace_attach_client(dst_workspace, client); + } + + return; } -int mwm_attach_monitor(struct mwm *mwm, struct monitor *mon) +static void _cmd_set_layout(struct mwm *mwm, void *arg) { - int idx; + extern struct layout *layouts[]; + struct monitor *monitor; + long num; - if(!mwm || !mon) { - return(-EINVAL); + num = (long)arg; + monitor = mwm_get_focused_monitor(mwm); + + if(monitor_get_layout(monitor) != layouts[num]) { + monitor_set_layout(monitor, layouts[num]); + monitor_needs_redraw(monitor); } - idx = monitor_get_id(mon); + return; +} - if(loop_append(&mwm->monitors, mon) < 0) { - return(-ENOMEM); - } +static void _cmd_shift_focus(struct mwm *mwm, void *arg) +{ + struct workspace *workspace; + long dir; - _mwm_notify(mwm, MWM_EVENT_MONITOR_ATTACHED, mon); + dir = (long)arg; - printf("Attached monitor %d: %p\n", idx, (void*)mon); + workspace = mwm_get_focused_workspace(mwm); + workspace_shift_focus(workspace, dir); - return(0); + return; } -int mwm_detach_monitor(struct mwm *mwm, struct monitor *mon) +static void _cmd_shift_client(struct mwm *mwm, void *arg) { - if(!mwm || !mon) { - return(-EINVAL); + struct workspace *workspace; + long dir; + + dir = (long)arg; + + workspace = mwm_get_focused_workspace(mwm); + workspace_shift_client(workspace, NULL, dir); + + return; +} + +static void _cmd_shift_monitor_focus(struct mwm *mwm, void *arg) +{ + struct monitor *src_monitor; + struct monitor *dst_monitor; + long dir; + + dir = (long)arg; + /* move focus to previous or next monitor */ + + if(!mwm || dir == 0) { + return; } - if(loop_remove(&mwm->monitors, mon) < 0) { - return(-ENODEV); + src_monitor = mwm_get_focused_monitor(mwm); + + if(dir > 0) { + if(loop_get_next(&mwm->monitors, src_monitor, (void**)&dst_monitor) < 0) { + return; + } + } else { + if(loop_get_prev(&mwm->monitors, src_monitor, (void**)&dst_monitor) < 0) { + return; + } + } + + if(src_monitor != dst_monitor) { + mwm_focus_monitor(mwm, dst_monitor); + + monitor_needs_redraw(src_monitor); + monitor_needs_redraw(dst_monitor); + } + + return; +} + +static void _cmd_shift_workspace(struct mwm *mwm, void *arg) +{ + struct monitor *src_monitor; + struct monitor *dst_monitor; + struct workspace *workspace; + long dir; + + /* move workspace to the next or previous monitor */ + + dir = (long)arg; + + if(!mwm || dir == 0) { + return; + } + + src_monitor = mwm_get_focused_monitor(mwm); + workspace = monitor_get_workspace(src_monitor); + + if(dir > 0) { + if(loop_get_next(&mwm->monitors, src_monitor, (void**)&dst_monitor) < 0) { + return; + } + } else { + if(loop_get_prev(&mwm->monitors, src_monitor, (void**)&dst_monitor) < 0) { + return; + } + } + + if(src_monitor != dst_monitor) { + monitor_set_workspace(dst_monitor, workspace); + mwm_focus_monitor(mwm, dst_monitor); + } + + return; +} + +static void _cmd_quit(struct mwm *mwm, void *arg) +{ + mwm_stop(mwm); + return; +} + +static int _xerror_startup(Display *display, XErrorEvent *event) +{ + fprintf(stderr, "Looks like I'm not your only window manager\n"); + exit(1); + return(-1); +} + +static int _xerror_nop(Display *display, XErrorEvent *event) +{ + return(0); +} + +static int _can_ignore_error(XErrorEvent *event) +{ + static struct { + unsigned char error_code; + unsigned char request_code; + } ignore_ok[] = { + { BadMatch, X_SetInputFocus }, + { BadDrawable, X_PolyText8 }, + { BadDrawable, X_PolyFillRectangle }, + { BadDrawable, X_PolySegment }, + { BadMatch, X_ConfigureWindow }, + { BadAccess, X_GrabButton }, + { BadAccess, X_GrabKey }, + { BadDrawable, X_CopyArea } + }; + int i; + + for(i = 0; i < (sizeof(ignore_ok) / sizeof(ignore_ok[0])); i++) { + if(event->request_code == ignore_ok[i].request_code && + event->error_code == ignore_ok[i].error_code) { + return(1); + } + } + + return(0); +} + +static int _xerror_handle(Display *display, XErrorEvent *event) +{ + if(_can_ignore_error(event)) { + return(0); + } + + return(__mwm->xerror_default_handler(display, event)); +} + +int mwm_init(struct mwm *mwm) +{ + extern struct theme config_theme; + PangoFontMap *fontmap; + PangoContext *context; + PangoFontDescription *fontdesc; + PangoFontMetrics *fontmetrics; + + if(!mwm) { + return(-EINVAL); + } + + mwm->display = XOpenDisplay(NULL); + + if(!mwm->display) { + return(-EIO); + } + + _sigchld(0); + + mwm->screen = DefaultScreen(mwm->display); + mwm->root = RootWindow(mwm->display, mwm->screen); + mwm->xerror_default_handler = XSetErrorHandler(_xerror_startup); + + if(!mwm->xerror_default_handler) { + return(-EIO); + } + + XSelectInput(mwm->display, mwm->root, + SubstructureRedirectMask | + SubstructureNotifyMask | + /* ButtonPressMask | */ + PointerMotionMask | + EnterWindowMask | + LeaveWindowMask | + StructureNotifyMask | + PropertyChangeMask); + XSync(mwm->display, False); + + XSetErrorHandler(_xerror_handle); + XSync(mwm->display, False); + + mwm_grab_keys(mwm); + + x_configure_notify(mwm->display, mwm->root, NULL, 0); + + fontmap = pango_xft_get_font_map(mwm->display, mwm->screen); + context = pango_font_map_create_context(fontmap); + fontdesc = pango_font_description_from_string(config_theme.statusbar_font); + mwm->font.layout = pango_layout_new(context); + pango_layout_set_font_description(mwm->font.layout, fontdesc); + + fontmetrics = pango_context_get_metrics(context, fontdesc, NULL); + mwm->font.ascent = pango_font_metrics_get_ascent(fontmetrics) / PANGO_SCALE; + mwm->font.descent = pango_font_metrics_get_descent(fontmetrics) / PANGO_SCALE; + mwm->font.height = mwm->font.ascent + mwm->font.descent; + + _palette_init(mwm, &(mwm->palette[MWM_PALETTE_ACTIVE]), + &config_theme.active); + _palette_init(mwm, &(mwm->palette[MWM_PALETTE_INACTIVE]), + &config_theme.inactive); + + pango_font_metrics_unref(fontmetrics); + g_object_unref(context); + + mwm->commands[MWM_CMD_QUIT] = _cmd_quit; + mwm->commands[MWM_CMD_SPAWN] = _cmd_spawn; + mwm->commands[MWM_CMD_SHOW_WORKSPACE] = _cmd_show_workspace; + mwm->commands[MWM_CMD_MOVE_TO_WORKSPACE] = _cmd_move_to_workspace; + mwm->commands[MWM_CMD_SET_LAYOUT] = _cmd_set_layout; + mwm->commands[MWM_CMD_SHIFT_FOCUS] = _cmd_shift_focus; + mwm->commands[MWM_CMD_SHIFT_CLIENT] = _cmd_shift_client; + mwm->commands[MWM_CMD_SHIFT_MONITOR_FOCUS] = _cmd_shift_monitor_focus; + mwm->commands[MWM_CMD_SHIFT_WORKSPACE] = _cmd_shift_workspace; + + return(0); +} + +int mwm_render_text(struct mwm *mwm, XftDraw *drawable, + mwm_palette_t palette, const char *text, + const int x, const int y) +{ + XftColor *color; + + if(!mwm || !drawable || !text) { + return(-EINVAL); + } + + color = &mwm->palette[palette].xcolor[MWM_COLOR_TEXT]; + + pango_layout_set_attributes(mwm->font.layout, NULL); + + pango_layout_set_markup(mwm->font.layout, text, -1); + pango_xft_render_layout(drawable, color, + mwm->font.layout, + x * PANGO_SCALE, + y * PANGO_SCALE); + + return(0); +} + +int mwm_run(struct mwm *mwm) +{ + XEvent event; + + XSync(mwm->display, False); + mwm->running = 1; + + while(mwm->running && !XNextEvent(mwm->display, &event)) { + struct client *focused_client; + + if(mwm->xhandler[event.type]) { + /* printf("XEvent: %x\n", event.type); */ + mwm->xhandler[event.type](mwm, &event); + } + + if(mwm->needs_redraw) { + mwm_redraw(mwm); + } + + focused_client = mwm_get_focused_client(mwm); + + if(mwm->focused_client != focused_client) { + client_focus(focused_client); + mwm->focused_client = focused_client; + } + } + + return(0); +} + +int mwm_stop(struct mwm *mwm) +{ + if(!mwm) { + return(-EINVAL); + } + + if(!mwm->running) { + return(-EALREADY); + } + + mwm->running = 0; + + return(0); +} + +int mwm_attach_monitor(struct mwm *mwm, struct monitor *mon) +{ + struct workspace *unviewed; + + if(!mwm || !mon) { + return(-EINVAL); + } + + if(loop_append(&mwm->monitors, mon) < 0) { + return(-ENOMEM); + } + + if(!mwm_get_focused_monitor(mwm)) { + mwm_focus_monitor(mwm, mon); + } + + if(loop_find(&mwm->workspaces, FIND_WORKSPACE_BY_VIEWER, NULL, (void**)&unviewed) < 0) { + return(-EFAULT); + } + + monitor_set_workspace(mon, unviewed); + + return(0); +} + +int mwm_detach_monitor(struct mwm *mwm, struct monitor *mon) +{ + struct workspace *workspace; + + if(!mwm || !mon) { + return(-EINVAL); + } + + if(loop_remove(&mwm->monitors, mon) < 0) { + return(-ENODEV); + } + + workspace = monitor_get_workspace(mon); + workspace_set_viewer(workspace, NULL); + + return(0); +} + +int mwm_focus_monitor(struct mwm *mwm, struct monitor *monitor) +{ + if(!mwm || !monitor) { + return(-EINVAL); + } + + if(mwm->current_monitor != monitor) { +#if MWM_DEBUG + printf("New current monitor: %p\n", (void*)monitor); +#endif /* MWM_DEBUG */ + + monitor_needs_redraw(mwm->current_monitor); + monitor_needs_redraw(monitor); + } + + mwm->current_monitor = monitor; + + return(0); +} + +struct monitor* mwm_get_focused_monitor(struct mwm *mwm) +{ + return(mwm->current_monitor); +} + +int mwm_attach_client(struct mwm *mwm, struct client *client) +{ + struct workspace *workspace; + + if(!mwm || !client) { + return(-EINVAL); + } + + workspace = NULL; + + if(mwm->current_monitor) { + workspace = monitor_get_workspace(mwm->current_monitor); + } + + if(!workspace) { + /* + * there's a chance that we might be attaching clients + * before the first monitor has been detected + */ + + if(loop_get_first(&mwm->workspaces, (void**)&workspace) < 0) { + /* this really shouldn't happen */ + return(-EFAULT); + } + } + +#if MWM_DEBUG + printf("Attaching client %p to workspace %p\n", + (void*)client, (void*)workspace); +#endif /* MWM_DEBUG */ + + return(workspace_attach_client(workspace, client)); +} + +int mwm_detach_client(struct mwm *mwm, struct client *client) +{ + struct workspace *workspace; + + if(!mwm || !client) { + return(-EINVAL); + } + + workspace = client_get_workspace(client); + + workspace_detach_client(workspace, client); + + return(0); +} + +int mwm_focus_client(struct mwm *mwm, struct client *client) +{ + struct workspace *workspace; + + if(!mwm) { + return(-EINVAL); + } + + if(client) { + workspace = client_get_workspace(client); + } else { + workspace = mwm_get_focused_workspace(mwm); + } + + if(!workspace) { + return(-EBADFD); + } + + return(workspace_focus_client(workspace, client)); +} + +struct client* mwm_get_focused_client(struct mwm *mwm) +{ + struct monitor *focused_monitor; + + focused_monitor = mwm_get_focused_monitor(mwm); + + if(!focused_monitor) { + return(NULL); + } + + return(monitor_get_focused_client(focused_monitor)); +} + +int mwm_find_client(struct mwm *mwm, int(*cmp)(struct client*, void*), + void *data, struct client **client) +{ + loop_iter_t first; + loop_iter_t cur; + + if(!mwm) { + return(-EINVAL); + } + + first = loop_get_iter(&mwm->workspaces); + cur = first; + + do { + struct workspace *workspace; + + workspace = (struct workspace*)loop_iter_get_data(cur); + + if(!workspace) { + fprintf(stderr, "%s: Invalid workspace in loop\n", __func__); + continue; + } + + if(workspace_find_client(workspace, cmp, data, client) == 0) { + return(0); + } + + cur = loop_iter_get_next(cur); + } while(cur != first); + + return(-ENOENT); +} + +struct workspace *mwm_get_focused_workspace(struct mwm *mwm) +{ + struct monitor *monitor; + struct workspace *workspace; + + workspace = NULL; + monitor = mwm_get_focused_monitor(mwm); + + if(monitor) { + workspace = monitor_get_workspace(monitor); + } + + return(workspace); +} + +int mwm_foreach_workspace(struct mwm *mwm, + int (*func)(struct mwm*, struct workspace*, void*), + void *data) +{ + loop_iter_t first; + loop_iter_t cur; + + if(!mwm) { + return(-EINVAL); + } + + first = loop_get_iter(&mwm->workspaces); + cur = first; + + do { + struct workspace *workspace; + + workspace = (struct workspace*)loop_iter_get_data(cur); + + if(func(mwm, workspace, data) < 0) { + break; + } + cur = loop_iter_get_next(cur); + } while(cur != first); + + return(0); +} + +int mwm_needs_redraw(struct mwm *mwm) +{ + if(!mwm) { + return(-EINVAL); + } + + mwm->needs_redraw = 1; + return(0); +} + +int mwm_redraw(struct mwm *mwm) +{ + if(!mwm) { + return(-EINVAL); + } + + if(mwm->needs_redraw) { + loop_foreach(&mwm->monitors, (void(*)(void*))monitor_redraw); + loop_foreach(&mwm->workspaces, (void(*)(void*))workspace_redraw); + mwm->needs_redraw = 0; + } + + return(0); +} + +Window mwm_create_window(struct mwm *mwm, const int x, const int y, const int w, const int h) +{ + Window window; + XSetWindowAttributes attrs; + int depth; + Visual *visual; + unsigned long mask; + + attrs.override_redirect = True; + attrs.background_pixmap = ParentRelative; + attrs.event_mask = ExposureMask; + + mask = CWOverrideRedirect | CWBackPixmap | CWEventMask; + depth = DefaultDepth(mwm->display, mwm->screen); + visual = DefaultVisual(mwm->display, mwm->screen); + + window = XCreateWindow(mwm->display, mwm->root, x, y, w, h, 0, + depth, CopyFromParent, visual, mask, &attrs); + + return(window); +} + +GC mwm_create_gc(struct mwm *mwm) +{ + GC context; + + context = XCreateGC(mwm->display, mwm->root, 0, NULL); + + XSetLineAttributes(mwm->display, context, 1, LineSolid, CapButt, JoinMiter); + + return(context); +} + +XftDraw* mwm_create_xft_context(struct mwm *mwm, Drawable drawable) +{ + return(XftDrawCreate(mwm->display, drawable, + DefaultVisual(mwm->display, mwm->screen), + DefaultColormap(mwm->display, mwm->screen))); +} + +int mwm_get_font_height(struct mwm *mwm) +{ + return(mwm->font.height); +} + +int mwm_get_text_width(struct mwm *mwm, const char *text) +{ + PangoRectangle extents; + + pango_layout_set_attributes(mwm->font.layout, NULL); + pango_layout_set_markup(mwm->font.layout, text, -1); + pango_layout_get_extents(mwm->font.layout, 0, &extents); + + return(extents.width / PANGO_SCALE); +} + +unsigned long mwm_get_color(struct mwm *mwm, mwm_palette_t palette, mwm_color_t color) +{ + return(mwm->palette[palette].color[color]); +} + +int mwm_get_text_property(struct mwm *mwm, Window window, Atom atom, char *buffer, size_t buffer_size) +{ + XTextProperty property; + int len; + + if(!mwm || !buffer || buffer_size == 0) { + return(-EINVAL); + } + + XGetTextProperty(mwm->display, window, &property, atom); + + if(property.nitems == 0) { + return(-ENOENT); + } + + if(property.encoding == XA_STRING) { + len = snprintf(buffer, buffer_size, "%s", (char*)property.value); + } else { + len = -ENOSYS; + } + + XFree(property.value); + return(len); +} + +int mwm_get_status(struct mwm *mwm, char *buffer, const size_t buffer_size) +{ + int len; + + len = mwm_get_text_property(mwm, mwm->root, XA_WM_NAME, buffer, buffer_size); + + if(len < 0) { + return(snprintf(buffer, buffer_size, "mwm-0.1")); + } + + return(len); +} + +int mwm_grab_keys(struct mwm *mwm) +{ + extern struct key_binding config_keybindings[]; + struct key_binding *binding; + + if(!mwm) { + return(-EINVAL); + } + + XUngrabKey(mwm->display, AnyKey, AnyModifier, mwm->root); + + for(binding = config_keybindings; binding->cmd < MWM_CMD_MAX; binding++) { + KeyCode code; + + code = XKeysymToKeycode(mwm->display, binding->key); + + XGrabKey(mwm->display, code, binding->mod, + mwm->root, True, GrabModeAsync, GrabModeAsync); + XGrabKey(mwm->display, code, binding->mod | LockMask, + mwm->root, True, GrabModeAsync, GrabModeAsync); + } + + return(0); +} + +int mwm_cmd(struct mwm *mwm, mwm_cmd_t cmd, void *data) +{ +#ifdef MWM_DEBUG + static const char *cmd_names[] = { + "quit", + "spawn", + "show_workspace", + "move_to_workspace", + "(invalid)" + }; + + printf("%s(%p, %d [%s], %p)\n", __func__, (void*)mwm, + cmd, cmd_names[cmd < MWM_CMD_MAX ? cmd : MWM_CMD_MAX], data); +#endif /* MWM_DEBUG */ + + if(!mwm || cmd < 0 || cmd >= MWM_CMD_MAX) { + return(-EINVAL); + } + + if(!mwm->commands[cmd]) { + return(-ENOSYS); + } + + mwm->commands[cmd](mwm, data); + return(0); +} + +int mwm_get_atom(struct mwm *mwm, const char *name, long *dst) +{ + long atom; + + if(!mwm || !name || !dst) { + return(-EINVAL); } - /* unregister monitor */ + /* FIXME: Cache the result */ + atom = XInternAtom(mwm->display, name, False); + *dst = atom; return(0); } diff --git a/mwm.h b/mwm.h index c0488fc..37ded4f 100644 --- a/mwm.h +++ b/mwm.h @@ -2,18 +2,26 @@ #define MWM_H 1 #include +#include +#include "theme.h" struct mwm; struct monitor; struct client; +struct workspace; typedef enum { - MWM_EVENT_MONITOR_ATTACHED = 0, - MWM_EVENT_MONITOR_DETACHED, - MWM_EVENT_LAST -} mwm_event_t; - -typedef void (mwm_handler_t)(struct mwm *mwm, mwm_event_t, void *); + MWM_CMD_QUIT = 0, + MWM_CMD_SPAWN, + MWM_CMD_SHOW_WORKSPACE, + MWM_CMD_MOVE_TO_WORKSPACE, + MWM_CMD_SET_LAYOUT, + MWM_CMD_SHIFT_FOCUS, + MWM_CMD_SHIFT_CLIENT, + MWM_CMD_SHIFT_WORKSPACE, + MWM_CMD_SHIFT_MONITOR_FOCUS, + MWM_CMD_MAX +} mwm_cmd_t; int mwm_new(struct mwm **mwm); int mwm_free(struct mwm **mwm); @@ -22,10 +30,50 @@ int mwm_init(struct mwm *mwm); int mwm_run(struct mwm *mwm); int mwm_stop(struct mwm *mwm); +int mwm_needs_redraw(struct mwm *mwm); +int mwm_redraw(struct mwm *mwm); + +Display* mwm_get_display(struct mwm *mwm); +Window mwm_get_root_window(struct mwm *mwm); + int mwm_attach_monitor(struct mwm *mwm, struct monitor *mon); int mwm_detach_monitor(struct mwm *mwm, struct monitor *mon); +int mwm_focus_monitor(struct mwm *mwm, struct monitor *mon); +struct monitor* mwm_get_focused_monitor(struct mwm *mwm); +int mwm_find_monitor(struct mwm *mwm, int (*cmp)(struct monitor*, void*), + void*, struct monitor**); int mwm_attach_client(struct mwm *mwm, struct client *client); int mwm_detach_client(struct mwm *mwm, struct client *client); +int mwm_focus_client(struct mwm *mwm, struct client *client); +struct client* mwm_get_focused_client(struct mwm *mwm); +int mwm_find_client(struct mwm *mwm, int (*cmp)(struct client*, void*), + void*, struct client**); + +int mwm_attach_workspace(struct mwm *mwm, struct workspace *workspace); +int mwm_detach_workspace(struct mwm *mwm, struct workspace *workspace); +int mwm_focus_workspace(struct mwm *mwm, struct workspace *workspace); +struct workspace* mwm_get_focused_workspace(struct mwm *mwm); +int mwm_find_workspace(struct mwm *mwm, int (*cmp)(struct workspace*, void*), + void*, struct workspace**); +int mwm_foreach_workspace(struct mwm *mwm, + int (*func)(struct mwm*, struct workspace*, void*), + void *data); + +Window mwm_create_window(struct mwm *mwm, const int x, const int y, const int w, const int h); +GC mwm_create_gc(struct mwm *mwm); +XftDraw* mwm_create_xft_context(struct mwm *mwm, Drawable drawable); + +int mwm_render_text(struct mwm *mwm, XftDraw *drawable, + mwm_palette_t palette, const char *text, + const int x, const int y); +int mwm_get_font_height(struct mwm *mwm); +int mwm_get_text_width(struct mwm *mwm, const char *text); +unsigned long mwm_get_color(struct mwm *mwm, mwm_palette_t palette, mwm_color_t color); + +int mwm_get_status(struct mwm *mwm, char *buffer, const size_t buffer_size); +int mwm_grab_keys(struct mwm *mwm); +int mwm_cmd(struct mwm *mwm, mwm_cmd_t, void *data); +int mwm_get_atom(struct mwm *mwm, const char *name, long *dst); #endif /* MWM_H */ -- 2.47.3