client.c (10930B)
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 "client.h" 19 #include "kbgwm.h" 20 #include "list.h" 21 #include "log.h" 22 #include "monitor.h" 23 #include "xcbutils.h" 24 25 #include <assert.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <xcb/xcb_icccm.h> 29 30 static inline int16_t int16_in_range(int16_t value, int16_t min, int16_t max) 31 { 32 if (value < min) 33 return min; 34 else if (value > max) 35 return max; 36 else 37 return value; 38 } 39 40 static inline uint16_t uint16_in_range(uint16_t value, uint16_t min, uint16_t max) 41 { 42 if (value < min) 43 return min; 44 else if (value > max) 45 return max; 46 else 47 return value; 48 } 49 50 void setup_clients() 51 { 52 // Retrieve the children of the root window 53 xcb_query_tree_reply_t *reply = xcb_query_tree_reply(c, xcb_query_tree(c, screen->root), 0); 54 if (NULL == reply) 55 { 56 LOG_ERROR("Unable to retrieve the root window's children"); 57 exit(-1); 58 } 59 60 int len = xcb_query_tree_children_length(reply); 61 xcb_window_t *children = xcb_query_tree_children(reply); 62 63 // Create the corresponding clients 64 for (int i = 0; i != len; i++) 65 client_create(children[i]); 66 67 free(reply); 68 } 69 70 // Add a client to the current workspace list 71 void client_add(struct client *client) 72 { 73 client_add_workspace(client, current_workspace); 74 } 75 76 // Add a client to a workspace list 77 void client_add_workspace(struct client *client, uint_fast8_t workspace) 78 { 79 assert(client != NULL); 80 assert(workspace < workspaces_length); 81 82 list_add(&workspaces[workspace], client); 83 } 84 85 // Update the monitor of a client based on its current position 86 void client_update_monitor(struct client *client) 87 { 88 const uint16_t center_x = client->x + (client->width >> 1); // x + width/2 89 const uint16_t center_y = client->y + (client->height >> 1); // y + height/2 90 91 // We're still in the current monitor -> nothing to be done 92 if (client->monitor != NULL && monitor_contains(client->monitor, center_x, center_y)) 93 return; 94 95 struct item *item = monitor_find_by_position(center_x, center_y); 96 if (item != NULL) 97 client->monitor = item->data; 98 } 99 100 void client_create(xcb_window_t id) 101 { 102 LOG_DEBUG_VA("client_create: id=%d", id); 103 104 // Request the information for the window 105 106 xcb_get_geometry_reply_t *geometry = 107 xcb_get_geometry_reply(c, xcb_get_geometry_unchecked(c, id), NULL); 108 xcb_size_hints_t hints; 109 xcb_icccm_get_wm_normal_hints_reply(c, xcb_icccm_get_wm_normal_hints_unchecked(c, id), &hints, 110 NULL); 111 112 struct client *new_client = emalloc(sizeof(struct client)); 113 114 new_client->id = id; 115 new_client->maximized = false; 116 new_client->monitor = NULL; 117 118 const bool position = 119 hints.flags & (XCB_ICCCM_SIZE_HINT_US_POSITION | XCB_ICCCM_SIZE_HINT_P_POSITION); 120 new_client->x = position ? hints.x : geometry->x; 121 new_client->y = position ? hints.y : geometry->y; 122 123 const bool size = hints.flags & (XCB_ICCCM_SIZE_HINT_US_SIZE | XCB_ICCCM_SIZE_HINT_P_SIZE); 124 new_client->width = size ? hints.width : geometry->width; 125 new_client->height = size ? hints.height : geometry->height; 126 127 const bool min_size = hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE; 128 new_client->min_width = min_size ? hints.min_width : 0; 129 new_client->min_height = min_size ? hints.min_height : 0; 130 131 const bool max_size = hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE; 132 new_client->max_width = max_size ? hints.max_width : INT32_MAX; 133 new_client->max_height = max_size ? hints.max_height : INT32_MAX; 134 135 client_sanitize_dimensions(new_client); 136 client_update_monitor(new_client); 137 138 LOG_DEBUG_VA( 139 "new window: id=%d x=%d y=%d width=%d height=%d min_width=%d min_height=%d max_width=%d " 140 "max_height=%d", 141 id, new_client->x, new_client->y, new_client->width, new_client->height, 142 new_client->min_width, new_client->min_height, new_client->max_width, 143 new_client->max_height); 144 145 xcb_configure_window( 146 c, id, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, 147 (uint32_t[]){new_client->width, new_client->height, border_width}); 148 149 // Display the client 150 xcb_map_window(c, new_client->id); 151 xcb_flush(c); 152 153 free(geometry); 154 155 focus_unfocus(); 156 client_add(new_client); 157 focus_apply(); 158 159 LOG_DEBUG("client_create: done"); 160 } 161 162 // Find a client in the current workspace list 163 struct client *client_find(xcb_window_t id) 164 { 165 return client_find_workspace(id, current_workspace); 166 } 167 168 struct client *client_find_all_workspaces(xcb_window_t id) 169 { 170 for (uint_fast8_t workspace = 0; workspace != workspaces_length; workspace++) 171 { 172 struct client *client = client_find_workspace(id, workspace); 173 if (client != NULL) 174 return client; 175 } 176 177 return NULL; 178 } 179 180 struct client *client_find_workspace(xcb_window_t id, uint_fast8_t workspace) 181 { 182 assert(workspace < workspaces_length); 183 184 struct item *item = list_find_client(&workspaces[workspace], id); 185 return item == NULL ? NULL : item->data; 186 } 187 188 // Remove the focused client from the current workspace list 189 struct client *client_remove() 190 { 191 return client_remove_workspace(current_workspace); 192 } 193 194 // Remove the focused client from a workspace list 195 struct client *client_remove_workspace(uint_fast8_t workspace) 196 { 197 assert(workspace < workspaces_length); 198 assert(!list_is_empty(&workspaces[workspace])); 199 200 return list_remove_head(&workspaces[workspace]); 201 } 202 203 void client_remove_all_workspaces(xcb_window_t id) 204 { 205 struct item *item; 206 207 for (uint_fast8_t workspace = 0; workspace != workspaces_length; workspace++) 208 { 209 item = list_find_client(&workspaces[workspace], id); 210 if (item != NULL) 211 list_remove(&workspaces[workspace], item); 212 } 213 } 214 215 void client_sanitize_position(struct client *client) 216 { 217 int16_t x = 218 int16_in_range(client->x, 0, screen->width_in_pixels - client->width - border_width_x2); 219 if (client->x != x) 220 client->x = x; 221 222 int16_t y = 223 int16_in_range(client->y, 0, screen->height_in_pixels - client->height - border_width_x2); 224 if (client->y != y) 225 client->y = y; 226 } 227 228 void client_sanitize_dimensions(struct client *client) 229 { 230 uint16_t width = uint16_in_range(client->width, client->min_width, client->max_width); 231 width = uint16_in_range(width, 0, screen->width_in_pixels - client->x - border_width_x2); 232 if (client->width != width) 233 client->width = width; 234 235 uint16_t height = uint16_in_range(client->height, client->min_height, client->max_height); 236 height = uint16_in_range(height, 0, screen->height_in_pixels - client->y - border_width_x2); 237 if (client->height != height) 238 client->height = height; 239 } 240 241 void client_kill(__attribute__((unused)) const union Arg *arg) 242 { 243 LOG_DEBUG("=======[ user action: client_kill ]======="); 244 245 struct item *item = list_head(&workspaces[current_workspace]); 246 247 // No client are focused 248 if (item == NULL) 249 return; // Nothing to be done 250 251 if (!xcb_send_atom(item->data, wm_delete_window)) 252 { 253 // The client does not support WM_DELETE, let's kill it 254 xcb_kill_client(c, ((struct client *)item->data)->id); 255 } 256 257 xcb_flush(c); 258 } 259 260 void client_toggle_maximize(__attribute__((unused)) const union Arg *arg) 261 { 262 LOG_DEBUG("=======[ user action: client_toggle_maximize ]======="); 263 264 struct item *item = list_head(&workspaces[current_workspace]); 265 266 // No client are focused 267 if (item == NULL) 268 return; // Nothing to be done 269 270 struct client *client = item->data; 271 272 if (client->maximized) 273 client_unmaximize(client); 274 else 275 client_maximize(client); 276 277 xcb_flush(c); 278 } 279 280 void client_maximize(struct client *client) 281 { 282 assert(client != NULL); 283 assert(!client->maximized); 284 285 client->maximized = true; 286 uint32_t *values; 287 288 if (client->monitor != NULL) 289 values = (uint32_t[]){client->monitor->x, client->monitor->y, client->monitor->width, 290 client->monitor->height, 0}; 291 else 292 values = (uint32_t[]){0, 0, screen->width_in_pixels, screen->height_in_pixels, 0}; 293 294 xcb_configure_window(c, client->id, 295 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | 296 XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, 297 values); 298 } 299 300 void client_unmaximize(struct client *client) 301 { 302 assert(client != NULL); 303 assert(client->maximized); 304 305 client->maximized = false; 306 307 uint32_t values[] = {client->x, client->y, client->width, client->height, border_width}; 308 xcb_configure_window(c, client->id, 309 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | 310 XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, 311 values); 312 } 313 314 void client_grab_buttons(struct client *client, bool focused) 315 { 316 xcb_ungrab_button(c, XCB_BUTTON_INDEX_ANY, client->id, XCB_MOD_MASK_ANY); 317 318 // The client is not the focused one -> grab everything 319 if (!focused) 320 { 321 xcb_grab_button(c, 1, client->id, BUTTON_EVENT_MASK, XCB_GRAB_MODE_ASYNC, 322 XCB_GRAB_MODE_ASYNC, screen->root, XCB_NONE, XCB_BUTTON_INDEX_ANY, 323 XCB_MOD_MASK_ANY); 324 } 325 326 // The client is the focused one -> grab only the configured buttons 327 else 328 { 329 for (uint_fast8_t i = 0; i != buttons_length; i++) 330 { 331 uint16_t modifiers[] = {0, numlockmask, XCB_MOD_MASK_LOCK, 332 numlockmask | XCB_MOD_MASK_LOCK}; 333 struct Button button = buttons[i]; 334 335 for (uint_fast8_t j = 0; j != LENGTH(modifiers); j++) 336 { 337 xcb_grab_button(c, 0, client->id, BUTTON_EVENT_MASK, XCB_GRAB_MODE_ASYNC, 338 XCB_GRAB_MODE_ASYNC, screen->root, XCB_NONE, button.keysym, 339 button.modifiers | modifiers[j]); 340 } 341 } 342 } 343 }