From 7e76b1b0449adb8c0aacd58fb1854f36008e2368 Mon Sep 17 00:00:00 2001 From: Tudor Date: Fri, 2 Sep 2016 23:03:43 +0300 Subject: [PATCH] init woooo! the wm itself is currently configured at compile time in config.h --- .sxhkdrc | 41 ++ .xinitrc | 3 + LICENSE | 16 + Makefile | 34 + README.md | 12 + TODO.md | 1 + client.c | 179 +++++ common.h | 10 + config.h | 11 + config.mk | 6 + ipc.h | 40 ++ list.c | 88 +++ list.h | 15 + types.h | 46 ++ wm.c | 2021 +++++++++++++++++++++++++++++++++++++++++++++++++++++ xephyr.sh | 10 + 16 files changed, 2533 insertions(+) create mode 100644 .sxhkdrc create mode 100644 .xinitrc create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 TODO.md create mode 100644 client.c create mode 100644 common.h create mode 100644 config.h create mode 100644 config.mk create mode 100644 ipc.h create mode 100644 list.c create mode 100644 list.h create mode 100644 types.h create mode 100644 wm.c create mode 100755 xephyr.sh diff --git a/.sxhkdrc b/.sxhkdrc new file mode 100644 index 0000000..14bd0a1 --- /dev/null +++ b/.sxhkdrc @@ -0,0 +1,41 @@ +super + {h,j,k,l} + waitron window_move {-20 0, 0 20, 0 -20, 20 0} + +super + alt + {h,j,k,l} + waitron window_resize {-20 0, 0 20, 0 -20, 20 0} + +super + shift + {h,j,k,l} + waitron window_move {-50 0, 0 50, 0 -50, 50 0} + +super + shift + alt + {h,j,k,l} + waitron window_resize {-50 0, 0 50, 0 -50, 50 0} + +super + alt + Escape + waitron wm_quit 0 + +super + f + waitron window_maximize + +super + w + waitron window_close + +super + b + waitron window_hor_maximize + +super + v + waitron window_ver_maximize + +alt + Tab + waitron window_cycle + +alt + shift + Tab + waitron window_rev_cycle + +super + {_,shift +}{1-6} + waitron {group_toggle,group_add_window} {1-6} + +super + ctrl + r + waitron group_remove_window + +super + Return + urxvt diff --git a/.xinitrc b/.xinitrc new file mode 100644 index 0000000..19e6e1a --- /dev/null +++ b/.xinitrc @@ -0,0 +1,3 @@ +sxhkd -c .sxhkdrc & + +exec ./stwm diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c26699e --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +ISC License + +Copyright (c) 2016, Tudor Roman + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted, provided that the +above copyright notice and this permission notice appear in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa32ecf --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +include config.mk + +__NAME__ = windowchef +__NAME_CLIENT__ = waitron +NAME_DEFINES = -D__NAME__=\"$(__NAME__)\" \ + -D__NAME_CLIENT__=\"$(__NAME_CLIENT__)\" + +SRC = list.c wm.c client.c +OBJ = $(SRC:.c=.o) +BIN = $(__NAME__) $(__NAME_CLIENT__) +CFLAGS += $(NAME_DEFINES) -DD + +all: $(BIN) + +$(__NAME__): wm.o list.o + @echo $@ + @$(CC) -o $@ $^ $(LDFLAGS) + +$(__NAME_CLIENT__): client.o + @echo $@ + @$(CC) -o $@ $^ $(LDFLAGS) + +%.o: %.c + @echo $@ + @$(CC) -o $@ -c $(CFLAGS) $< + +$(OBJ): common.h list.h ipc.h types.h config.h + +install: all + install $(__NAME__) $(DESTDIR)$(PREFIX)/bin/$(__NAME__) + install $(__NAME_CLIENT__) $(DESTDIR)$(PREFIX)/bin/$(__NAME_CLIENT__) + +clean: + rm -f $(OBJ) $(BIN) diff --git a/README.md b/README.md new file mode 100644 index 0000000..67d00ef --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +Windowchef +========== + +Cooking windows since 2016 +-------------------------- + +### Building and installing + +```bash +make +sudo make install +``` diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3f3bcce --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- Unlimited borders diff --git a/client.c b/client.c new file mode 100644 index 0000000..b21af48 --- /dev/null +++ b/client.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ipc.h" + +#ifndef __NAME_CLIENT__ +#define __NAME_CLIENT__ "client" +#endif + +static bool +fn_offset(uint32_t *data, int argc, char **argv) +{ + int i = 0; + do { + errno = 0; + int c = strtol(argv[i], NULL, 10); + if (c >= 0) + data[i] = IPC_MUL_PLUS; + else + data[i] = IPC_MUL_MINUS; + data[i + 2] = abs(c); + i++; + } while (i < argc && errno == 0); + + if (errno != 0) + return false; + else + return true; +} + +static bool +fn_naturals(uint32_t *data, int argc, char **argv) +{ + int i = 0; + do { + errno = 0; + data[i] = strtol(argv[i], NULL, 10); + i++; + } while (i < argc && errno == 0); + + if (errno != 0) + return false; + else + return true; +} + +struct Command { + char *string_command; + enum IPCCommand command; + int argc; + bool (*handler)(uint32_t *, int , char **); +}; + +/* vim-tabularize is cool, i swear */ +static struct Command c[] = { + { "window_move" , IPCWindowMove , 2 , fn_offset } , + { "window_move_absolute" , IPCWindowMoveAbsolute , 2 , fn_offset } , + { "window_resize" , IPCWindowResize , 2 , fn_offset } , + { "window_resize_absolute" , IPCWindowResizeAbsolute , 2 , fn_naturals } , + { "window_maximize" , IPCWindowMaximize , 0 , NULL } , + { "window_hor_maximize" , IPCWindowHorMaximize , 0 , NULL } , + { "window_ver_maximize" , IPCWindowVerMaximize , 0 , NULL } , + { "window_close" , IPCWindowClose , 0 , NULL } , + { "window_put_in_grid" , IPCWindowPutInGrid , 4 , fn_naturals } , + { "window_snap" , IPCWindowSnap , 1 , fn_naturals } , + { "window_cycle" , IPCWindowCycle , 0 , NULL } , + { "window_rev_cycle" , IPCWindowRevCycle , 0 , NULL } , + { "group_add_window" , IPCGroupAddWindow , 1 , fn_naturals } , + { "group_remove_window" , IPCGroupRemoveWindow , 0 , NULL } , + { "group_activate" , IPCGroupActivate , 1 , fn_naturals } , + { "group_deactivate" , IPCGroupDeactivate , 1 , fn_naturals } , + { "group_toggle" , IPCGroupToggle , 1 , fn_naturals } , + { "wm_quit" , IPCWMQuit , 1 , fn_naturals } , + { "wm_change_nr_of_groups" , IPCWMChangeNrOfGroups , 1 , fn_naturals } , + +}; + +xcb_connection_t *conn; +xcb_screen_t *scr; + +static void +init_xcb(xcb_connection_t **conn) +{ + *conn = xcb_connect(NULL, NULL); + if (xcb_connection_has_error(*conn)) + errx(EXIT_FAILURE, "unable to connect to X server"); + scr = xcb_setup_roots_iterator(xcb_get_setup(*conn)).data; +} + +static xcb_atom_t +get_atom(char *name) +{ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, 0, strlen(name), name); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, cookie, NULL); + + if (!reply) + return XCB_ATOM_STRING; + + return reply->atom; +} + +static void +send_command(struct Command *c, int argc, char **argv) +{ + xcb_client_message_event_t msg; + xcb_client_message_data_t data; + xcb_generic_error_t *err; + xcb_void_cookie_t cookie; + bool status = true; + size_t str_size; + + msg.response_type = XCB_CLIENT_MESSAGE; + msg.type = get_atom(ATOM_COMMAND); + msg.format = 32; + data.data32[0] = c->command; + if (c->handler != NULL) + status = (c->handler)(data.data32 + 1, argc, argv); + if (status == false) + errx(EXIT_FAILURE, "malformed input"); + + msg.data = data; + + cookie = xcb_send_event_checked(conn, false, scr->root, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&msg); + + err = xcb_request_check(conn, cookie); + if (err) + fprintf(stderr, "oops %d\n", err->error_code); + xcb_flush(conn); +} + +void +usage() +{ + fprintf(stderr, "Usage: %s [args]...\n", __NAME_CLIENT__); + + exit(1); +} + +int main(int argc, char **argv) +{ + int i; + int command_argc; + char **command_argv; + init_xcb(&conn); + + if (argc == 1) + usage(); + + /* argc - program name - command to send */ + command_argc = argc - 2; + command_argv = argv + 2; + + i = 0; + while (i < NR_IPC_COMMANDS && strcmp(argv[1], c[i].string_command) != 0) + i++; + + if (i < NR_IPC_COMMANDS) { + if (command_argc < c[i].argc) + errx(EXIT_FAILURE, "not enough argmuents"); + else if (command_argc > c[i].argc) + warnx("too many arguments"); + else + send_command(&c[i], command_argc, command_argv); + + } else { + errx(EXIT_FAILURE, "no such command"); + } + + if (conn != NULL) + xcb_disconnect(conn); + + return 0; +} diff --git a/common.h b/common.h new file mode 100644 index 0000000..2d4f7a2 --- /dev/null +++ b/common.h @@ -0,0 +1,10 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#ifdef D +#define DMSG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#else +#define DMSG +#endif + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..8a76ec3 --- /dev/null +++ b/config.h @@ -0,0 +1,11 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +#define BORDER_WIDTH 3 +#define COLOR_FOCUS 0x3d637d +#define COLOR_UNFOCUS 0x003b4f +#define GAP 20 +#define CURSOR_POSITION CENTER +#define GROUPS 4 + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..f338be2 --- /dev/null +++ b/config.mk @@ -0,0 +1,6 @@ +PREFIX = /usr/local +MANPREFIX = /usr/local/share +MANDIR = $(MANPREFIX)/man/man1 + +CFLAGS += -std=c99 -Wextra -g +LDFLAGS += -lxcb -lxcb-ewmh -lxcb-icccm -lxcb-randr diff --git a/ipc.h b/ipc.h new file mode 100644 index 0000000..775de80 --- /dev/null +++ b/ipc.h @@ -0,0 +1,40 @@ +#ifndef _WM_IPC_H +#define _WM_IPC_H + +#define ATOM_COMMAND "__WM_IPC_COMMAND" + +#define IPC_MUL_PLUS 0 +#define IPC_MUL_MINUS 1 + +enum IPCCommand { + IPCWindowMove, + IPCWindowMoveAbsolute, + IPCWindowResize, + IPCWindowResizeAbsolute, + IPCWindowMaximize, + IPCWindowHorMaximize, + IPCWindowVerMaximize, + IPCWindowClose, + IPCWindowPutInGrid, + IPCWindowSnap, + IPCWindowCycle, + IPCWindowRevCycle, + IPCGroupAddWindow, + IPCGroupRemoveWindow, + IPCGroupActivate, + IPCGroupDeactivate, + IPCGroupToggle, + IPCWMQuit, + IPCWMChangeNrOfGroups, + NR_IPC_COMMANDS, +}; + +enum SnapPosition { + SnapTopLeft = 0, + SnapTopRight, + SnapBottomLeft, + SnapBottomRight, + SnapMiddle, +}; + +#endif diff --git a/list.c b/list.c new file mode 100644 index 0000000..ab204e7 --- /dev/null +++ b/list.c @@ -0,0 +1,88 @@ +#include + +#include "list.h" + +/* + * Move list item to the beginning (head) of the list. + */ + +void +list_move_to_head(struct list_item **list, struct list_item *item) +{ + if (list == NULL || *list == NULL || *list == item || item == NULL) + return; + + /* fill the hole */ + item->prev->next = item->next; + item->next->prev = item->prev; + + /* we are at the head, nothing is behind us now */ + item->prev = NULL; + item->next = *list; + item->next->prev = item; + *list = item; +} + +/* + * Allocate item at the head of the list. + */ + +struct list_item* +list_add_item(struct list_item **list) +{ + struct list_item *item; + + item = malloc(sizeof(struct list_item)); + if (item == NULL) + return NULL; + + if (*list == NULL) { + item->prev = item->next = NULL; + } else { + item->prev = NULL; + item->next = *list; + item->next->prev = item; + } + + *list = item; + + return item; +} + +/* + * Delete item from the list. + * Data must be freed manually. + */ + +void +list_delete_item(struct list_item **list, struct list_item *item) +{ + if (list == NULL || *list == NULL || item == NULL) + return; + + if (*list == item) { + *list = item->next; + } else { + item->prev->next = item->next; + if (item->next != NULL) + item->next->prev = item->prev; + } + + free(item); +} + +/* + * Deletes a whole list. + */ + +void +list_delete_all_items(struct list_item **list) +{ + struct list_item *item, *next; + + for (item = *list; item->next != NULL; item = next) { + next = item->next; + free(item->data); + list_delete_item(list, item); + } +} diff --git a/list.h b/list.h new file mode 100644 index 0000000..3a4bda7 --- /dev/null +++ b/list.h @@ -0,0 +1,15 @@ +#ifndef _LIST_H +#define _LIST_H + +struct list_item { + void *data; + struct list_item *prev; + struct list_item *next; +}; + +void list_move_to_head(struct list_item **, struct list_item *); +struct list_item* list_add_item(struct list_item **); +void list_delete_item(struct list_item **, struct list_item *); +void list_delete_all_items(struct list_item **); + +#endif diff --git a/types.h b/types.h new file mode 100644 index 0000000..cedeaf2 --- /dev/null +++ b/types.h @@ -0,0 +1,46 @@ +#ifndef _TYPES_H +#define _TYPES_H + +enum cursor_position { + BOTTOM_LEFT, + BOTTOM_RIGHT, + TOP_LEFT, + TOP_RIGHT, + CENTER +}; + +struct window_geom { + int16_t x, y; + uint16_t width, height; + bool set_by_user; +}; + +struct client { + xcb_window_t window; + struct window_geom geom; + struct window_geom orig_geom; + bool maxed, hmaxed, vmaxed; + struct list_item *item; + struct monitor *monitor; + uint16_t min_width, min_height; + uint16_t max_width, max_height; + bool mapped; + uint32_t group; +}; + +struct monitor { + xcb_randr_output_t monitor; + char *name; + int16_t x, y; + uint16_t width, height; + struct list_item *item; +}; + +struct conf { + int8_t border_width, gap; + uint32_t focus_color, unfocus_color; + enum cursor_position cursor_position; + uint32_t groups; +}; + +#endif diff --git a/wm.c b/wm.c new file mode 100644 index 0000000..2723afc --- /dev/null +++ b/wm.c @@ -0,0 +1,2021 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "ipc.h" +#include "list.h" +#include "types.h" +#include "config.h" + +#ifndef __NAME__ +#define __NAME__ "wm" +#endif + +#define EVENT_MASK(ev) ((ev & ~0x80)) +/* XCB event with the biggest values */ +#define LAST_XCB_EVENT XCB_GET_MODIFIER_MAPPING +#define NULL_GROUP 0xffffffff + +/* atoms identifiers */ +enum { WM_DELETE_WINDOW, _IPC_ATOM_COMMAND, NR_ATOMS }; + +/* connection to the X server */ +static xcb_connection_t *conn; +static xcb_ewmh_connection_t*ewmh; +static xcb_screen_t *scr; +static struct client *focused_win; +static struct conf conf; +/* number of the screen we're using */ +static int scrno; +/* base for checking randr events */ +static int randr_base; +static bool halt; +static int exit_code; +static bool *group_in_use; +/* list of all windows. NULL is the empty list */ +static struct list_item *win_list = NULL; +static struct list_item *mon_list = NULL; +static char *atom_names[NR_ATOMS] = { + "WM_DELETE_WINDOW", + ATOM_COMMAND, +}; +static xcb_atom_t ATOMS[NR_ATOMS]; +/* function handlers for ipc commands */ +static void (*ipc_handlers[NR_IPC_COMMANDS])(uint32_t *); +/* function handlers for events received from the X server */ +static void (*events[LAST_XCB_EVENT + 1])(xcb_generic_event_t *); + +static void cleanup(void); +static int setup(void); +static int setup_randr(void); +static void get_randr(void); +static void get_outputs(xcb_randr_output_t *, int len, xcb_timestamp_t); +static struct monitor * find_monitor(xcb_randr_output_t); +static struct monitor * find_monitor_by_coord(int16_t, int16_t); +static struct monitor * find_clones(xcb_randr_output_t, int16_t, int16_t); +static struct monitor * add_monitor(xcb_randr_output_t, char *, int16_t, int16_t, uint16_t, uint16_t); +static void free_monitor(struct monitor *); +static void get_monitor_size(struct client *, int16_t *, int16_t *, uint16_t *, uint16_t *); +static void arrange_by_monitor(struct monitor *); +static void handle_events(void); +static struct client * setup_window(xcb_window_t); +static void set_focused_no_raise(struct client *); +static void set_focused(struct client *); +static void raise_window(xcb_window_t); +static void close_window(struct client *); +static void delete_window(xcb_window_t); +static void teleport_window(xcb_window_t, int16_t, int16_t); +static void move_window(xcb_window_t , int16_t, int16_t); +static void resize_window_absolute(xcb_window_t, uint16_t, uint16_t); +static void resize_window(xcb_window_t, int16_t, int16_t); +static void resize_window_limit(struct client *); +static void fit_on_screen(struct client *); +static void maximize_window(struct client *, int16_t, int16_t, uint16_t, uint16_t); +static void hmaximize_window(struct client *, int16_t, uint16_t); +static void vmaximize_window(struct client *, int16_t, uint16_t); +static void unmaximize_window(struct client *); +static void cycle_window(struct client *); +static void rcycle_window(struct client *); +static void save_original_size(struct client *); +static xcb_atom_t get_atom(char *); +static bool get_pointer_location(xcb_window_t *, int16_t *, int16_t *); +static void center_pointer(struct client *); +static struct client * find_client(xcb_window_t *); +static bool get_geometry(xcb_window_t *, int16_t *, int16_t *, uint16_t *, uint16_t *); +static void set_borders(struct client *client, uint32_t); +static bool is_mapped(xcb_window_t); +static uint16_t get_border_width(xcb_window_t); +static void free_window(struct client *); +static void add_to_client_list(xcb_window_t); +static void update_client_list(void); +static void update_wm_desktop(struct client *); +static void group_add_window(struct client *, uint32_t); +static void group_remove_window(struct client *); +static void group_activate(uint32_t); +static void group_deactivate(uint32_t); +static void group_toggle(uint32_t); +static void change_nr_of_groups(uint32_t); +static void register_event_handlers(void); +static void event_configure_request(xcb_generic_event_t *); +static void event_destroy_notify(xcb_generic_event_t *); +static void event_enter_notify(xcb_generic_event_t *); +static void event_map_request(xcb_generic_event_t *); +static void event_map_notify(xcb_generic_event_t *); +static void event_unmap_notify(xcb_generic_event_t *); +static void event_configure_notify(xcb_generic_event_t *); +static void event_circulate_request(xcb_generic_event_t *); +static void event_client_message(xcb_generic_event_t *); +static void register_ipc_handlers(void); +static void ipc_window_move(uint32_t *); +static void ipc_window_move_absolute(uint32_t *); +static void ipc_window_resize(uint32_t *); +static void ipc_window_resize_absolute(uint32_t *); +static void ipc_window_maximize(uint32_t *); +static void ipc_window_hor_maximize(uint32_t *); +static void ipc_window_ver_maximize(uint32_t *); +static void ipc_window_unmaximize(uint32_t *); +static void ipc_window_close(uint32_t *); +static void ipc_window_put_in_grid(uint32_t *); +static void ipc_window_snap(uint32_t *); +static void ipc_window_cycle(uint32_t *); +static void ipc_window_rev_cycle(uint32_t *); +static void ipc_group_add_window(uint32_t *); +static void ipc_group_remove_window(uint32_t *); +static void ipc_group_activate(uint32_t *); +static void ipc_group_deactivate(uint32_t *); +static void ipc_group_toggle(uint32_t *); +static void ipc_wm_quit(uint32_t *); +static void ipc_wm_change_nr_of_groups(uint32_t *); + +/* + * Gracefully disconnect. + */ + +static void +cleanup(void) +{ + if (ewmh != NULL) + xcb_ewmh_connection_wipe(ewmh); + if (conn != NULL) + xcb_disconnect(conn); + if (win_list != NULL) + list_delete_all_items(&win_list); +} + +/* + * Connect to the X server and initialize some things. + */ + +static int +setup(void) +{ + /* init xcb and grab events */ + unsigned int values[1]; + int mask; + + conn = xcb_connect(NULL, &scrno); + if (xcb_connection_has_error(conn)) { + return -1; + } + + /* get the first screen. hope it's the last one too */ + scr = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + focused_win = NULL; + + mask = XCB_CW_EVENT_MASK; + values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | \ + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; + xcb_generic_error_t *e = xcb_request_check(conn, + xcb_change_window_attributes_checked(conn, scr->root, + mask, values)); + if (e != NULL) + errx(EXIT_FAILURE, "Another window manager is already running."); + + /* initialize ewmh variables */ + ewmh = calloc(1, sizeof(xcb_ewmh_connection_t)); + if (!ewmh) + warnx("couldn't set up ewmh connection"); + xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(conn, ewmh); + xcb_ewmh_init_atoms_replies(ewmh, cookie, (void *)0); + xcb_ewmh_set_wm_pid(ewmh, scr->root, getpid()); + xcb_ewmh_set_wm_name(ewmh, scr->root, strlen(__NAME__), __NAME__); + xcb_ewmh_set_current_desktop(ewmh, 0, 0); + xcb_ewmh_set_number_of_desktops(ewmh, 0, GROUPS); + + xcb_atom_t supported_atoms[] = { + ewmh->_NET_SUPPORTED , ewmh->_NET_WM_DESKTOP , + ewmh->_NET_NUMBER_OF_DESKTOPS , ewmh->_NET_CURRENT_DESKTOP , + ewmh->_NET_ACTIVE_WINDOW , ewmh->_NET_WM_STATE , + ewmh->_NET_WM_STATE_FULLSCREEN , ewmh->_NET_WM_NAME , + ewmh->_NET_WM_ICON_NAME , ewmh->_NET_WM_WINDOW_TYPE , + ewmh->_NET_WM_WINDOW_TYPE_DOCK , ewmh->_NET_WM_PID , + ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR , + }; + xcb_ewmh_set_supported(ewmh, scrno, sizeof(supported_atoms) / sizeof(xcb_atom_t), supported_atoms); + + /* send requests */ + xcb_flush(conn); + + /* get various atoms for icccm and ewmh */ + for (int i = 0; i < NR_ATOMS; i++) + ATOMS[i] = get_atom(atom_names[i]); + + randr_base = setup_randr(); + + conf.border_width = BORDER_WIDTH; + conf.focus_color = COLOR_FOCUS; + conf.unfocus_color = COLOR_UNFOCUS; + conf.gap = GAP; + conf.cursor_position = CURSOR_POSITION; + conf.groups = GROUPS; + + group_in_use = malloc(conf.groups * sizeof(bool)); + for (uint32_t i = 0; i < conf.groups; i++) + group_in_use[i] = false; + + return 0; +} + +static int +setup_randr(void) +{ + int base; + const xcb_query_extension_reply_t *r = xcb_get_extension_data(conn, &xcb_randr_id); + + if (!r->present) + return -1; + else + get_randr(); + + base = r->first_event; + xcb_randr_select_input(conn, scr->root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE + | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE + | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE + | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); + + return base; +} + +static void +get_randr(void) +{ + int len; + xcb_randr_get_screen_resources_current_cookie_t c + = xcb_randr_get_screen_resources_current(conn, scr->root); + xcb_randr_get_screen_resources_current_reply_t *r + = xcb_randr_get_screen_resources_current_reply(conn, c, NULL); + + if (r == NULL) + return; + + xcb_timestamp_t timestamp = r->config_timestamp; + len = xcb_randr_get_screen_resources_current_outputs_length(r); + xcb_randr_output_t *outputs + = xcb_randr_get_screen_resources_current_outputs(r); + + /* Request information for all outputs */ + get_outputs(outputs, len, timestamp); + free(r); +} + +static void +get_outputs(xcb_randr_output_t *outputs, int len, xcb_timestamp_t timestamp) +{ + int name_len; + char *name; + xcb_randr_get_crtc_info_cookie_t info_c; + xcb_randr_get_crtc_info_reply_t *crtc; + xcb_randr_get_output_info_reply_t *output; + struct monitor *mon, *clonemon; + struct list_item *item; + xcb_randr_get_output_info_cookie_t out_cookie[len]; + + for (int i = 0; i < len; i++) + out_cookie[i] = xcb_randr_get_output_info(conn, outputs[i], + timestamp); + + for (int i = 0; i < len; i++) { + output = xcb_randr_get_output_info_reply(conn, out_cookie[i], NULL); + if (output == NULL) + continue; + + name_len = xcb_randr_get_output_info_name_length(output); + if (16 < name_len) + name_len = 16; + + /* +1 for the null character */ + name = malloc(name_len + 1); + /* make sure the name is at most name_len + 1 length + * or we may run into problems. */ + snprintf(name, name_len + 1, "%.*s", name_len, + xcb_randr_get_output_info_name(output)); + + if (output->crtc != XCB_NONE) { + info_c = xcb_randr_get_crtc_info(conn, output->crtc, + timestamp); + crtc = xcb_randr_get_crtc_info_reply(conn, info_c, NULL); + + if (crtc == NULL) + return; + + clonemon = find_clones(outputs[i], crtc->x, crtc->y); + if (clonemon != NULL) + continue; + + mon = find_monitor(outputs[i]); + if (mon == NULL) { + add_monitor(outputs[i], name, crtc->x, crtc->y, + crtc->width, crtc->height); + } else { + mon->x = crtc->x; + mon->y = crtc->y; + mon->width = crtc->width; + mon->height = crtc->height; + + arrange_by_monitor(mon); + } + + free(crtc); + } else { + /* Check if the monitor was used before + * becoming disabled. */ + mon = find_monitor(outputs[i]); + if (mon) { + struct client *client; + for (item = win_list; item != NULL; item = item->next) { + /* Move window from this monitor to + * either the next one or the first one. */ + client = item->data; + + if (client->monitor == mon) { + if (client->monitor->item->next) + // If at end, take from the beginning + if (mon_list == NULL) + client->monitor = NULL; + else + client->monitor = mon_list->data; + else + client->monitor = client->monitor->item->next->data; + fit_on_screen(client); + } + } + + /* Monitor not active. Delete it. */ + free_monitor(mon); + } + } + + if (output != NULL) + free(output); + free(name); + } +} + +struct monitor * +find_monitor(xcb_randr_output_t mon) +{ + struct list_item *item; + struct monitor *m; + + item = mon_list; + while (item != NULL && (m = item->data)->monitor != mon) + item = item->next; + + if (item == NULL) + return NULL; + else + return item->data; +} + +static struct monitor * +find_monitor_by_coord(int16_t x, int16_t y) +{ + struct list_item *item; + struct monitor *m = NULL; + + for (item = mon_list; item != NULL; item = item->next) { + m = item->data; + + if (x >= m->x && x <= m->x + m->width + && y >= m->y && y <= m->y + m->height) + break; + } + + return m; +} + +struct monitor * +find_clones(xcb_randr_output_t mon, int16_t x, int16_t y) +{ + struct monitor *clonemon; + struct list_item *item; + + item = mon_list; + while (item != NULL && ((clonemon = item->data)->monitor == mon + || clonemon->x != x + || clonemon->y != y)) { + item = item->next; + } + + if (item == NULL) + return NULL; + else + return clonemon; +} + +static struct monitor * +add_monitor(xcb_randr_output_t mon, char *name, int16_t x, int16_t y, uint16_t width, uint16_t height) +{ + struct list_item *item; + struct monitor *monitor = malloc(sizeof(struct monitor)); + + item = list_add_item(&mon_list); + if (item == NULL || monitor == NULL) + return NULL; + + item->data = monitor; + monitor->item = item; + monitor->monitor = mon; + monitor->name = name; + monitor->x = x; + monitor->y = y; + monitor->width = width; + monitor->height = height; + + return monitor; +} + +static void +free_monitor(struct monitor *mon) +{ + struct list_item *item = mon->item; + + free(mon); + list_delete_item(&mon_list, item); +} + +static void +get_monitor_size(struct client *client, int16_t *mon_x, int16_t *mon_y, uint16_t *mon_width, uint16_t *mon_height) +{ + if (client == NULL || client->monitor == NULL) { + if (mon_x != NULL && mon_y != NULL) + *mon_x = *mon_y = 0; + if (mon_width != NULL) + *mon_width = scr->width_in_pixels; + if (mon_height != NULL) + *mon_height = scr->height_in_pixels; + } else { + if (mon_x != NULL) + *mon_x = client->monitor->x; + if (mon_y != NULL) + *mon_y = client->monitor->y; + if (mon_width != NULL) + *mon_width = client->monitor->width; + if (mon_height != NULL) + *mon_height = client->monitor->height; + } +} + +static void +arrange_by_monitor(struct monitor *mon) +{ + struct client *client; + struct list_item *item; + + for (item = win_list; item != NULL; item = item->next) { + client = item->data; + + if (client->monitor == mon) + fit_on_screen(client); + } +} + +/* + * Waits for events and handles them. + */ + +static void +run(void) +{ + xcb_generic_event_t *ev; + + halt = false; + exit_code = 0; + while (!halt) { + xcb_flush(conn); + ev = xcb_wait_for_event(conn); + if (ev) { + DMSG("%d\n", ev->response_type & ~0x80); + if (ev->response_type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { + get_randr(); + DMSG("Screen layout changed\n"); + } + if (events[EVENT_MASK(ev->response_type)] != NULL) + (events[EVENT_MASK(ev->response_type)])(ev); + free(ev); + } + } +} + +/* + * Initialize a window for further work. + */ + +static struct client * +setup_window(xcb_window_t win) +{ + uint32_t values[2]; + xcb_ewmh_get_atoms_reply_t win_type; + xcb_atom_t atom; + struct client *client; + struct list_item *item; + xcb_size_hints_t hints; + + if (xcb_ewmh_get_wm_window_type_reply(ewmh, + xcb_ewmh_get_wm_window_type(ewmh, win), + &win_type, NULL) == 1) { + unsigned int i = 0; + /* if the window is a toolbar or a dock, map it and ignore it */ + while (i < win_type.atoms_len && + (atom = win_type.atoms[i]) != ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR && + atom != ewmh->_NET_WM_WINDOW_TYPE_DOCK) + i++; + + if (i < win_type.atoms_len) { + xcb_ewmh_get_atoms_reply_wipe(&win_type); + xcb_map_window(conn, win); + return NULL; + } + } + + /* subscribe to events */ + values[0] = XCB_EVENT_MASK_ENTER_WINDOW; + xcb_change_window_attributes(conn, win, XCB_CW_EVENT_MASK, values); + + /* in case of fire */ + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, win); + + /* assign to the null group */ + xcb_ewmh_set_wm_desktop(ewmh, win, NULL_GROUP); + + item = list_add_item(&win_list); + if (item == NULL) + return NULL; + + client = malloc(sizeof(struct client)); + if (client == NULL) + return NULL; + + item->data = client; + client->item = item; + client->window = win; + client->geom.x = client->geom.y = client->geom.width + = client->geom.height + = client->min_width = client->min_height + = client->max_width = client->max_height = 0; + client->max_width = scr->width_in_pixels; + client->max_height = scr->height_in_pixels; + client->maxed = client->hmaxed = client->vmaxed = client->geom.set_by_user = false; + client->monitor = NULL; + client->mapped = false; + client->group = NULL_GROUP; + get_geometry(&client->window, &client->geom.x, &client->geom.y, + &client->geom.width, &client->geom.height); + + xcb_icccm_get_wm_normal_hints_reply(conn, + xcb_icccm_get_wm_normal_hints_unchecked(conn, win), + &hints, NULL); + + if (hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION) + client->geom.set_by_user = true; + + if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { + client->min_width = hints.min_width; + client->min_height = hints.min_height; + } + + if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) { + client->max_width = hints.max_width; + client->max_height = hints.max_height; + } + + return client; +} + +/* + * Set focus state to active or inactive without raising the window. + */ + +static void +set_focused_no_raise(struct client *client) +{ + long data[] = { + XCB_ICCCM_WM_STATE_NORMAL, + XCB_NONE, + }; + if (client == NULL) + return; + + if (!client->maxed) + set_borders(client, conf.focus_color); + + /* revert state to normal */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->window, + ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE, 32, 2, data); + + /* focus the window */ + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, + client->window, XCB_CURRENT_TIME); + + /* set ewmh property */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, scr->root, + ewmh->_NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &client->window); + + /* set the focus state to inactive on the previously focused window */ + if (client != focused_win) { + if (focused_win != NULL && !focused_win->maxed) + set_borders(focused_win, conf.unfocus_color); + focused_win = client; + } +} + +/* + * Focus and raise. + */ + +static void +set_focused(struct client *client) +{ + set_focused_no_raise(client); + raise_window(client->window); +} + +/* + * Put window at the top of the window stack. + */ + +static void +raise_window(xcb_window_t win) +{ + uint32_t values[1] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values); +} + +/* + * Ask window to close gracefully. If the window doesn't respond, kill it. + */ + +static void +close_window(struct client *client) +{ + if (client == NULL) + return; + + xcb_window_t win = client->window; + xcb_get_property_cookie_t cookie = + xcb_icccm_get_wm_protocols_unchecked(conn, + win, ewmh->WM_PROTOCOLS); + xcb_icccm_get_wm_protocols_reply_t reply; + unsigned int i = 0; + bool got = false; + + if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &reply, NULL)) { + for (i = 0; i < reply.atoms_len; i++) { + got = (reply.atoms[i] = ATOMS[WM_DELETE_WINDOW]); + if (got) + break; + } + + xcb_icccm_get_wm_protocols_reply_wipe(&reply); + } + + if (got) + delete_window(win); + else + xcb_kill_client(conn, win); + + free_window(client); +} + +/* + * Gracefully ask a window to close. + */ + +static void +delete_window(xcb_window_t win) +{ + xcb_client_message_event_t ev; + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.sequence = 0; + ev.format = 32; + ev.window = win; + ev.type = ewmh->WM_PROTOCOLS; + ev.data.data32[0] = ATOMS[WM_DELETE_WINDOW]; + ev.data.data32[1] = XCB_CURRENT_TIME; + + xcb_send_event(conn, 0, win, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); +} + +/* + * Teleports window absolutely to the given coordinates. + */ + +static void +teleport_window(xcb_window_t win, int16_t x, int16_t y) +{ + uint32_t values[2] = {x, y}; + + if (win == scr->root || win == 0) + return; + + xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); + + xcb_flush(conn); +} + +/* + * Moves the window by a certain amount. + */ + +static void +move_window(xcb_window_t win, int16_t x, int16_t y) +{ + int16_t win_x, win_y; + uint16_t win_w, win_h, win_b; + + if (!is_mapped(win) || win == scr->root) + return; + + win_b = get_border_width(win); + get_geometry(&win, &win_x, &win_y, &win_w, &win_h); + + win_x += x; + win_y += y; + + teleport_window(win, win_x, win_y); +} + +/* + * Resizes window to the given size. + */ + +static void +resize_window_absolute(xcb_window_t win, uint16_t w, uint16_t h) +{ + uint16_t val[2]; + uint16_t mask = XCB_CONFIG_WINDOW_WIDTH + | XCB_CONFIG_WINDOW_HEIGHT; + + val[0] = w; + val[1] = h; + + xcb_configure_window(conn, win, mask, val); +} + +/* + * Resizes window by a certain amount. + */ + +static void +resize_window(xcb_window_t win, int16_t w, int16_t h) +{ + uint16_t win_w, win_h; + + get_geometry(&win, NULL, NULL, &win_w, &win_h); + resize_window_absolute(win, win_w + w, win_h + h); +} + +static void +resize_window_limit(struct client *client) +{ + int16_t mon_x, mon_y; + uint16_t mon_width, mon_height; + + get_monitor_size(client, &mon_x, &mon_y, &mon_width, &mon_height); +} + +static void +fit_on_screen(struct client *client) +{ + int16_t mon_x, mon_y; + uint16_t mon_width, mon_height; + bool will_resize, will_move; + + will_resize = will_move = false; + client->hmaxed = client->vmaxed = false; + get_monitor_size(client, &mon_x, &mon_y, &mon_width, &mon_height); + if (client->maxed) { + client->maxed = false; + } else if (client->geom.width == mon_width && client->geom.height == mon_height) { + client->geom.x = mon_x; + client->geom.y = mon_y; + client->geom.width -= 2 * conf.border_width; + client->geom.height -= 2 * conf.border_width; + maximize_window(client, mon_x, mon_y, mon_width, mon_height); + return; + } + + /* Is it outside the display? */ + if (client->geom.x > mon_x + mon_width || client->geom.y > mon_y + mon_height + || client->geom.x < mon_x || client->geom.y < mon_y) { + will_move = true; + if (client->geom.x > mon_x + mon_width) + client->geom.x = mon_x + mon_width - client->geom.width - 2 * conf.border_width; + else if (client->geom.x < mon_x) + client->geom.x = mon_x; + if (client->geom.y > mon_y + mon_height) + client->geom.y = mon_y + mon_height - client->geom.height - 2 * conf.border_width; + else if (client->geom.y < mon_y) + client->geom.y = mon_y; + } + + /* Is it smaller than it wants to be? */ + if (client->min_width != 0 && client->geom.width < client->min_width) { + client->geom.width = client->min_width; + will_resize = true; + } + if (client->min_height != 0 && client->geom.height < client->min_height) { + client->geom.height = client->min_height; + + will_resize = true; + } + + /* If the window is larger than the screen or is a bit in the outside, + * move it to the corner and resize it accordingly. */ + if (client->geom.width + 2 * conf.border_width > mon_width) { + client->geom.x = mon_x; + client->geom.width = mon_width - 2 * conf.border_width; + will_move = will_resize = true; + } else if (client->geom.x + client->geom.width + 2 * conf.border_width + > mon_x + mon_width) { + client->geom.x = mon_x + mon_width - client->geom.width - 2 * conf.border_width; + will_move = true; + } + + if (client->geom.height + 2 * conf.border_width > mon_height) { + client->geom.y = mon_y; + client->geom.height = mon_height - 2 * conf.border_width; + will_move = will_resize = true; + } else if (client->geom.y + client->geom.height + 2 * conf.border_width + > mon_y + mon_height) { + client->geom.y = mon_y + mon_height - client->geom.height - 2 * conf.border_width; + will_move = true; + } + + if (will_move) + teleport_window(client->window, client->geom.x, client->geom.y); + if (will_resize) + resize_window_absolute(client->window, client->geom.width, client->geom.height); +} + +static void +maximize_window(struct client *client, int16_t mon_x, int16_t mon_y, uint16_t mon_width, uint16_t mon_height) +{ + uint32_t values[1]; + + if ((client->max_width != 0 && mon_width > client->max_width) + || (client->max_height != 0 && mon_height > client->max_height)) + return; + + /* maximized windows don't have borders */ + values[0] = 0; + if (client->geom.width != mon_width || client->geom.height != mon_height) + save_original_size(client); + xcb_configure_window(conn, client->window, XCB_CONFIG_WINDOW_BORDER_WIDTH, + values); + + client->geom.x = mon_x; + client->geom.y = mon_y; + client->geom.width = mon_width; + client->geom.height = mon_height; + client->vmaxed = client->hmaxed = false; + + teleport_window(client->window, client->geom.x, client->geom.y); + resize_window_absolute(client->window, client->geom.width, client->geom.height); + client->maxed = true; +} + +static void +hmaximize_window(struct client *client, int16_t mon_x, uint16_t mon_width) { + if (client->max_width != 0 && mon_width > client->max_width) + return; + + if (client->geom.width != mon_width) + save_original_size(client); + client->geom.x = mon_x + conf.gap - conf.border_width; + client->geom.width = mon_width - 2 * conf.gap; + client->vmaxed = client->maxed = false; + + teleport_window(client->window, client->geom.x, client->geom.y); + resize_window_absolute(client->window, client->geom.width, client->geom.height); + client->hmaxed = true; +} + +static void +vmaximize_window(struct client *client, int16_t mon_y, uint16_t mon_height) { + if (client->max_height != 0 && mon_height > client->max_height) + return; + + if (client->geom.height != mon_height) + save_original_size(client); + + client->geom.y = mon_y + conf.gap - conf.border_width; + client->geom.height = mon_height - 2 * conf.gap; + client->hmaxed = client->maxed = false; + + teleport_window(client->window, client->geom.x, client->geom.y); + resize_window_absolute(client->window, client->geom.width, client->geom.height); + client->vmaxed = true; +} + +static void +unmaximize_window(struct client *client) { + client->geom.x = client->orig_geom.x; + client->geom.y = client->orig_geom.y; + client->geom.width = client->orig_geom.width; + client->geom.height = client->orig_geom.height; + client->maxed = client->hmaxed = client->vmaxed = false; + + teleport_window(client->window, client->geom.x, client->geom.y); + resize_window_absolute(client->window, client->geom.width, client->geom.height); +} + +static void +cycle_window(struct client *client) +{ + struct list_item *item; + struct client *data; + + item = win_list; + if (client != NULL) + while (item != NULL && item->data != client) + item = item->next; + + /* if item is not found item will be null + * and we'll get a nice segmentation fault. may the debugger be with you */ + if (item != NULL) + do { + item = item->next; + if (item == NULL) + item = win_list; + data = item->data; + } while (item != NULL && !data->mapped); + + if (item != NULL && item->data != client) + set_focused(item->data); +} + +static void +rcycle_window(struct client *client) +{ + struct list_item *item; + struct list_item *last_item; + struct client *data; + + item = win_list; + if (client != NULL) { + while (item != NULL && item->data != client) + item = item->next; + + last_item = item; + while (last_item->next != NULL) + last_item = last_item->next; + + do { + item = item->prev; + if (item == NULL) + item = last_item; + data = item->data; + } while (item != NULL && !data->mapped); + } + + if (item != NULL && item->data != client) + set_focused(item->data); +} + +static void +save_original_size(struct client *client) +{ + client->orig_geom.x = client->geom.x; + client->orig_geom.y = client->geom.y; + client->orig_geom.width = client->geom.width; + client->orig_geom.height = client->geom.height; +} + +/* + * Get atom by name. + */ + +static xcb_atom_t +get_atom(char *name) +{ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, 0, strlen(name), name); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, cookie, NULL); + + if (!reply) + return XCB_ATOM_STRING; + + return reply->atom; +} + +/* + * Get the mouse pointer's coordinates. + */ + +static bool +get_pointer_location(xcb_window_t *win, int16_t *x, int16_t *y) +{ + xcb_query_pointer_reply_t *pointer; + + pointer = xcb_query_pointer_reply(conn, + xcb_query_pointer(conn, *win), 0); + + *x = pointer->win_x; + *y = pointer->win_y; + + free(pointer); + + return pointer != NULL; +} + +static void +center_pointer(struct client *client) +{ + int16_t cur_x, cur_y; + + cur_x = cur_y = 0; + + switch (conf.cursor_position) { + case TOP_LEFT: + cur_x = 0; + cur_y = 0; + break; + case TOP_RIGHT: + cur_x = client->geom.width; + cur_y = 0; + break; + case BOTTOM_LEFT: + cur_x = client->geom.width; + break; + case BOTTOM_RIGHT: + cur_x = client->geom.width; + cur_y = client->geom.height; + break; + case CENTER: + cur_x = client->geom.width / 2; + cur_y = client->geom.height / 2; + } + + xcb_warp_pointer(conn, XCB_NONE, client->window, 0, 0, 0, 0, cur_x, cur_y); + xcb_flush(conn); +} + +/* + * Get the client instance with a given window id. + */ + +static struct client* +find_client(xcb_window_t *win) +{ + struct list_item *item; + + item = win_list; + while (item != NULL && ((struct client *)item->data)->window != *win) + item = item ->next; + + if (item == NULL) + return NULL; + else + return item->data; +} + +/* + * Get a window's geometry. + */ + +static bool +get_geometry(xcb_window_t *win, int16_t *x, int16_t *y, uint16_t *width, uint16_t *height) +{ + xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, *win), NULL); + + if (reply == NULL) + return false; + if (x != NULL) + *x = reply->x; + if (y != NULL) + *y = reply->y; + if (width != NULL) + *width = reply->width; + if (height != NULL) + *height = reply->height; + + free(reply); + return true; +} + +/* + * Set the color of the border. + */ + +static void +set_borders(struct client *client, uint32_t color) { + if (client == NULL) + return; + uint32_t values[1]; + values[0] = conf.border_width; + xcb_configure_window(conn, client->window, + XCB_CONFIG_WINDOW_BORDER_WIDTH, values); + values[0] = color; + xcb_change_window_attributes(conn, client->window, XCB_CW_BORDER_PIXEL, values); +} + +/* + * Returns true if window is mapped. + */ + +static bool +is_mapped(xcb_window_t win) +{ + bool yes; + xcb_get_window_attributes_reply_t *r = + xcb_get_window_attributes_reply(conn, + xcb_get_window_attributes(conn, win), + NULL); + if (r == NULL) + return false; + + yes = r->map_state == XCB_MAP_STATE_VIEWABLE; + free(r); + + return yes; +} + +/* + * Returns the widht of the window border in pixels. + */ + +static uint16_t +get_border_width(xcb_window_t win) +{ + xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(conn, + xcb_get_geometry(conn, win), + NULL); + uint16_t w = r->border_width; + + free(r); + + return w; +} + +/* + * Deletes and frees a client from the list. + */ + +static void +free_window(struct client *client) +{ + struct list_item *item; + + item = client->item; + + free(client); + list_delete_item(&win_list, item); +} + +/* + * Add window to the ewmh client list. + */ + +static void +add_to_client_list(xcb_window_t win) +{ + xcb_change_property(conn, XCB_PROP_MODE_APPEND, scr->root, + ewmh->_NET_CLIENT_LIST, XCB_ATOM_WINDOW, 32, 1, &win); + xcb_change_property(conn, XCB_PROP_MODE_APPEND, scr->root, ewmh->_NET_CLIENT_LIST_STACKING, XCB_ATOM_WINDOW, 32, 1, &win); +} + +/* + * Adds all windows to the ewmh client list. + */ + +static void +update_client_list(void) +{ + xcb_window_t *children; + struct client *client; + uint32_t len; + + xcb_query_tree_reply_t *reply = xcb_query_tree_reply(conn, + xcb_query_tree(conn, scr->root), NULL); + xcb_delete_property(conn, scr->root, ewmh->_NET_CLIENT_LIST); + xcb_delete_property(conn, scr->root, ewmh->_NET_CLIENT_LIST_STACKING); + + if (reply == NULL) { + add_to_client_list(0); + return; + } + + len = xcb_query_tree_children_length(reply); + children = xcb_query_tree_children(reply); + + for (unsigned int i = 0; i < len; i++) { + client = find_client(&children[i]); + if (client != NULL) + add_to_client_list(client->window); + } + + free(reply); +} + +static void +update_wm_desktop(struct client *client) { + if (client != NULL) + xcb_ewmh_set_wm_desktop(ewmh, client->window, client->group); +} + +static void +group_add_window(struct client *client, uint32_t group) { + if (client != NULL && group < conf.groups) { + client->group = group; + update_wm_desktop(client); + group_in_use[group] = true; + } +} + +static void +group_remove_window(struct client *client) { + if (client != NULL) { + client->group = NULL_GROUP; + update_wm_desktop(client); + } +} + +static void +group_activate(uint32_t group) { + struct list_item *item; + struct client *client; + + for (item = win_list; item != NULL; item = item->next) { + client = item->data; + if (client->group == group) + xcb_map_window(conn, client->window); + } + group_in_use[group] = true; +} + +static void +group_deactivate(uint32_t group) { + struct list_item *item; + struct client *client; + + for (item = win_list; item != NULL; item = item->next) { + client = item->data; + if (client->group == group) + xcb_unmap_window(conn, client->window); + } + group_in_use[group] = false; +} + +static void +group_toggle(uint32_t group) { + if (group_in_use[group]) + group_deactivate(group); + else + group_activate(group); +} + +static void +change_nr_of_groups(uint32_t groups) { + bool *copy = malloc(groups * sizeof(bool)); + uint32_t until = groups < conf.groups ? groups : conf.groups; + struct list_item *item; + struct client *client; + + for (uint32_t i = 0; i < until; i++) + copy[i] = group_in_use[i]; + + free(group_in_use); + group_in_use = copy; + + if (groups < conf.groups) + for (item = win_list; item != NULL; item = item->next) { + client = item->data; + if (client->group >= groups) { + client->group = NULL_GROUP; + update_wm_desktop(client); + } + } + + conf.groups = groups; + +} +/* + * Adds X event handlers to the array. + */ + +static void +register_event_handlers(void) +{ + for (int i = 0; i <= LAST_XCB_EVENT; i++) + events[i] = NULL; + + events[XCB_CONFIGURE_REQUEST] = event_configure_request; + events[XCB_DESTROY_NOTIFY] = event_destroy_notify; + events[XCB_ENTER_NOTIFY] = event_enter_notify; + events[XCB_MAP_REQUEST] = event_map_request; + events[XCB_MAP_NOTIFY] = event_map_notify; + events[XCB_UNMAP_NOTIFY] = event_unmap_notify; + events[XCB_CLIENT_MESSAGE] = event_client_message; + events[XCB_CONFIGURE_NOTIFY] = event_configure_notify; +} + +/* + * A window wants to be configured. + */ + +static void +event_configure_request(xcb_generic_event_t *ev) +{ + xcb_configure_request_event_t *e = (xcb_configure_request_event_t *)ev; + struct client *client; + uint32_t values[7]; + int i = 0; + + client = find_client(&e->window); + if (client != NULL) { + + if (e->value_mask & XCB_CONFIG_WINDOW_X + && !client->maxed && !client->hmaxed) + client->geom.x = e->x; + + if (e->value_mask & XCB_CONFIG_WINDOW_Y + && !client->maxed && !client->vmaxed) + client->geom.y = e->y; + + if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH + && !client->maxed && !client->hmaxed) + client->geom.width= e->width; + + if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT + && !client->maxed && !client->vmaxed) + client->geom.height = e->height; + + if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + values[0] = e->stack_mode; + xcb_configure_window(conn, e->window, + XCB_CONFIG_WINDOW_STACK_MODE, values); + } + + if (!client->maxed) { + fit_on_screen(client); + } + + teleport_window(client->window, client->geom.x, client->geom.y); + resize_window_absolute(client->window, client->geom.width, client->geom.height); + if (!client->maxed) + set_borders(client, conf.focus_color); + } else { + if (e->value_mask & XCB_CONFIG_WINDOW_X) { + values[i] = e->x; + i++; + } + + if (e->value_mask & XCB_CONFIG_WINDOW_Y) { + values[i] = e->y; + i++; + } + + if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { + values[i] = e->width; + i++; + } + + if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + values[i] = e->height; + i++; + } + + if (e->value_mask & XCB_CONFIG_WINDOW_SIBLING) { + values[i] = e->sibling; + i++; + } + + if (e->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + values[i] = e->stack_mode; + i++; + } + + if (i == 0) + return; + xcb_configure_window(conn, e->window, e->value_mask, values); + } +} + +/* + * Window has been destroyed. + */ + +static void +event_destroy_notify(xcb_generic_event_t *ev) +{ + struct client *client; + xcb_destroy_notify_event_t *e = (xcb_destroy_notify_event_t *)ev; + + client = find_client(&e->window); + if (focused_win != NULL && focused_win == client) + focused_win = NULL; + + if (client != NULL) { + free_window(client); + } + + update_client_list(); +} + +/* + * The mouse pointer has entered the window. + */ + +static void +event_enter_notify(xcb_generic_event_t *ev) +{ + xcb_enter_notify_event_t *e = (xcb_enter_notify_event_t *)ev; + struct client *client; + + if (e->mode == XCB_NOTIFY_MODE_NORMAL || + e->mode == XCB_NOTIFY_MODE_UNGRAB) { + if (focused_win != NULL && e->event == focused_win->window) + return; + + client = find_client(&e->event); + if (client == NULL || client->maxed) + return; + + set_focused_no_raise(client); + } +} + +/* + * A window wants to show up on the screen. + */ + +static void +event_map_request(xcb_generic_event_t *ev) +{ + xcb_map_request_event_t *e = (xcb_map_request_event_t *)ev; + struct client *client; + long data[] = { + XCB_ICCCM_WM_STATE_NORMAL, + XCB_NONE, + }; + + /* window wants to magically show up. we prohibit that */ + if (find_client(&e->window) != NULL) + return; + + client = setup_window(e->window); + + if (!client->geom.set_by_user) { + if (!get_pointer_location(&scr->root, &client->geom.x, &client->geom.y)) + client->geom.x = client->geom.y = 0; + + client->geom.x -= client->geom.width / 2; + client->geom.y -= client->geom.height / 2; + teleport_window(client->window, client->geom.x, client->geom.y); + } + + if (randr_base != -1) { + client->monitor = find_monitor_by_coord(client->geom.x, client->geom.y); + if (client->monitor == NULL && mon_list != NULL) + client->monitor = mon_list->data; + } + + fit_on_screen(client); + + xcb_map_window(conn, client->window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, client->window, + ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE, 32, 2, data); + + center_pointer(client); + update_client_list(); + + if (!client->maxed) + set_borders(client, conf.focus_color); +} + +static void +event_map_notify(xcb_generic_event_t *ev) +{ + xcb_map_notify_event_t *e = (xcb_map_notify_event_t *)ev; + struct client *client = find_client(&e->window); + + if (client != NULL) + client->mapped = true; +} + +/* + * Window has been unmapped (became invisible). + */ + +static void +event_unmap_notify(xcb_generic_event_t *ev) +{ + xcb_map_request_event_t *e = (xcb_map_request_event_t *)ev; + struct client *client = NULL; + + client = find_client(&e->window); + if (client == NULL) + return; + + if (focused_win != NULL && client->window == focused_win->window) + focused_win = NULL; + + client->mapped = false; + + update_client_list(); +} + +/* + * Window has been configured. + */ + +static void +event_configure_notify(xcb_generic_event_t *ev) +{ + xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *)ev; + struct client *client; + struct list_item *item; + + /* The root window changes its geometry when the + * user adds/removes/tilts screens */ + if (e->window == scr->root) { + if (e->window != scr->width_in_pixels + || e->height != scr->height_in_pixels) { + scr->width_in_pixels = e->width; + scr->height_in_pixels = e->height; + + if (randr_base != -1) { + get_randr(); + for (item = win_list; item != NULL; item = item->next) { + client = item->data; + fit_on_screen(client); + } + } + } + } else { + client = find_client(&e->window); + if (client != NULL) + client->monitor = find_monitor_by_coord(client->geom.x, client->geom.y); + } +} + +/* + * Window wants to change its position in the stacking order. + */ + +static void +event_circulate_request(xcb_generic_event_t *ev) +{ + xcb_circulate_request_event_t *e = (xcb_circulate_request_event_t *)ev; + + xcb_circulate_window(conn, e->window, e->place); +} + +/* + * Received client message. Either ewmh/icccm thing or + * message from the client. + */ + +static void +event_client_message(xcb_generic_event_t *ev) +{ + xcb_client_message_event_t *e = (xcb_client_message_event_t *)ev; + uint32_t ipc_command; + uint32_t *data; + bool maxed, vmaxed, hmaxed; + struct client *client; + int16_t mon_x, mon_y; + uint16_t mon_w, mon_h; + + if (e->type == ATOMS[_IPC_ATOM_COMMAND] && e->format == 32) { + /* Message from the client */ + data = e->data.data32; + ipc_command = data[0]; + if (ipc_handlers[ipc_command] != NULL) + (ipc_handlers[ipc_command])(data + 1); + DMSG("%u %u %u %u %u\n", data[0], data[1], data[2], data[3], data[4]); + } else if (e->type == ewmh->_NET_WM_STATE && e->format == 32) { + /* A window change its state */ + client = find_client(&e->window); + maxed = vmaxed = hmaxed = false; + /* We handle only two states at the same time */ + for (int i = 0; i < 2; i++) { + xcb_atom_t state = e->data.data32[i]; + if (state == ewmh->_NET_WM_STATE_FULLSCREEN) + maxed = true; + else if (state == ewmh->_NET_WM_STATE_MAXIMIZED_VERT) + vmaxed = true; + else if (state == ewmh->_NET_WM_STATE_MAXIMIZED_HORZ) + hmaxed = true; + } + /* max/unmax the window if needed */ + if (client != NULL && (maxed || vmaxed || hmaxed)) { + get_monitor_size(client, &mon_x, &mon_y, &mon_w, &mon_h); + if (client->maxed || client->vmaxed || client->hmaxed) { + unmaximize_window(client); + set_focused(client); + } else if (maxed) { + maximize_window(client, mon_x, mon_y, mon_w, mon_h); + } else if (vmaxed) { + vmaximize_window(client, mon_y, mon_h); + } else if (hmaxed) { + hmaximize_window(client, mon_x, mon_w); + } + } + } +} + +/* + * Populates array with functions for handling IPC commands. + */ + +static void +register_ipc_handlers(void) +{ + ipc_handlers[IPCWindowMove] = ipc_window_move; + ipc_handlers[IPCWindowMoveAbsolute] = ipc_window_move_absolute; + ipc_handlers[IPCWindowResize] = ipc_window_resize; + ipc_handlers[IPCWindowResizeAbsolute] = ipc_window_resize_absolute; + ipc_handlers[IPCWindowMaximize] = ipc_window_maximize; + ipc_handlers[IPCWindowHorMaximize] = ipc_window_hor_maximize; + ipc_handlers[IPCWindowVerMaximize] = ipc_window_ver_maximize; + ipc_handlers[IPCWindowClose] = ipc_window_close; + ipc_handlers[IPCWindowPutInGrid] = ipc_window_put_in_grid; + ipc_handlers[IPCWindowSnap] = ipc_window_snap; + ipc_handlers[IPCWindowCycle] = ipc_window_cycle; + ipc_handlers[IPCWindowRevCycle] = ipc_window_rev_cycle; + ipc_handlers[IPCGroupAddWindow] = ipc_group_add_window; + ipc_handlers[IPCGroupRemoveWindow] = ipc_group_remove_window; + ipc_handlers[IPCGroupActivate] = ipc_group_activate; + ipc_handlers[IPCGroupDeactivate] = ipc_group_deactivate; + ipc_handlers[IPCGroupToggle] = ipc_group_toggle; + ipc_handlers[IPCWMQuit] = ipc_wm_quit; + ipc_handlers[IPCWMChangeNrOfGroups] = ipc_wm_change_nr_of_groups; +} + +static void +ipc_window_move(uint32_t *d) +{ + int16_t x, y; + + if (focused_win == NULL) + return; + + x = d[2]; + y = d[3]; +if (d[0]) + x = -x; + if (d[1]) + y = -y; + + focused_win->geom.x += x; + focused_win->geom.y += y; + + move_window(focused_win->window, x, y); + center_pointer(focused_win); +} + +static void +ipc_window_move_absolute(uint32_t *d) +{ + int16_t x, y; + + if (focused_win == NULL) + return; + + x = d[2]; + y = d[3]; + + if (d[0]) + x = -x; + if (d[1]) + y = -y; + + focused_win->geom.x = x; + focused_win->geom.y = y; + + teleport_window(focused_win->window, x, y); + center_pointer(focused_win); +} + +static void +ipc_window_resize(uint32_t *d) +{ + int16_t w, h; + int32_t aw, ah; + + if (focused_win == NULL) + return; + + w = d[2]; + h = d[3]; + + if (d[0]) + w = -w; + if (d[1]) + h = -h; + + aw = focused_win->geom.width; + ah = focused_win->geom.height; + if (aw + w > 0) + aw += w; + if (ah + h > 0) + ah += h; + + if (aw < 0) + aw = 0; + if (ah < 0) + ah = 0; + DMSG("aw: %d\tah: %d\n", aw, ah); + + if (focused_win->max_width != 0 && aw > focused_win->max_width) + aw = focused_win->max_width; + + if (focused_win->max_height != 0 && ah > focused_win->max_height) + ah = focused_win->max_height; + + if (focused_win->min_width != 0 && aw < focused_win->min_width) + aw = focused_win->min_width; + + if (focused_win->min_height != 0 && ah < focused_win->min_height) + ah = focused_win->min_height; + + focused_win->geom.width = aw; + focused_win->geom.height = ah; + + resize_window_absolute(focused_win->window, aw, ah); + center_pointer(focused_win); +} + +static void +ipc_window_resize_absolute(uint32_t *d) +{ + int16_t w, h; + + if (focused_win == NULL) + return; + + w = d[0]; + h = d[1]; + + if (focused_win->max_width != 0 && w > focused_win->max_width) + w = focused_win->max_width; + + if (focused_win->max_height != 0 && h > focused_win->max_height) + h = focused_win->max_height; + + if (focused_win->min_width != 0 && w < focused_win->min_width) + w = focused_win->min_width; + + if (focused_win->min_height != 0 && h < focused_win->min_height) + h = focused_win->min_height; + + focused_win->geom.width = w; + focused_win->geom.height = h; + + resize_window_absolute(focused_win->window, w, h); + center_pointer(focused_win); +} + +static void +ipc_window_maximize(uint32_t *d) { + (void)(d); + int16_t mon_x, mon_y; + uint16_t mon_w, mon_h; + + if (focused_win == NULL) + return; + + if (focused_win->maxed || focused_win->hmaxed || focused_win->vmaxed) { + unmaximize_window(focused_win); + set_focused(focused_win); + } else { + get_monitor_size(focused_win, &mon_x, &mon_y, &mon_w, &mon_h); + maximize_window(focused_win, mon_x, mon_y, mon_w, mon_h); + } + + xcb_flush(conn); +} + +static void +ipc_window_hor_maximize(uint32_t *d) +{ + (void)(d); + int16_t mon_x, mon_y; + uint16_t mon_w; + bool was_hmaxed; + + if (focused_win == NULL) + return; + + was_hmaxed = focused_win->hmaxed; + + if (focused_win->maxed || focused_win->vmaxed || focused_win->hmaxed) { + unmaximize_window(focused_win); + set_focused(focused_win); + } else { + get_monitor_size(focused_win, &mon_x, &mon_y, &mon_w, NULL); + hmaximize_window(focused_win, mon_x, mon_w); + } + + xcb_flush(conn); +} + +static void +ipc_window_ver_maximize(uint32_t *d) +{ + (void)(d); + int16_t mon_x, mon_y; + uint16_t mon_h; + bool was_hmaxed; + + if (focused_win == NULL) + return; + + was_hmaxed = focused_win->hmaxed; + + if (focused_win->maxed || focused_win->vmaxed || focused_win->hmaxed) { + unmaximize_window(focused_win); + set_focused(focused_win); + } else { + get_monitor_size(focused_win, &mon_x, &mon_y, NULL, &mon_h); + vmaximize_window(focused_win, mon_y, mon_h); + } + + xcb_flush(conn); +} + +static void +ipc_window_close(uint32_t *d) +{ + (void)(d); + close_window(focused_win); + focused_win = NULL; +} + +static void +ipc_window_put_in_grid(uint32_t *d) +{ + uint32_t grid_width, grid_height; + uint32_t grid_x, grid_y; + int step_x, step_y; + int16_t mon_x, mon_y; + uint16_t mon_w, mon_h; + + grid_width = d[0]; + grid_height = d[1]; + grid_x = d[2]; + grid_y = d[3]; + + if (focused_win == NULL || grid_x >= grid_width || grid_y >= grid_height) + return; + + if (focused_win->maxed || focused_win->vmaxed || focused_win->hmaxed) { + unmaximize_window(focused_win); + set_focused(focused_win); + } + + get_monitor_size(focused_win, &mon_x, &mon_y, &mon_w, &mon_h); + step_x = mon_w / grid_width; + step_y = mon_h / grid_height; + DMSG("%d %d %d %d %d %d\n", grid_width, grid_height, grid_x, grid_y, step_x, step_y); + + focused_win->geom.width = step_x; + focused_win->geom.height = step_y; + + focused_win->geom.x = mon_x + grid_x * step_x; + focused_win->geom.y = mon_y + grid_y * step_y; + + teleport_window(focused_win->window, focused_win->geom.x, focused_win->geom.y); + resize_window_absolute(focused_win->window, focused_win->geom.width, focused_win->geom.height); + + xcb_flush(conn); +} + +static void +ipc_window_snap(uint32_t *d) +{ + uint32_t mode = d[0]; + int16_t mon_x, mon_y, win_x, win_y; + uint16_t mon_w, mon_h, win_w, win_h; + + if (focused_win == NULL) + return; + + fit_on_screen(focused_win); + + win_x = focused_win->geom.x; + win_y = focused_win->geom.y; + win_w = focused_win->geom.width + 2 * conf.border_width; + win_h = focused_win->geom.height + 2 * conf.border_width; + + get_monitor_size(focused_win, &mon_x, &mon_y, &mon_w, &mon_h); + + switch (mode) { + case SnapTopLeft: + win_x = mon_x + conf.gap; + win_y = mon_y + conf.gap; + break; + + case SnapTopRight: + win_x = mon_x + mon_w - conf.gap - win_w; + win_y = mon_y + conf.gap; + break; + + case SnapBottomLeft: + win_x = mon_x + conf.gap; + win_y = mon_y + mon_h - conf.gap - win_h; + break; + + case SnapBottomRight: + win_x = mon_x + mon_w - conf.gap - win_w; + win_y = mon_y + mon_h - conf.gap - win_h; + break; + + case SnapMiddle: + win_x = mon_x + (mon_w - win_w) / 2; + win_y = mon_y + (mon_h - win_h) / 2; + break; + + default: + return; + } + + focused_win->geom.x = win_x; + focused_win->geom.y = win_y; + teleport_window(focused_win->window, win_x, win_y); + center_pointer(focused_win); + xcb_flush(conn); +} + +static +void ipc_window_cycle(uint32_t *d) +{ + (void)(d); + + cycle_window(focused_win); +} + +static +void ipc_window_rev_cycle(uint32_t *d) +{ + (void)(d); + + rcycle_window(focused_win); +} + +static void +ipc_group_add_window(uint32_t *d) { + if (focused_win != NULL) + group_add_window(focused_win, d[0]); +} + +static void +ipc_group_remove_window(uint32_t *d) { + (void)(d); + if (focused_win != NULL) + group_remove_window(focused_win); +} + +static void +ipc_group_activate(uint32_t *d) { + group_activate(d[0]); +} + +static void +ipc_group_deactivate(uint32_t *d) { + group_activate(d[0]); +} + +static void +ipc_group_toggle(uint32_t *d) { + group_toggle(d[0]); +} + +static void +ipc_wm_quit(uint32_t *d) { + uint32_t code = d[0]; + halt = true; + exit_code = code; +} + +static void +ipc_wm_change_nr_of_groups(uint32_t *d) +{ + change_nr_of_groups(d[0]); +} + +int main(int argc, char **argv) +{ + atexit(cleanup); + + register_event_handlers(); + register_ipc_handlers(); + if (setup() < 0) + errx(EXIT_FAILURE, "error connecting to X"); + run(); + + return exit_code; +} diff --git a/xephyr.sh b/xephyr.sh new file mode 100755 index 0000000..0d71b8a --- /dev/null +++ b/xephyr.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +D=${D:-80} + +Xephyr -screen 1280x720 :$D& +sleep 1 + +export DISPLAY=:$D +sxhkd -c .sxhkdrc & +exec ./windowchef