feat: add maximize window functionality and MRU-based focus management

feat: add focus follow delay configuration
feat: add demo mode timing configurations and wait conditions
docs: update configuration documentation for new options
This commit is contained in:
2025-12-28 10:23:03 +01:00
parent d0b1669cb4
commit 395583aea9
31 changed files with 896 additions and 208 deletions
+90 -1
View File
@@ -11,6 +11,7 @@
#include "workspace.h"
#include "decorations.h"
#include "notifications.h"
#include "layout.h"
#include <string.h>
#include <stdlib.h>
@@ -43,6 +44,8 @@ Client *client_create(Window window)
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;
@@ -242,6 +245,9 @@ void client_unmanage(Client *client)
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);
@@ -258,14 +264,22 @@ void client_unmanage(Client *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_get_first_client(dwn->current_workspace);
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");
}
@@ -336,6 +350,7 @@ void client_focus(Client *client)
if (ws != NULL) {
ws->focused = client;
workspace_mru_push(client->workspace, client);
}
client_sync_log("client_focus: raising");
@@ -672,6 +687,80 @@ void client_toggle_floating(Client *client)
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)
{
+19
View File
@@ -53,6 +53,7 @@ void config_set_defaults(Config *cfg)
strncpy(cfg->launcher, "dmenu_run", sizeof(cfg->launcher) - 1);
strncpy(cfg->file_manager, "thunar", sizeof(cfg->file_manager) - 1);
cfg->focus_mode = FOCUS_CLICK;
cfg->focus_follow_delay_ms = 100;
cfg->show_decorations = true;
cfg->border_width = DEFAULT_BORDER_WIDTH;
@@ -83,6 +84,10 @@ void config_set_defaults(Config *cfg)
strncpy(cfg->autostart_path, autostart_path, sizeof(cfg->autostart_path) - 1);
dwn_free(autostart_path);
}
cfg->demo_step_delay_ms = 4000;
cfg->demo_ai_timeout_ms = 15000;
cfg->demo_window_timeout_ms = 5000;
}
@@ -110,6 +115,9 @@ static void handle_config_entry(const char *section, const char *key,
} else {
cfg->focus_mode = FOCUS_CLICK;
}
} else if (strcmp(key, "focus_follow_delay") == 0) {
int val = atoi(value);
cfg->focus_follow_delay_ms = (val >= 0 && val <= 1000) ? val : 100;
} else if (strcmp(key, "decorations") == 0) {
cfg->show_decorations = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
}
@@ -172,6 +180,17 @@ static void handle_config_entry(const char *section, const char *key,
dwn_free(expanded);
}
}
} else if (strcmp(section, "demo") == 0) {
if (strcmp(key, "step_delay") == 0) {
int val = atoi(value);
cfg->demo_step_delay_ms = (val >= 1000 && val <= 30000) ? val : 4000;
} else if (strcmp(key, "ai_timeout") == 0) {
int val = atoi(value);
cfg->demo_ai_timeout_ms = (val >= 5000 && val <= 60000) ? val : 15000;
} else if (strcmp(key, "window_timeout") == 0) {
int val = atoi(value);
cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
}
}
}
+580 -198
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -608,7 +608,7 @@ void key_toggle_maximize(void)
{
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused != NULL) {
client_toggle_fullscreen(ws->focused);
client_toggle_maximize(ws->focused);
}
}
+50 -1
View File
@@ -369,7 +369,29 @@ static void handle_enter_notify(XCrossingEvent *ev)
}
if (c != NULL && c->workspace == (unsigned int)dwn->current_workspace) {
client_focus(c);
int delay = dwn->config->focus_follow_delay_ms;
if (delay <= 0) {
client_focus(c);
} else {
dwn->pending_focus_client = c;
dwn->pending_focus_time = get_time_ms() + delay;
}
}
}
static void handle_leave_notify(XCrossingEvent *ev)
{
if (ev == NULL || dwn == NULL) {
return;
}
Client *c = client_find_by_frame(ev->window);
if (c == NULL) {
c = client_find_by_window(ev->window);
}
if (c != NULL && dwn->pending_focus_client == c) {
dwn->pending_focus_client = NULL;
}
}
@@ -568,6 +590,17 @@ static void handle_client_message(XClientMessageEvent *ev)
client_set_fullscreen(c, set);
}
}
if (prop1 == ewmh.NET_WM_STATE_MAXIMIZED_VERT ||
prop1 == ewmh.NET_WM_STATE_MAXIMIZED_HORZ ||
prop2 == ewmh.NET_WM_STATE_MAXIMIZED_VERT ||
prop2 == ewmh.NET_WM_STATE_MAXIMIZED_HORZ) {
if (toggle) {
client_toggle_maximize(c);
} else {
client_set_maximize(c, set);
}
}
}
} else if (ev->message_type == ewmh.NET_CURRENT_DESKTOP) {
int desktop = ev->data.l[0];
@@ -635,6 +668,9 @@ void dwn_handle_event(XEvent *ev)
case EnterNotify:
handle_enter_notify(&ev->xcrossing);
break;
case LeaveNotify:
handle_leave_notify(&ev->xcrossing);
break;
case ButtonPress:
handle_button_press(&ev->xbutton);
break;
@@ -840,6 +876,19 @@ void dwn_run(void)
notifications_update();
if (dwn->pending_focus_client != NULL) {
long now_focus = get_time_ms();
if (now_focus >= dwn->pending_focus_time) {
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused != dwn->pending_focus_client) {
if (dwn->pending_focus_client->workspace == (unsigned int)dwn->current_workspace) {
client_focus(dwn->pending_focus_client);
}
}
dwn->pending_focus_client = NULL;
}
}
long now = get_time_ms();
if (now - last_news_update >= 16) {
+2 -3
View File
@@ -391,12 +391,11 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
XFillRectangle(dpy, panel->buffer, dwn->gc,
current_x, 2, item_width - 2, panel->height - 4);
char title[64];
char title[260];
if (minimized) {
snprintf(title, sizeof(title), "[%s]", c->title);
} else {
strncpy(title, c->title, sizeof(title) - 1);
title[sizeof(title) - 1] = '\0';
snprintf(title, sizeof(title), "%s", c->title);
}
int max_text_width = item_width - 8;
+74
View File
@@ -29,6 +29,8 @@ void workspace_init(void)
ws->clients = NULL;
ws->focused = NULL;
ws->mru_head = NULL;
ws->mru_tail = NULL;
ws->layout = (dwn->config != NULL) ?
dwn->config->default_layout : LAYOUT_TILING;
ws->master_ratio = (dwn->config != NULL) ?
@@ -444,3 +446,75 @@ void workspace_focus_master(void)
client_focus(master);
}
}
void workspace_mru_push(int workspace, Client *client)
{
if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
return;
}
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return;
}
workspace_mru_remove(workspace, client);
client->mru_next = ws->mru_head;
client->mru_prev = NULL;
if (ws->mru_head != NULL) {
ws->mru_head->mru_prev = client;
}
ws->mru_head = client;
if (ws->mru_tail == NULL) {
ws->mru_tail = client;
}
}
void workspace_mru_remove(int workspace, Client *client)
{
if (client == NULL || workspace < 0 || workspace >= MAX_WORKSPACES) {
return;
}
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return;
}
if (client->mru_prev != NULL) {
client->mru_prev->mru_next = client->mru_next;
} else if (ws->mru_head == client) {
ws->mru_head = client->mru_next;
}
if (client->mru_next != NULL) {
client->mru_next->mru_prev = client->mru_prev;
} else if (ws->mru_tail == client) {
ws->mru_tail = client->mru_prev;
}
client->mru_next = NULL;
client->mru_prev = NULL;
}
Client *workspace_mru_get_previous(int workspace, Client *current)
{
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return NULL;
}
Client *candidate = ws->mru_head;
while (candidate != NULL) {
if (candidate != current && !client_is_minimized(candidate)) {
return candidate;
}
candidate = candidate->mru_next;
}
return NULL;
}