kbgwm

sucklessy floating window manager
git clone https://git.neuralcrash.com/kbgwm.git
Log | Files | Refs | README | LICENSE

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 }