1027 lines
26 KiB
C
Raw Normal View History

2025-12-28 03:14:31 +01:00
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
2025-12-28 03:14:31 +01:00
* Client (window) management implementation
*/
#include "client.h"
#include "atoms.h"
#include "config.h"
#include "util.h"
#include "workspace.h"
#include "decorations.h"
#include "notifications.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xresource.h>
Client *client_create(Window window)
{
if (dwn == NULL || dwn->display == NULL) {
LOG_ERROR("client_create: dwn or display is NULL");
return NULL;
}
if (window == None) {
LOG_ERROR("client_create: invalid window (None)");
return NULL;
}
Client *client = dwn_calloc(1, sizeof(Client));
if (client == NULL) {
LOG_ERROR("client_create: failed to allocate client");
return NULL;
}
client->window = window;
client->frame = None;
client->border_width = config_get_border_width();
client->flags = CLIENT_NORMAL;
client->workspace = dwn->current_workspace;
client->next = NULL;
client->prev = NULL;
XWindowAttributes wa;
int orig_width = 640, orig_height = 480;
if (XGetWindowAttributes(dwn->display, window, &wa)) {
orig_width = wa.width;
orig_height = wa.height;
}
int work_x = 0;
int work_y = 0;
int work_width = dwn->screen_width;
int work_height = dwn->screen_height;
if (dwn->config != NULL) {
if (dwn->config->top_panel_enabled) {
work_y += config_get_panel_height();
work_height -= config_get_panel_height();
}
if (dwn->config->bottom_panel_enabled) {
work_height -= config_get_panel_height();
}
}
int target_width = (work_width * 75) / 100;
int target_height = (work_height * 75) / 100;
client->width = (orig_width > target_width) ? orig_width : target_width;
client->height = (orig_height > target_height) ? orig_height : target_height;
if (client->width > work_width - 20) client->width = work_width - 20;
if (client->height > work_height - 20) client->height = work_height - 20;
client->x = work_x + (work_width - client->width) / 2;
client->y = work_y + (work_height - client->height) / 2;
client->old_x = client->x;
client->old_y = client->y;
client->old_width = client->width;
client->old_height = client->height;
client_update_title(client);
client_update_class(client);
return client;
}
void client_destroy(Client *client)
{
if (client == NULL) {
return;
}
if (client->frame != None) {
client_destroy_frame(client);
}
dwn_free(client);
}
static inline void client_sync_log(const char *msg)
{
(void)msg;
2025-12-28 03:14:31 +01:00
}
Client *client_manage(Window window)
{
client_sync_log("client_manage: START");
if (dwn == NULL || dwn->display == NULL) {
client_sync_log("client_manage: dwn NULL");
return NULL;
}
Client *existing = client_find_by_window(window);
if (existing != NULL) {
client_sync_log("client_manage: already managed");
return existing;
}
if (client_is_dock(window) || client_is_desktop(window)) {
LOG_DEBUG("Skipping dock/desktop window: %lu", window);
client_sync_log("client_manage: skip dock/desktop");
return NULL;
}
LOG_DEBUG("Managing window: %lu", window);
client_sync_log("client_manage: creating client");
Client *client = client_create(window);
if (client == NULL) {
LOG_ERROR("client_manage: failed to create client for window %lu", window);
client_sync_log("client_manage: create FAILED");
return NULL;
}
client_sync_log("client_manage: checking dialog");
if (client_is_dialog(window)) {
client->flags |= CLIENT_FLOATING;
}
client_sync_log("client_manage: creating frame");
client_create_frame(client);
client_sync_log("client_manage: verifying window");
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, window, &wa)) {
LOG_WARN("client_manage: window %lu disappeared before reparenting", window);
client_sync_log("client_manage: window gone before reparent");
client_destroy(client);
return NULL;
}
client_sync_log("client_manage: reparenting");
client_reparent_to_frame(client);
client_sync_log("client_manage: verifying after reparent");
XSync(dwn->display, False);
2025-12-28 03:14:31 +01:00
if (client->frame == None) {
client_sync_log("client_manage: frame is None after reparent");
LOG_WARN("client_manage: frame creation failed for window %lu", window);
client_destroy(client);
return NULL;
}
XWindowAttributes wa_frame;
if (!XGetWindowAttributes(dwn->display, client->frame, &wa_frame)) {
client_sync_log("client_manage: frame gone after reparent");
LOG_WARN("client_manage: frame %lu no longer exists", client->frame);
client_destroy(client);
return NULL;
}
if (!XGetWindowAttributes(dwn->display, window, &wa)) {
client_sync_log("client_manage: window gone after reparent");
LOG_WARN("client_manage: window %lu disappeared after reparent", window);
client_destroy_frame(client);
client_destroy(client);
return NULL;
}
client_sync_log("client_manage: configuring");
client_configure(client);
client_sync_log("client_manage: adding to list");
client_add_to_list(client);
client_sync_log("client_manage: adding to workspace");
workspace_add_client(dwn->current_workspace, client);
client_sync_log("client_manage: setting EWMH");
atoms_set_window_desktop(window, client->workspace);
atoms_update_client_list();
client_sync_log("client_manage: verifying window again");
if (!XGetWindowAttributes(dwn->display, window, &wa)) {
LOG_WARN("client_manage: window %lu disappeared before event setup", window);
client_sync_log("client_manage: window gone before events");
workspace_remove_client(client->workspace, client);
client_remove_from_list(client);
client_destroy(client);
atoms_update_client_list();
return NULL;
}
client_sync_log("client_manage: selecting input");
XSelectInput(dwn->display, window,
EnterWindowMask | FocusChangeMask | PropertyChangeMask |
StructureNotifyMask);
client_sync_log("client_manage: grabbing button");
XGrabButton(dwn->display, Button1, AnyModifier, window, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
client_sync_log("client_manage: syncing X");
XSync(dwn->display, False);
client_sync_log("client_manage: showing");
client_show(client);
client_sync_log("client_manage: focusing");
client_focus(client);
client_sync_log("client_manage: DONE");
return client;
}
void client_unmanage(Client *client)
{
client_sync_log("client_unmanage: START");
if (client == NULL) {
client_sync_log("client_unmanage: NULL client");
return;
}
LOG_DEBUG("Unmanaging window: %lu", client->window);
client_sync_log("client_unmanage: remove from workspace");
workspace_remove_client(client->workspace, client);
client_sync_log("client_unmanage: remove from list");
client_remove_from_list(client);
client_sync_log("client_unmanage: reparent from frame");
client_reparent_from_frame(client);
client_sync_log("client_unmanage: update EWMH");
atoms_update_client_list();
client_sync_log("client_unmanage: destroy client");
client_destroy(client);
client_sync_log("client_unmanage: focus next");
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused == NULL) {
Client *next = workspace_get_first_client(dwn->current_workspace);
if (next != NULL) {
client_focus(next);
}
}
client_sync_log("client_unmanage: DONE");
}
Client *client_find_by_window(Window window)
{
if (dwn == NULL || window == None) {
return NULL;
}
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->window == window) {
return c;
}
}
return NULL;
}
Client *client_find_by_frame(Window frame)
{
if (dwn == NULL || frame == None) {
return NULL;
}
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->frame == frame) {
return c;
}
}
return NULL;
}
void client_focus(Client *client)
{
client_sync_log("client_focus: START");
if (client == NULL || dwn == NULL || dwn->display == NULL) {
client_sync_log("client_focus: NULL check failed");
return;
}
if (client->window == None) {
LOG_WARN("client_focus: client has invalid window (None)");
client_sync_log("client_focus: window is None");
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_focus: window %lu no longer exists", client->window);
client_sync_log("client_focus: window gone");
return;
}
client_sync_log("client_focus: unfocusing previous");
Workspace *ws = workspace_get(client->workspace);
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
client_unfocus(ws->focused);
}
client_sync_log("client_focus: XSetInputFocus");
XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime);
XSync(dwn->display, False);
2025-12-28 03:14:31 +01:00
client_sync_log("client_focus: updating workspace");
if (ws != NULL) {
ws->focused = client;
}
client_sync_log("client_focus: raising");
client_raise(client);
client_sync_log("client_focus: decorations");
decorations_render(client, true);
client_sync_log("client_focus: EWMH");
atoms_set_active_window(client->window);
client_sync_log("client_focus: DONE");
LOG_DEBUG("Focused window: %s", client->title);
}
void client_unfocus(Client *client)
{
if (client == NULL) {
return;
}
decorations_render(client, false);
}
void client_raise(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
Window win = (client->frame != None) ? client->frame : client->window;
if (win == None) {
return;
}
XRaiseWindow(dwn->display, win);
notifications_raise_all();
}
void client_lower(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
Window win = (client->frame != None) ? client->frame : client->window;
if (win == None) {
return;
}
XLowerWindow(dwn->display, win);
}
void client_minimize(Client *client)
{
if (client == NULL) {
return;
}
client->flags |= CLIENT_MINIMIZED;
client_hide(client);
}
void client_restore(Client *client)
{
if (client == NULL) {
return;
}
client->flags &= ~CLIENT_MINIMIZED;
client_show(client);
client_focus(client);
}
void client_move(Client *client, int x, int y)
{
if (client == NULL) {
return;
}
client->x = x;
client->y = y;
client_configure(client);
}
void client_resize(Client *client, int width, int height)
{
if (client == NULL) {
return;
}
client_apply_size_hints(client, &width, &height);
client->width = width;
client->height = height;
client_configure(client);
}
void client_move_resize(Client *client, int x, int y, int width, int height)
{
if (client == NULL) {
return;
}
client_apply_size_hints(client, &width, &height);
client->x = x;
client->y = y;
client->width = width;
client->height = height;
client_configure(client);
}
void client_configure(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_configure: window no longer exists");
return;
}
int title_height = (dwn->config && dwn->config->show_decorations) ?
config_get_title_height() : 0;
int border = client->border_width;
if (client->frame != None) {
XMoveResizeWindow(dwn->display, client->frame,
client->x - border,
client->y - title_height - border,
client->width + 2 * border,
client->height + title_height + 2 * border);
XMoveResizeWindow(dwn->display, client->window,
border, title_height + border,
client->width, client->height);
} else {
XMoveResizeWindow(dwn->display, client->window,
client->x, client->y,
client->width, client->height);
}
XConfigureEvent ce;
memset(&ce, 0, sizeof(ce));
2025-12-28 03:14:31 +01:00
ce.type = ConfigureNotify;
ce.event = client->window;
ce.window = client->window;
ce.x = client->x;
ce.y = client->y;
ce.width = client->width;
ce.height = client->height;
ce.border_width = 0;
ce.above = None;
ce.override_redirect = False;
XSendEvent(dwn->display, client->window, False, StructureNotifyMask, (XEvent *)&ce);
}
void client_apply_size_hints(Client *client, int *width, int *height)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
XSizeHints hints;
long supplied;
if (!XGetWMNormalHints(dwn->display, client->window, &hints, &supplied)) {
return;
}
if (hints.flags & PMinSize) {
if (*width < hints.min_width) *width = hints.min_width;
if (*height < hints.min_height) *height = hints.min_height;
}
if (hints.flags & PMaxSize) {
if (*width > hints.max_width) *width = hints.max_width;
if (*height > hints.max_height) *height = hints.max_height;
}
if (hints.flags & PResizeInc) {
int base_w = (hints.flags & PBaseSize) ? hints.base_width : 0;
int base_h = (hints.flags & PBaseSize) ? hints.base_height : 0;
*width = base_w + ((*width - base_w) / hints.width_inc) * hints.width_inc;
*height = base_h + ((*height - base_h) / hints.height_inc) * hints.height_inc;
}
}
void client_update_title(Client *client)
{
if (client == NULL) {
return;
}
char *name = atoms_get_window_name(client->window);
if (name != NULL) {
strncpy(client->title, name, sizeof(client->title) - 1);
client->title[sizeof(client->title) - 1] = '\0';
dwn_free(name);
} else {
strncpy(client->title, "Untitled", sizeof(client->title) - 1);
}
if (client->frame != None) {
Workspace *ws = workspace_get(client->workspace);
bool focused = (ws != NULL && ws->focused == client);
decorations_render(client, focused);
}
}
void client_update_class(Client *client)
{
if (client == NULL) {
return;
}
atoms_get_wm_class(client->window, client->class, NULL, sizeof(client->class));
}
void client_set_fullscreen(Client *client, bool fullscreen)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_set_fullscreen: window %lu no longer exists", client->window);
return;
}
if (fullscreen) {
if (!(client->flags & CLIENT_FULLSCREEN)) {
client->old_x = client->x;
client->old_y = client->y;
client->old_width = client->width;
client->old_height = client->height;
}
client->flags |= CLIENT_FULLSCREEN;
client->x = 0;
client->y = 0;
client->width = dwn->screen_width;
client->height = dwn->screen_height;
if (client->frame != None) {
XUnmapWindow(dwn->display, client->frame);
XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, dwn->root, 0, 0);
XSync(dwn->display, False);
}
XMoveResizeWindow(dwn->display, client->window,
0, 0, client->width, client->height);
XMapWindow(dwn->display, client->window);
XRaiseWindow(dwn->display, client->window);
} else {
client->flags &= ~CLIENT_FULLSCREEN;
client->x = client->old_x;
client->y = client->old_y;
client->width = client->old_width;
client->height = client->old_height;
if (client->frame != None) {
int title_height = config_get_title_height();
int border = client->border_width;
XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, client->frame,
border, title_height + border);
XSync(dwn->display, False);
XMapWindow(dwn->display, client->frame);
}
client_configure(client);
}
}
void client_toggle_fullscreen(Client *client)
{
if (client == NULL) {
return;
}
client_set_fullscreen(client, !(client->flags & CLIENT_FULLSCREEN));
}
void client_set_floating(Client *client, bool floating)
{
if (client == NULL) {
return;
}
if (floating) {
client->flags |= CLIENT_FLOATING;
} else {
client->flags &= ~CLIENT_FLOATING;
}
}
void client_toggle_floating(Client *client)
{
if (client == NULL) {
return;
}
client_set_floating(client, !(client->flags & CLIENT_FLOATING));
}
bool client_is_floating(Client *client)
{
return client != NULL && (client->flags & CLIENT_FLOATING);
}
bool client_is_fullscreen(Client *client)
{
return client != NULL && (client->flags & CLIENT_FULLSCREEN);
}
bool client_is_minimized(Client *client)
{
return client != NULL && (client->flags & CLIENT_MINIMIZED);
}
bool client_is_dialog(Window window)
{
Atom type;
if (atoms_get_window_type(window, &type)) {
return type == ewmh.NET_WM_WINDOW_TYPE_DIALOG;
}
Window transient_for = None;
if (XGetTransientForHint(dwn->display, window, &transient_for)) {
return transient_for != None;
}
return false;
}
bool client_is_dock(Window window)
{
Atom type;
if (atoms_get_window_type(window, &type)) {
return type == ewmh.NET_WM_WINDOW_TYPE_DOCK;
}
return false;
}
bool client_is_desktop(Window window)
{
Atom type;
if (atoms_get_window_type(window, &type)) {
return type == ewmh.NET_WM_WINDOW_TYPE_DESKTOP;
}
return false;
}
void client_create_frame(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
LOG_ERROR("client_create_frame: invalid parameters");
return;
}
if (!dwn->config || !dwn->config->show_decorations) {
return;
}
if (client->window == None) {
LOG_ERROR("client_create_frame: client has no valid window");
return;
}
int title_height = config_get_title_height();
int border = client->border_width;
int frame_width = client->width + 2 * border;
int frame_height = client->height + title_height + 2 * border;
if (frame_width <= 0 || frame_height <= 0) {
LOG_ERROR("client_create_frame: invalid dimensions (%dx%d)", frame_width, frame_height);
return;
}
XSetWindowAttributes swa;
memset(&swa, 0, sizeof(swa));
2025-12-28 03:14:31 +01:00
swa.override_redirect = True;
swa.background_pixel = dwn->config->colors.title_unfocused_bg;
swa.border_pixel = dwn->config->colors.border_unfocused;
swa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
ExposureMask | EnterWindowMask;
client->frame = XCreateWindow(dwn->display, dwn->root,
client->x - border,
client->y - title_height - border,
frame_width,
frame_height,
0,
CopyFromParent, InputOutput, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask,
&swa);
if (client->frame == None) {
LOG_ERROR("client_create_frame: XCreateWindow failed for window %lu", client->window);
return;
}
XSetWindowBorderWidth(dwn->display, client->frame, border);
XSync(dwn->display, False);
2025-12-28 03:14:31 +01:00
LOG_DEBUG("Created frame %lu for window %lu", client->frame, client->window);
}
void client_destroy_frame(Client *client)
{
if (client == NULL || client->frame == None) {
return;
}
XDestroyWindow(dwn->display, client->frame);
client->frame = None;
}
void client_reparent_to_frame(Client *client)
{
if (client == NULL || client->frame == None) {
return;
}
if (dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
LOG_WARN("client_reparent_to_frame: client has no valid window");
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_reparent_to_frame: window %lu no longer exists", client->window);
return;
}
int title_height = config_get_title_height();
int border = client->border_width;
XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, client->frame,
border, title_height + border);
XSync(dwn->display, False);
XSaveContext(dwn->display, client->window, XUniqueContext(), (XPointer)client);
}
void client_reparent_from_frame(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->frame == None || client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_WARN("client_reparent_from_frame: window %lu no longer exists", client->window);
return;
}
XSync(dwn->display, False);
XReparentWindow(dwn->display, client->window, dwn->root,
client->x, client->y);
XSync(dwn->display, False);
}
void client_show(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_show: window no longer exists");
return;
}
if (client->frame != None) {
XMapWindow(dwn->display, client->frame);
}
XMapWindow(dwn->display, client->window);
}
void client_hide(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_hide: window no longer exists");
return;
}
if (client->frame != None) {
XUnmapWindow(dwn->display, client->frame);
}
XUnmapWindow(dwn->display, client->window);
}
bool client_is_visible(Client *client)
{
if (client == NULL) {
return false;
}
if (client->flags & CLIENT_MINIMIZED) {
return false;
}
if (!(client->flags & CLIENT_STICKY) &&
client->workspace != (unsigned int)dwn->current_workspace) {
return false;
}
return true;
}
void client_close(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XWindowAttributes wa;
if (!XGetWindowAttributes(dwn->display, client->window, &wa)) {
LOG_DEBUG("client_close: window no longer exists");
return;
}
if (atoms_window_supports_protocol(client->window, icccm.WM_DELETE_WINDOW)) {
atoms_send_protocol(client->window, icccm.WM_DELETE_WINDOW, CurrentTime);
} else {
client_kill(client);
}
}
void client_kill(Client *client)
{
if (client == NULL || dwn == NULL || dwn->display == NULL) {
return;
}
if (client->window == None) {
return;
}
XKillClient(dwn->display, client->window);
}
void client_add_to_list(Client *client)
{
if (client == NULL || dwn == NULL) {
return;
}
client->next = dwn->client_list;
client->prev = NULL;
if (dwn->client_list != NULL) {
dwn->client_list->prev = client;
}
dwn->client_list = client;
dwn->client_count++;
}
void client_remove_from_list(Client *client)
{
if (client == NULL || dwn == NULL) {
return;
}
if (client->prev != NULL) {
client->prev->next = client->next;
} else {
dwn->client_list = client->next;
}
if (client->next != NULL) {
client->next->prev = client->prev;
}
dwn->client_count--;
}
int client_count(void)
{
return dwn != NULL ? dwn->client_count : 0;
}
int client_count_on_workspace(int workspace)
{
int count = 0;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace == (unsigned int)workspace) {
count++;
}
}
return count;
}
Client *client_get_next(Client *client)
{
return client != NULL ? client->next : NULL;
}
Client *client_get_prev(Client *client)
{
return client != NULL ? client->prev : NULL;
}
Client *client_get_first(void)
{
return dwn != NULL ? dwn->client_list : NULL;
}
Client *client_get_last(void)
{
if (dwn == NULL || dwn->client_list == NULL) {
return NULL;
}
Client *c = dwn->client_list;
while (c->next != NULL) {
c = c->next;
}
return c;
}