/* * DWN - Desktop Window Manager * retoor * 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 "layout.h" #include #include #include #include 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; client->mru_next = NULL; client->mru_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(); } } client->width = orig_width; client->height = orig_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; } 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); 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 MRU"); workspace_mru_remove(client->workspace, client); 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"); XGrabServer(dwn->display); Workspace *ws = workspace_get_current(); if (ws != NULL && ws->focused == NULL) { Client *next = workspace_mru_get_previous(dwn->current_workspace, NULL); if (next != NULL) { client_focus(next); } else { XSetInputFocus(dwn->display, dwn->root, RevertToPointerRoot, CurrentTime); atoms_set_active_window(None); } } XUngrabServer(dwn->display); XSync(dwn->display, False); 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); client_sync_log("client_focus: updating workspace"); if (ws != NULL) { ws->focused = client; workspace_mru_push(client->workspace, 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)); 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; atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, true); 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); XSync(dwn->display, False); } else { client->flags &= ~CLIENT_FULLSCREEN; atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, false); 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); decorations_render(client, true); client_raise(client); XSync(dwn->display, False); } } 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_maximized(Client *client) { return client != NULL && (client->flags & CLIENT_MAXIMIZED); } void client_set_maximize(Client *client, bool maximized) { 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_maximize: window %lu no longer exists", client->window); return; } if (maximized) { if (!(client->flags & CLIENT_MAXIMIZED)) { client->old_x = client->x; client->old_y = client->y; client->old_width = client->width; client->old_height = client->height; } client->flags |= CLIENT_MAXIMIZED; client->flags |= CLIENT_FLOATING; atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, true); atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, true); int area_x, area_y, area_width, area_height; layout_get_usable_area(&area_x, &area_y, &area_width, &area_height); int gap = config_get_gap(); int title_height = config_get_title_height(); int border = client->border_width; client->x = area_x + gap + border; client->y = area_y + gap + title_height + border; client->width = area_width - 2 * gap - 2 * border; client->height = area_height - 2 * gap - title_height - 2 * border; client_configure(client); decorations_render(client, true); client_raise(client); } else { client->flags &= ~CLIENT_MAXIMIZED; atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, false); atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, false); client->x = client->old_x; client->y = client->old_y; client->width = client->old_width; client->height = client->old_height; client_configure(client); decorations_render(client, true); } } void client_toggle_maximize(Client *client) { if (client == NULL) { return; } client_set_maximize(client, !(client->flags & CLIENT_MAXIMIZED)); } 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)); 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); 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; }