summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--schewm.c596
1 files changed, 530 insertions, 66 deletions
diff --git a/schewm.c b/schewm.c
index e4c52fe..9eafd2e 100644
--- a/schewm.c
+++ b/schewm.c
@@ -1,15 +1,542 @@
-#include <stdio.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
+#include <unistd.h>
#include <string.h>
#include <xcb/randr.h>
#include <xcb/xcb_ewmh.h>
+#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <X11/keysym.h>
-static uint16_t mod_key = XCB_MOD_MASK_4;
-static const uint16_t SHIFT = XCB_MOD_MASK_SHIFT;
+// Workspace values which are special for us
+const uint32_t ALL_WORKSPACES = 0xffffffff;
+const uint32_t NULL_WORKSPACE = 0xfffffffe;
+
+// Glyphs (mouse cursor types)
+const uint16_t MOVE_GLYPH = 52;
+const uint16_t RESIZE_GLYPH = 120;
+
+// Key modifiers
+const uint16_t META = XCB_MOD_MASK_4;
+const uint16_t SHIFT = XCB_MOD_MASK_SHIFT;
+
+struct Point {
+ int16_t x, y;
+};
+
+struct Rect {
+ int16_t x, y;
+ uint16_t width, height;
+};
+
+int16_t rect_center_x(const struct Rect *rect) {
+ return rect->x + rect->width / 2;
+}
+
+int16_t rect_center_y(const struct Rect *rect) {
+ return rect->y + rect->height / 2;
+}
+
+void rect_center(const struct Rect *rect, struct Point *p) {
+ p->x = rect_center_x(rect);
+ p->y = rect_center_y(rect);
+}
+
+int16_t max(int16_t a, int16_t b) {
+ return a > b ? a : b;
+}
+
+int16_t min(int16_t a, int16_t b) {
+ return a < b ? a : b;
+}
+
+uint16_t rect_intersect_area(const struct Rect *r1, const struct Rect *r2) {
+ int16_t x1 = max(r1->x, r2->x);
+ int16_t y1 = max(r1->y, r2->y);
+ int16_t x2 = min(r1->x + r1->width, r2->x + r2->width);
+ int16_t y2 = min(r1->y + r1->height, r2->y + r2->height);
+ return (x1 > x2 || y1 > y2) ? 0 : (x2 - x1) * (y2 - y1);
+}
+
+enum WindowState {
+ WS_NORMAL = 0,
+ WS_ICONIFIED,
+ WS_MAXIMIZED,
+ WS_FULLSCREEN,
+};
+
+struct Client;
+
+struct Client {
+ xcb_window_t id;
+ bool user_coord;
+ int16_t x, y;
+ uint16_t width, height;
+ uint16_t min_width, min_height, max_width, max_height;
+ uint16_t width_inc, height_inc, base_width, base_height;
+ uint32_t workspace;
+ enum WindowState state, prev_state;
+ bool is_unkillable;
+ bool should_ignore_borders;
+ struct Monitor *monitor;
+ bool has_old_size;
+ int16_t old_x, old_y;
+ uint16_t old_width, old_height;
+ struct Client *prev, *next;
+};
+
+struct ClientList {
+ struct Client *client;
+ struct ClientList *next;
+};
+
+struct Client *client_new(xcb_window_t id) {
+ struct Client *client = (struct Client *) calloc(1, sizeof(struct Client));
+ if (client == NULL) {
+ return NULL;
+ }
+
+ client->id = id;
+ client->width_inc = 1;
+ client->height_inc = 1;
+ client->workspace = NULL_WORKSPACE;
+ /*
+ * Supposed to be 0 anyway:
+ *
+ * client->state = WS_NORMAL;
+ * client->prev_state = WS_NORMAL;
+ */
+ return client;
+}
+
+void client_free(struct Client *client) {
+ free(client);
+}
+
+struct Client *client_ring_add(struct Client *ring, struct Client *client) {
+ if (ring == NULL || ring->next == NULL || ring->prev == NULL) {
+ ring = client;
+ }
+
+ client->prev = ring;
+ client->next = ring->next;
+ ring->next->prev = client;
+ ring->next = client;
+ return client;
+}
+
+struct Client *client_ring_erase(struct Client *ring, struct Client *client) {
+ if (ring == NULL) {
+ return NULL;
+ }
+ if (ring == client && client->next == client) {
+ client_free(ring);
+ return NULL;
+ }
+
+ client->next->prev = client->prev;
+ client->prev->next = client->next;
+ if (ring == client) {
+ ring = client->next;
+ client_free(client);
+ }
+ return ring;
+}
+
+void client_store_size(struct Client *client) {
+ client->old_x = client->x;
+ client->old_y = client->y;
+ client->old_width = client->width;
+ client->old_height = client->height;
+ client->has_old_size = true;
+}
+
+void client_restore_size(struct Client *client) {
+ if (!client->has_old_size) {
+ return;
+ }
+
+ client->x = client->old_x;
+ client->y = client->old_y;
+ client->width = client->old_width;
+ client->height = client->old_height;
+}
+
+struct Monitor {
+ xcb_randr_output_t id;
+ const char *name;
+ int16_t x, y;
+ uint16_t width, height;
+ struct Monitor *prev, *next;
+};
+
+void monitor_rect(const struct Monitor *monitor, struct Rect *rect) {
+ rect->x = monitor->x;
+ rect->y = monitor->y;
+ rect->width = monitor->width;
+ rect->height = monitor->height;
+}
+
+struct Workspace {
+ struct Client *ring;
+};
+
+struct WorkspaceList {
+ struct Workspace *workspace;
+ struct WorkspaceList *next;
+};
+
+static struct {
+ uint16_t inner_border_width, outer_border_width, magnet_border_width;
+ int16_t offset_x, offset_y;
+ uint16_t offset_width, offset_height;
+ uint32_t pointer_motion_interval;
+ struct {
+ uint32_t focused, unfocused, unkillable, empty, outer;
+ } colors;
+} config;
+
+static struct {
+ xcb_connection_t *conn;
+ xcb_ewmh_connection_t *ewmh;
+ bool has_error;
+ uint16_t num_lock, caps_lock, scroll_lock;
+ xcb_font_t cursor_font;
+ xcb_cursor_t fleur_cursor, sizing_cursor;
+ xcb_screen_t *screen;
+ bool has_randr;
+ int randr_base, screen_num;
+ struct Monitor monitors;
+} display_server;
+
+/*
+ * Function pointer type for event handlers that take a generic event
+ * and know what to do with it.
+ */
+typedef void (*generic_event_handler_t)(xcb_generic_event_t *);
+
+static struct {
+ struct ClientList clients;
+ struct WorkspaceList workspaces;
+ struct xcb_connection_t *conn;
+ uint32_t cur_workspace;
+ bool is_running;
+ /*
+ * Array of (internal) event handlers. They cast the event pointer
+ * to the proper type and handle them.
+ */
+ generic_event_handler_t events[XCB_NO_OPERATION];
+} wm;
+
+static xcb_screen_t *get_screen();
+static bool request_has_error(xcb_void_cookie_t);
+static bool conn_init();
+static void conn_flush();
+
+static xcb_screen_t *get_screen() {
+ const xcb_setup_t* setup = xcb_get_setup(display_server.conn);
+ xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
+ for (int i = display_server.screen_num; iter.rem; --i) {
+ if (i == 0) {
+ return iter.data;
+ }
+ xcb_screen_next(&iter);
+ }
+ return NULL;
+}
+
+static bool request_has_error(xcb_void_cookie_t cookie) {
+ xcb_generic_error_t *error = xcb_request_check(display_server.conn, cookie);
+ if (error) {
+ free(error);
+ return true;
+ }
+ return false;
+}
+
+struct WindowsQueryReply {
+ xcb_window_t *data;
+ int length;
+};
+
+void windows_query(xcb_window_t root, struct WindowsQueryReply *reply) {
+ xcb_query_tree_cookie_t cookie = xcb_query_tree(display_server.conn, root);
+ xcb_query_tree_reply_t *data = xcb_query_tree_reply(display_server.conn, cookie, NULL);
+ reply->length = 0;
+ if (!data) {
+ return;
+ }
+ int length = xcb_query_tree_children_length(data);
+ // Assume length is OK, but reply->length hold the length of
+ // clients we are really interested in
+ reply->data = (xcb_window_t *) calloc(length, sizeof(xcb_window_t));
+ xcb_window_t *children = xcb_query_tree_children(data);
+ for (int i = 0; i < length; i++) {
+ xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes(
+ display_server.conn,
+ children[i]);
+ xcb_get_window_attributes_reply_t *attr_reply = xcb_get_window_attributes_reply(
+ display_server.conn,
+ attr_cookie,
+ NULL);
+ /*
+ * From the 2bwm source:
+ * Ignore windows in override redirect mode. This mode means
+ * they wouldn't have been reported to us with a
+ * XCB_MAP_REQUEST if we had been running, so in the normal
+ * case we wouldn't have seen them. Only handle visible
+ * windows.
+ */
+ if (attr_reply
+ && !attr_reply->override_redirect
+ && attr_reply->map_state == XCB_MAP_STATE_VIEWABLE) {
+ reply->data[reply->length++] = children[i];
+ }
+ free(attr_reply);
+ }
+}
+
+void windows_query_reply_free(struct WindowsQueryReply *reply) {
+ if (reply->length > 0) {
+ free(reply->data);
+ }
+}
+
+bool wm_has_error();
+bool wm_init();
+void wm_quit();
+void wm_close_client();
+void wm_begin_move_client();
+void wm_begin_resize_client();
+void wm_focus_prev();
+void wm_focus_next();
+void wm_toggle_maximize();
+void wm_toggle_half_left();
+void wm_toggle_half_right();
+void wm_toggle_half_top();
+void wm_toggle_half_bottom();
+void wm_toggle_top_left();
+void wm_toggle_top_right();
+void wm_toggle_bottom_left();
+void wm_toggle_bottom_right();
+void wm_pack_left();
+void wm_pack_right();
+void wm_pack_top();
+void wm_pack_bottom();
+void wm_set_workspace(uint32_t workspace);
+void wm_set_client_workspace(uint32_t workspace);
+void wm_client_monitor_prev();
+void wm_client_monitor_next();
+
+bool wm_has_error() {
+ return display_server.has_error || xcb_connection_has_error(display_server.conn) > 0;
+}
+
+static bool conn_init(const char *wm_name) {
+ // TODO: init keysyms
+ display_server.has_error = false;
+ display_server.has_randr = false;
+ display_server.randr_base = -1;
+ display_server.conn = xcb_connect(NULL, &display_server.screen_num);
+ if (wm_has_error()) {
+ return false;
+ }
+
+ display_server.screen = get_screen();
+ if (!display_server.screen) {
+ display_server.has_error = true;
+ return false;
+ }
+
+ display_server.ewmh = (xcb_ewmh_connection_t *) calloc(1, sizeof(xcb_ewmh_connection_t));
+ if (!display_server.ewmh) {
+ display_server.has_error = true;
+ return false;
+ }
+
+ xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(
+ display_server.conn,
+ display_server.ewmh);
+ if (xcb_ewmh_init_atoms_replies(display_server.ewmh, ewmh_cookie, NULL) == 0) {
+ display_server.has_error = true;
+ return false;
+ }
+
+ xcb_atom_t net_atoms[] = {
+ display_server.ewmh->_NET_SUPPORTED,
+ display_server.ewmh->_NET_WM_DESKTOP,
+ display_server.ewmh->_NET_NUMBER_OF_DESKTOPS,
+ display_server.ewmh->_NET_CURRENT_DESKTOP,
+ display_server.ewmh->_NET_ACTIVE_WINDOW,
+ display_server.ewmh->_NET_WM_ICON,
+ display_server.ewmh->_NET_WM_STATE,
+ display_server.ewmh->_NET_WM_NAME,
+ display_server.ewmh->_NET_SUPPORTING_WM_CHECK,
+ display_server.ewmh->_NET_WM_STATE_HIDDEN,
+ display_server.ewmh->_NET_WM_ICON_NAME,
+ display_server.ewmh->_NET_WM_WINDOW_TYPE,
+ display_server.ewmh->_NET_WM_WINDOW_TYPE_DOCK,
+ display_server.ewmh->_NET_WM_WINDOW_TYPE_DESKTOP,
+ display_server.ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR,
+ display_server.ewmh->_NET_WM_PID,
+ display_server.ewmh->_NET_CLIENT_LIST,
+ display_server.ewmh->WM_PROTOCOLS,
+ display_server.ewmh->_NET_WM_STATE,
+ display_server.ewmh->_NET_WM_STATE_DEMANDS_ATTENTION,
+ display_server.ewmh->_NET_WM_STATE_FULLSCREEN,
+ };
+ xcb_ewmh_set_supported(
+ display_server.ewmh,
+ display_server.screen_num,
+ sizeof(net_atoms) / sizeof(net_atoms[0]),
+ net_atoms);
+
+ // set_focus(XCB_NONE);
+ // setup_randr();
+ // setup_keys();
+ // setup_cursors();
+
+ xcb_event_mask_t event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
+ | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY
+ | XCB_EVENT_MASK_PROPERTY_CHANGE
+ | XCB_EVENT_MASK_BUTTON_PRESS;
+ xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(
+ display_server.conn,
+ display_server.screen->root,
+ XCB_CW_EVENT_MASK,
+ &event_mask);
+ if (request_has_error(cookie)) {
+ display_server.has_error = true;
+ return false;
+ }
+
+ xcb_ewmh_set_wm_name(display_server.ewmh, display_server.screen->root, strlen(wm_name), wm_name);
+ xcb_ewmh_set_wm_pid(display_server.ewmh, display_server.screen->root, getpid());
+
+ conn_flush();
+ return !display_server.has_error;
+}
+
+static void conn_flush() {
+ xcb_flush(display_server.conn);
+}
+
+/*
+ * Event handlers which take a generic event and know what to cast it into.
+ */
+static void ev_configure_request(xcb_generic_event_t *);
+static void ev_destroy_notify(xcb_generic_event_t *);
+static void ev_enter_notify(xcb_generic_event_t *);
+static void ev_key_press(xcb_generic_event_t *);
+static void ev_map_request(xcb_generic_event_t *);
+static void ev_unmap_notify(xcb_generic_event_t *);
+static void ev_mapping_notify(xcb_generic_event_t *);
+static void ev_configure_notify(xcb_generic_event_t *);
+static void ev_circulate_request(xcb_generic_event_t *);
+static void ev_button_press(xcb_generic_event_t *);
+static void ev_client_message(xcb_generic_event_t *);
+
+bool wm_init() {
+ if (!conn_init("schewm")) {
+ return false;
+ }
+
+ wm.is_running = true;
+ wm.cur_workspace = 0;
+
+ // conn_set_num_workspaces();
+
+ // TODO: load config
+ // m_delete_window_atom = conn.get_atom("WM_DELETE_WINDOW");
+ // m_change_state_atom = conn.get_atom("WM_CHANGE_STATE");
+
+ // Look into all existing windows and set them up
+ struct WindowsQueryReply reply;
+ windows_query(display_server.screen->root, &reply);
+ for (int i = 0; i < reply.length; i++) {
+ xcb_window_t window = reply.data[i];
+ if (!setup_client(window)) {
+ /*
+ * Could not be setup, just map it on screen because we
+ * probably should not own it.
+ */
+ conn_map_window(window);
+ } else {
+ /*
+ * Unmap everything we grab initially because they may be
+ * mapped currently, but belong to another workspace; this
+ * way, we can map again only the correct windows at the
+ * set_workspace() call below.
+ */
+ conn_unmap_window(window);
+ }
+ }
+ windows_query_reply_free(&reply);
+ update_client_list();
+ set_workspace(wm.cur_workspace);
+ grab_keys();
+ conn_flush();
+
+ memset(wm.events, 0, sizeof(wm.events));
+ wm.events[XCB_CONFIGURE_REQUEST] = ev_configure_request;
+ wm.events[XCB_DESTROY_NOTIFY] = ev_destroy_notify;
+ wm.events[XCB_ENTER_NOTIFY] = ev_enter_notify;
+ wm.events[XCB_KEY_PRESS] = ev_key_press;
+ wm.events[XCB_MAP_REQUEST] = ev_map_request;
+ wm.events[XCB_UNMAP_NOTIFY] = ev_unmap_notify;
+ wm.events[XCB_MAPPING_NOTIFY] = ev_mapping_notify;
+ wm.events[XCB_CONFIGURE_NOTIFY] = ev_configure_notify;
+ wm.events[XCB_CIRCULATE_REQUEST] = ev_circulate_request;
+ wm.events[XCB_BUTTON_PRESS] = ev_button_press;
+ wm.events[XCB_CLIENT_MESSAGE] = ev_client_message;
+
+ return !display_server.has_error;
+}
+
+void wm_quit() {
+ wm.is_running = false;
+}
+
+void wm_destroy() {
+ if (display_server.ewmh) {
+ xcb_ewmh_connection_wipe(display_server.ewmh);
+ free(display_server.ewmh);
+ }
+
+ if (!wm_has_error()) {
+ xcb_free_cursor(display_server.conn, display_server.fleur_cursor);
+ xcb_free_cursor(display_server.conn, display_server.sizing_cursor);
+ xcb_close_font(display_server.conn, display_server.cursor_font);
+ xcb_set_input_focus(
+ display_server.conn,
+ XCB_NONE,
+ XCB_INPUT_FOCUS_POINTER_ROOT,
+ XCB_CURRENT_TIME);
+ conn_flush();
+ }
+ xcb_disconnect(display_server.conn);
+}
+
+static int ev_type(const xcb_generic_event_t *ev) {
+ return ev->response_type & 0x80;
+}
+
+void wm_run() {
+ while (wm.is_running) {
+ xcb_generic_event_t *ev = xcb_wait_for_event(display_server.conn);
+ generic_event_handler_t handler = wm.events[ev_type(ev)];
+ if (handler) {
+ handler(ev);
+ }
+ free(ev);
+ }
+
+ wm_destroy();
+}
+
+static uint16_t mod_key = META;
void wm_set_mod_key(uint16_t mod) {
mod_key = mod;
@@ -23,13 +550,6 @@ uint16_t wm_get_mod_key(bool with_shift) {
}
/*
- * Array of (internal) event handlers. They cast the event pointer to
- * the proper type and call the custom handlers defined in Scheme.
- */
-typedef void (*generic_event_handler_t)(xcb_generic_event_t *);
-static generic_event_handler_t events[XCB_NO_OPERATION];
-
-/*
* NOTE:
* Don't do the thing below. Create hooks for relevant stuff the user
* might care about, call them at the appropriate places. Design should be:
@@ -41,7 +561,6 @@ static generic_event_handler_t events[XCB_NO_OPERATION];
*/
// XCB_CONFIGURE_REQUEST
-static void ev_configure_request(xcb_generic_event_t *);
typedef void (*configure_request_handler_t)();
static configure_request_handler_t configure_request_handler = NULL;
@@ -57,7 +576,6 @@ static void ev_configure_request(xcb_generic_event_t *generic_ev) {
}
// XCB_DESTROY_NOTIFY
-static void ev_destroy_notify(xcb_generic_event_t *);
typedef void (*destroy_notify_handler_t)();
static destroy_notify_handler_t destroy_notify_handler = NULL;
@@ -73,7 +591,6 @@ static void ev_destroy_notify(xcb_generic_event_t *generic_ev) {
}
// XCB_ENTER_NOTIFY
-static void ev_enter_notify(xcb_generic_event_t *);
typedef void (*enter_notify_handler_t)();
static enter_notify_handler_t enter_notify_handler = NULL;
@@ -89,7 +606,6 @@ static void ev_enter_notify(xcb_generic_event_t *generic_ev) {
}
// XCB_KEY_PRESS
-static void ev_key_press(xcb_generic_event_t *);
typedef void (*key_press_handler_t)();
static key_press_handler_t key_press_handler = NULL;
@@ -105,7 +621,6 @@ static void ev_key_press(xcb_generic_event_t *generic_ev) {
}
// XCB_MAP_REQUEST
-static void ev_map_request(xcb_generic_event_t *);
typedef void (*map_request_handler_t)();
static map_request_handler_t map_request_handler = NULL;
@@ -121,7 +636,6 @@ static void ev_map_request(xcb_generic_event_t *generic_ev) {
}
// XCB_UNMAP_NOTIFY
-static void ev_unmap_notify(xcb_generic_event_t *);
typedef void (*unmap_notify_handler_t)();
static unmap_notify_handler_t unmap_notify_handler = NULL;
@@ -137,7 +651,6 @@ static void ev_unmap_notify(xcb_generic_event_t *generic_ev) {
}
// XCB_MAPPING_NOTIFY
-static void ev_mapping_notify(xcb_generic_event_t *);
typedef void (*mapping_notify_handler_t)();
static mapping_notify_handler_t mapping_notify_handler = NULL;
@@ -153,7 +666,6 @@ static void ev_mapping_notify(xcb_generic_event_t *generic_ev) {
}
// XCB_CONFIGURE_NOTIFY
-static void ev_configure_notify(xcb_generic_event_t *);
typedef void (*configure_notify_handler_t)(int16_t, int16_t, uint16_t, uint16_t);
static configure_notify_handler_t configure_notify_handler = NULL;
@@ -169,7 +681,6 @@ static void ev_configure_notify(xcb_generic_event_t *generic_ev) {
}
// XCB_CIRCULATE_REQUEST
-static void ev_circulate_request(xcb_generic_event_t *);
typedef void (*circulate_request_handler_t)();
static circulate_request_handler_t circulate_request_handler = NULL;
@@ -185,7 +696,6 @@ static void ev_circulate_request(xcb_generic_event_t *generic_ev) {
}
// XCB_BUTTON_PRESS
-static void ev_button_press(xcb_generic_event_t *);
typedef void (*button_press_handler_t)();
static button_press_handler_t button_press_handler = NULL;
@@ -201,7 +711,6 @@ static void ev_button_press(xcb_generic_event_t *generic_ev) {
}
// XCB_CLIENT_MESSAGE
-static void ev_client_message(xcb_generic_event_t *);
typedef void (*client_message_handler_t)();
static client_message_handler_t client_message_handler = NULL;
@@ -215,48 +724,3 @@ static void ev_client_message(xcb_generic_event_t *generic_ev) {
client_message_handler();
}
}
-
-bool wm_init() {
- memset(events, 0, sizeof(events));
- events[XCB_CONFIGURE_REQUEST] = ev_configure_request;
- events[XCB_DESTROY_NOTIFY] = ev_destroy_notify;
- events[XCB_ENTER_NOTIFY] = ev_enter_notify;
- events[XCB_KEY_PRESS] = ev_key_press;
- events[XCB_MAP_REQUEST] = ev_map_request;
- events[XCB_UNMAP_NOTIFY] = ev_unmap_notify;
- events[XCB_MAPPING_NOTIFY] = ev_mapping_notify;
- events[XCB_CONFIGURE_NOTIFY] = ev_configure_notify;
- events[XCB_CIRCULATE_REQUEST] = ev_circulate_request;
- events[XCB_BUTTON_PRESS] = ev_button_press;
- events[XCB_CLIENT_MESSAGE] = ev_client_message;
- return true;
-}
-
-void wm_destroy();
-
-void wm_run() {
- /*
- while (wm.is_running) {
- xcb_generic_event_t *ev = xcb_wait_for_event(display_server.conn);
- int ev_type = ev->response_type & 0x80;
- generic_event_handler_t handler = wm.events[ev_type];
- if (handler) {
- handler(ev);
- }
- free(ev);
- }
-
- wm_destroy();
- */
-}
-
-void wm_quit() {
- if (configure_notify_handler) {
- // xcb_configure_notify_event_t ev;
- // ev.x = 0;
- // ev.y = 1;
- // ev.width = 2;
- // ev.height = 3;
- configure_notify_handler(0, 1, 2, 3);
- }
-}