kbgwm.c (9521B)
1 /* 2 * kbgwm, a sucklessy floating window manager 3 * Copyright (c) 2020-2021, Kebigon <git@kebigon.xyz> 4 * 5 * Permission to use, copy, modify, and/or distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include "kbgwm.h" 19 #include "events.h" 20 #include "list.h" 21 #include "log.h" 22 #include "monitor.h" 23 #include "xcbutils.h" 24 #include <X11/keysym.h> 25 #include <assert.h> 26 #include <inttypes.h> 27 #include <math.h> 28 #include <stdint.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <unistd.h> 32 #include <xcb/xcb.h> 33 #include <xcb/xcb_icccm.h> 34 35 #include "config.h" 36 37 bool running = true; 38 bool moving = false; 39 bool resizing = false; 40 xcb_connection_t *c; 41 xcb_screen_t *screen; 42 uint_least16_t previous_x; 43 uint_least16_t previous_y; 44 uint16_t numlockmask = 0; 45 xcb_atom_t wm_protocols; 46 xcb_atom_t wm_delete_window; 47 48 uint_fast8_t current_workspace = 0; 49 struct item *workspaces[NB_WORKSPACES]; 50 51 void mousemove(__attribute__((unused)) const union Arg *arg) 52 { 53 LOG_DEBUG("=======[ user action: mousemove ]======="); 54 moving = true; 55 56 xcb_grab_pointer( 57 c, 0, screen->root, XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_BUTTON_RELEASE, 58 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, screen->root, XCB_NONE, XCB_CURRENT_TIME); 59 xcb_flush(c); 60 } 61 62 void mouseresize(__attribute__((unused)) const union Arg *arg) 63 { 64 LOG_DEBUG("=======[ user action: mouseresize ]======="); 65 resizing = true; 66 67 xcb_grab_pointer( 68 c, 0, screen->root, XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_BUTTON_RELEASE, 69 XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, screen->root, XCB_NONE, XCB_CURRENT_TIME); 70 xcb_flush(c); 71 } 72 73 void eventLoop() 74 { 75 xcb_generic_event_t *event; 76 77 while (running) 78 { 79 if ((event = xcb_wait_for_event(c)) != NULL) 80 { 81 log_debug_event(event); 82 83 handle_event(event); 84 85 free(event); 86 LOG_DEBUG("=======[ event: DONE ]=======\n"); 87 } 88 } 89 } 90 91 void start(const union Arg *arg) 92 { 93 LOG_DEBUG("=======[ user action: start ]======="); 94 LOG_DEBUG_VA("cmd %s", arg->cmd[0]); 95 96 if (fork() == 0) 97 { 98 // Child process 99 setsid(); 100 101 if (execvp((char *)arg->cmd[0], (char **)arg->cmd) == -1) 102 { 103 perror(arg->cmd[0]); 104 exit(-1); 105 } 106 } 107 } 108 109 /* 110 * Focus 111 */ 112 113 void focus_apply() 114 { 115 struct item *item = list_head(&workspaces[current_workspace]); 116 assert(item != NULL); 117 118 struct client *client = item->data; 119 120 // We change the color of the focused client 121 xcb_change_window_attributes(c, client->id, XCB_CW_BORDER_PIXEL, 122 (uint32_t[]){0xFF000000 | FOCUS_COLOR}); 123 124 // Raise the window so it is on top 125 xcb_configure_window(c, client->id, XCB_CONFIG_WINDOW_STACK_MODE, 126 (uint32_t[]){XCB_STACK_MODE_ABOVE}); 127 128 // Set the keyboard on the focused window 129 xcb_set_input_focus(c, XCB_INPUT_FOCUS_POINTER_ROOT, client->id, XCB_CURRENT_TIME); 130 client_grab_buttons(client, true); 131 xcb_flush(c); 132 133 LOG_DEBUG("focus_apply: done"); 134 } 135 136 // Focus the next client in the current workspace list 137 // arg->b : reverse mode 138 void focus_next(const union Arg *arg) 139 { 140 LOG_DEBUG("=======[ user action: focus_next ]======="); 141 struct item **workspace = &workspaces[current_workspace]; 142 143 // No clients in the current workspace list 144 // Only one client in the current workspace list 145 if (list_is_empty(workspace) || list_head(workspace)->next == NULL) 146 return; // Nothing to be done 147 148 focus_unfocus(); 149 150 if (arg->b) 151 list_move_to_head(workspace, list_tail(workspace)); 152 else 153 list_move_to_tail(workspace, list_head(workspace)); 154 155 focus_apply(); 156 } 157 158 // Remove the focus from the current client 159 void focus_unfocus() 160 { 161 struct item *item = list_head(&workspaces[current_workspace]); 162 163 // No client are focused 164 if (item == NULL) 165 return; // Nothing to be done 166 167 struct client *client = item->data; 168 169 // Change the border color to UNFOCUS_COLOR 170 xcb_change_window_attributes(c, client->id, XCB_CW_BORDER_PIXEL, 171 (uint32_t[]){0xFF000000 | UNFOCUS_COLOR}); 172 client_grab_buttons(client, false); 173 } 174 175 /* 176 * Handling events from the event loop 177 */ 178 179 void quit(__attribute__((unused)) const union Arg *arg) 180 { 181 LOG_DEBUG("=======[ user action: quit ]======="); 182 running = false; 183 } 184 185 /* 186 * Setup 187 */ 188 189 // Retrieve the numlock keycode 190 void setup_keyboard() 191 { 192 xcb_get_modifier_mapping_reply_t *reply = 193 xcb_get_modifier_mapping_reply(c, xcb_get_modifier_mapping_unchecked(c), NULL); 194 if (!reply) 195 { 196 LOG_ERROR("Unable to retrieve midifier mapping"); 197 exit(-1); 198 } 199 200 xcb_keycode_t *modmap = xcb_get_modifier_mapping_keycodes(reply); 201 if (!modmap) 202 { 203 LOG_ERROR("Unable to retrieve midifier mapping keycodes"); 204 exit(-1); 205 } 206 207 xcb_keycode_t *numlock = xcb_get_keycodes(XK_Num_Lock); 208 209 // Not sure why 8, I looked at both dwm and 2bwm, and they both do the same 210 for (uint_fast8_t i = 0; i < 8; i++) 211 { 212 for (uint_fast8_t j = 0; j < reply->keycodes_per_modifier; j++) 213 { 214 xcb_keycode_t keycode = modmap[i * reply->keycodes_per_modifier + j]; 215 216 if (keycode == *numlock) 217 { 218 numlockmask = (1 << i); 219 LOG_DEBUG_VA("numlock is %d", keycode); 220 } 221 } 222 } 223 224 free(numlock); 225 free(reply); 226 } 227 228 /* 229 * Workspaces 230 */ 231 232 void workspace_change(const union Arg *arg) 233 { 234 LOG_DEBUG("=======[ user action: workspace_change ]======="); 235 LOG_DEBUG_VA("i=%d", arg->i); 236 237 workspace_set(arg->i); 238 } 239 240 void workspace_next(__attribute__((unused)) const union Arg *arg) 241 { 242 LOG_DEBUG("=======[ user action: workspace_next ]======="); 243 244 workspace_set(current_workspace + 1 == workspaces_length ? 0 : current_workspace + 1); 245 } 246 247 void workspace_previous(__attribute__((unused)) const union Arg *arg) 248 { 249 LOG_DEBUG("=======[ user action: workspace_previous ]======="); 250 workspace_set(current_workspace == 0 ? workspaces_length - 1 : current_workspace - 1); 251 } 252 253 void workspace_send(const union Arg *arg) 254 { 255 LOG_DEBUG("=======[ user action: workspace_send ]======="); 256 LOG_DEBUG_VA("i=%d", arg->i); 257 log_debug_globals(); 258 259 uint_fast8_t new_workspace = arg->i; 260 261 if (current_workspace == new_workspace || list_is_empty(&workspaces[current_workspace])) 262 return; // Nothing to be done 263 264 struct client *client = client_remove(); 265 client_add_workspace(client, new_workspace); 266 267 xcb_unmap_window(c, client->id); 268 xcb_flush(c); 269 LOG_DEBUG("workspace_send: done"); 270 } 271 272 void workspace_set(uint_fast8_t new_workspace) 273 { 274 LOG_DEBUG_VA("workspace_set: old=%d new=%d", current_workspace, new_workspace); 275 276 if (current_workspace == new_workspace) 277 return; // Nothing to be done 278 279 struct item *item; 280 281 // Unmap the clients of the current workspace (if any) 282 if ((item = list_head(&workspaces[current_workspace])) != NULL) 283 { 284 do 285 { 286 xcb_unmap_window(c, ((struct client *)item->data)->id); 287 } while ((item = item->next) != NULL); 288 } 289 290 // Map the clients of the new workspace (if any) 291 if ((item = list_head(&workspaces[new_workspace])) != NULL) 292 { 293 do 294 { 295 xcb_map_window(c, ((struct client *)item->data)->id); 296 } while ((item = item->next) != NULL); 297 } 298 299 xcb_flush(c); 300 current_workspace = new_workspace; 301 302 if (!list_is_empty(&workspaces[current_workspace])) 303 focus_apply(); 304 305 LOG_DEBUG("workspace_set: done"); 306 } 307 308 /* 309 * Main 310 */ 311 312 int main(void) 313 { 314 /* 315 * displayname = NULL -> use DISPLAY environment variable 316 */ 317 int screenp; 318 c = xcb_connect(NULL, &screenp); 319 320 if (xcb_connection_has_error(c)) 321 { 322 LOG_ERROR_VA("xcb_connect failed: %d", xcb_connection_has_error(c)); 323 exit(1); 324 } 325 326 /* 327 * Find the screen 328 */ 329 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(c)); 330 331 for (int i = 0; i < screenp; ++i) 332 { 333 xcb_screen_next(&iter); 334 } 335 336 screen = iter.data; 337 338 if (!screen) 339 { 340 xcb_disconnect(c); 341 perror("screen not found"); 342 exit(1); 343 } 344 345 xcb_flush(c); 346 347 /* 348 * Initialize variables 349 */ 350 351 for (uint_fast8_t i = 0; i != workspaces_length; i++) 352 workspaces[i] = NULL; 353 354 wm_protocols = xcb_get_atom(WM_PROTOCOLS); 355 wm_delete_window = xcb_get_atom(WM_DELETE_WINDOW); 356 357 setup_keyboard(); 358 setup_monitors(); 359 setup_clients(); 360 setup_events(); 361 362 // Event loop 363 eventLoop(); 364 365 for (uint_fast8_t i = 0; i != workspaces_length; i++) 366 { 367 while (workspaces[i] != NULL) 368 { 369 struct client *client = client_remove_workspace(i); 370 free(client); 371 } 372 } 373 374 xcb_disconnect(c); 375 376 return (0); 377 }