feat: add expose all windows and Alt-drag window manipulation

feat: improve maximize behavior with floating state and constraints
feat: enhance resize cursors and edge detection
This commit is contained in:
retoor 2026-02-16 02:24:05 +01:00
parent 6b143990bb
commit 527f644a94
28 changed files with 249 additions and 29 deletions

BIN
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,7 +19,7 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
include/applauncher.h include/decorations.h include/demo.h \
include/layout.h include/api.h include/marks.h
include/layout.h include/api.h include/marks.h include/panel.h
include/keys.h:
include/dwn.h:
include/client.h:
@ -53,3 +53,4 @@ include/demo.h:
include/layout.h:
include/api.h:
include/marks.h:
include/panel.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -125,6 +125,7 @@ struct Client {
char title[256];
char class[64];
SnapConstraint snap;
bool floating_before_maximize;
unsigned long taskbar_color;
ColorAnimation title_anim;
TextGlowAnimation text_glow;
@ -219,6 +220,17 @@ typedef struct {
int last_mouse_x;
int last_mouse_y;
bool mouse_tracking_initialized;
Cursor cursor_default;
Cursor cursor_resize_top_left;
Cursor cursor_resize_top_right;
Cursor cursor_resize_bottom_left;
Cursor cursor_resize_bottom_right;
Cursor cursor_resize_left;
Cursor cursor_resize_right;
Cursor cursor_resize_top;
Cursor cursor_resize_bottom;
Cursor cursor_move;
} DWNState;
#define PHASE_OFFSET_PANEL_BG 0.0f

View File

@ -100,6 +100,7 @@ void key_resize_window_up(void);
void key_resize_window_down(void);
void key_set_mark(void);
void key_goto_mark(void);
void key_expose_all(void);
void tutorial_start(void);
void tutorial_stop(void);

View File

@ -53,6 +53,7 @@ Client *client_create(Window window)
client->snap.horizontal = SNAP_H_NONE;
client->snap.vertical = SNAP_V_NONE;
client->snap.timestamp = 0;
client->floating_before_maximize = false;
XWindowAttributes wa;
int orig_width = 640, orig_height = 480;
@ -234,6 +235,16 @@ Client *client_manage(Window window)
XGrabButton(dwn->display, Button1, AnyModifier, window, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
unsigned int lock_combos[] = { 0, Mod2Mask, LockMask, Mod2Mask | LockMask };
for (size_t gi = 0; gi < sizeof(lock_combos) / sizeof(lock_combos[0]); gi++) {
XGrabButton(dwn->display, Button3, Mod1Mask | lock_combos[gi], window, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, None);
XGrabButton(dwn->display, Button1, Mod1Mask | lock_combos[gi], window, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, None);
}
client_sync_log("client_manage: syncing X");
XSync(dwn->display, False);
@ -905,6 +916,7 @@ void client_set_maximize(Client *client, bool maximized)
client->old_y = client->y;
client->old_width = client->width;
client->old_height = client->height;
client->floating_before_maximize = (client->flags & CLIENT_FLOATING) != 0;
}
client->flags |= CLIENT_MAXIMIZED;
@ -933,6 +945,10 @@ void client_set_maximize(Client *client, bool maximized)
} else {
client->flags &= ~CLIENT_MAXIMIZED;
if (!client->floating_before_maximize) {
client->flags &= ~CLIENT_FLOATING;
}
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);
api_emit_window_maximized(client->window, false);
@ -942,8 +958,33 @@ void client_set_maximize(Client *client, bool maximized)
client->width = client->old_width;
client->height = client->old_height;
if (client->width < 50) client->width = 50;
if (client->height < 50) client->height = 50;
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
if (client->x + client->width > area_x + area_w) {
client->x = area_x + area_w - client->width;
}
if (client->x < area_x) {
client->x = area_x;
}
if (client->y + client->height > area_y + area_h) {
client->y = area_y + area_h - client->height;
}
if (client->y < area_y) {
client->y = area_y;
}
client_configure(client);
decorations_render(client, true);
client_raise(client);
client_focus(client, true);
if (!client->floating_before_maximize) {
workspace_arrange_current();
}
}
}

View File

@ -17,7 +17,7 @@
#define BUTTON_SIZE 16
#define BUTTON_PADDING 4
#define RESIZE_EDGE 8
#define RESIZE_EDGE 12
#define DEC_XFT_COLOR_CACHE_SIZE 8

View File

@ -18,6 +18,7 @@
#include "layout.h"
#include "api.h"
#include "marks.h"
#include "panel.h"
#include <stdio.h>
#include <string.h>
@ -610,6 +611,8 @@ void keys_setup_defaults(void)
keys_bind(MOD_SUPER, XK_m, key_set_mark, "Set window mark");
keys_bind(MOD_SUPER, XK_apostrophe, key_goto_mark, "Go to marked window");
keys_bind(MOD_ALT, XK_space, key_expose_all, "Expose all windows");
}
@ -936,8 +939,11 @@ void key_show_shortcuts(void)
"Alt+F9 Toggle minimize\n"
"Alt+F10 Toggle maximize\n"
"Alt+F11 Toggle fullscreen\n"
"Alt+Space Expose all windows\n"
"Super+F9 Toggle floating\n"
"Super+K Kill all applications\n"
"Alt+Drag Move window (any position)\n"
"Alt+Right-Drag Resize window (any position)\n"
"\n"
"=== Workspaces ===\n"
"F1-F9 Switch to workspace\n"
@ -1327,6 +1333,36 @@ void key_resize_window_down(void)
decorations_render(c, true);
}
void key_expose_all(void)
{
if (dwn == NULL) {
return;
}
if (dwn->desktop_shown) {
dwn->desktop_shown = false;
dwn->desktop_minimized_count = 0;
}
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)dwn->current_workspace) {
continue;
}
if (client_is_fullscreen(c)) {
client_set_fullscreen(c, false);
}
if (client_is_maximized(c)) {
client_set_maximize(c, false);
}
if (client_is_minimized(c)) {
client_restore(c);
}
}
workspace_arrange_current();
panel_render_all();
}
void key_set_mark(void)
{
marks_start_set_mode();

View File

@ -497,6 +497,8 @@ static void handle_leave_notify(XCrossingEvent *ev)
}
}
static Cursor cursor_for_direction(int direction);
static void handle_button_press(XButtonEvent *ev)
{
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
@ -560,6 +562,50 @@ static void handle_button_press(XButtonEvent *ev)
}
if (is_client_window) {
bool alt_held = (ev->state & Mod1Mask) != 0;
if (alt_held && ev->button == Button3) {
client_focus(c, true);
int cx = c->x + c->width / 2;
int cy = c->y + c->height / 2;
int dir = 0;
if (ev->x_root < cx) dir |= RESIZE_LEFT; else dir |= RESIZE_RIGHT;
if (ev->y_root < cy) dir |= RESIZE_TOP; else dir |= RESIZE_BOTTOM;
dwn->drag_client = c;
dwn->drag_start_x = ev->x_root;
dwn->drag_start_y = ev->y_root;
dwn->drag_orig_x = c->x;
dwn->drag_orig_y = c->y;
dwn->drag_orig_w = c->width;
dwn->drag_orig_h = c->height;
dwn->resizing = true;
dwn->drag_direction = dir;
XGrabPointer(dwn->display, dwn->root, True,
PointerMotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None,
cursor_for_direction(dir), CurrentTime);
return;
}
if (alt_held && ev->button == Button1) {
client_focus(c, true);
dwn->drag_client = c;
dwn->drag_start_x = ev->x_root;
dwn->drag_start_y = ev->y_root;
dwn->drag_orig_x = c->x;
dwn->drag_orig_y = c->y;
dwn->resizing = false;
dwn->drag_direction = 0;
XGrabPointer(dwn->display, dwn->root, True,
PointerMotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None,
dwn->cursor_move, CurrentTime);
return;
}
XAllowEvents(dwn->display, ReplayPointer, ev->time);
client_focus(c, true);
return;
@ -588,7 +634,8 @@ static void handle_button_press(XButtonEvent *ev)
XGrabPointer(dwn->display, c->frame, True,
PointerMotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
GrabModeAsync, GrabModeAsync, None,
dwn->cursor_move, CurrentTime);
}
return;
}
@ -610,7 +657,8 @@ static void handle_button_press(XButtonEvent *ev)
XGrabPointer(dwn->display, c->frame, True,
PointerMotionMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
GrabModeAsync, GrabModeAsync, None,
cursor_for_direction(direction), CurrentTime);
}
}
}
@ -645,6 +693,24 @@ static void handle_button_release(XButtonEvent *ev)
api_emit_mouse_button_released(ev->button, ev->x_root, ev->y_root);
}
static Cursor cursor_for_direction(int direction)
{
bool left = (direction & RESIZE_LEFT) != 0;
bool right = (direction & RESIZE_RIGHT) != 0;
bool top = (direction & RESIZE_TOP) != 0;
bool bottom = (direction & RESIZE_BOTTOM) != 0;
if (top && left) return dwn->cursor_resize_top_left;
if (top && right) return dwn->cursor_resize_top_right;
if (bottom && left) return dwn->cursor_resize_bottom_left;
if (bottom && right) return dwn->cursor_resize_bottom_right;
if (left) return dwn->cursor_resize_left;
if (right) return dwn->cursor_resize_right;
if (top) return dwn->cursor_resize_top;
if (bottom) return dwn->cursor_resize_bottom;
return dwn->cursor_default;
}
static void handle_motion_notify(XMotionEvent *ev)
{
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
@ -661,7 +727,7 @@ static void handle_motion_notify(XMotionEvent *ev)
slider_handle_motion(fade_speed_control.slider, ev->x, ev->y);
return;
}
if (fade_intensity_control.slider != NULL && fade_intensity_control.slider->visible && ev->window == fade_intensity_control.slider->window) {
LOG_DEBUG("Fade intensity slider motion: x=%d, y=%d", ev->x, ev->y);
slider_handle_motion(fade_intensity_control.slider, ev->x, ev->y);
@ -669,6 +735,17 @@ static void handle_motion_notify(XMotionEvent *ev)
}
if (dwn->drag_client == NULL) {
Client *c = client_find_by_frame(ev->window);
if (c != NULL && c->frame != None) {
int direction = 0;
if (decorations_hit_test_resize_area(c, ev->x, ev->y, &direction)) {
XDefineCursor(dwn->display, c->frame, cursor_for_direction(direction));
} else if (decorations_hit_test_title_bar(c, ev->x, ev->y)) {
XDefineCursor(dwn->display, c->frame, dwn->cursor_move);
} else {
XDefineCursor(dwn->display, c->frame, dwn->cursor_default);
}
}
return;
}
@ -986,8 +1063,18 @@ int dwn_init(void)
StructureNotifyMask | PropertyChangeMask |
ButtonPressMask | PointerMotionMask);
Cursor cursor = XCreateFontCursor(dwn->display, XC_left_ptr);
XDefineCursor(dwn->display, dwn->root, cursor);
dwn->cursor_default = XCreateFontCursor(dwn->display, XC_left_ptr);
dwn->cursor_resize_top_left = XCreateFontCursor(dwn->display, XC_top_left_corner);
dwn->cursor_resize_top_right = XCreateFontCursor(dwn->display, XC_top_right_corner);
dwn->cursor_resize_bottom_left = XCreateFontCursor(dwn->display, XC_bottom_left_corner);
dwn->cursor_resize_bottom_right = XCreateFontCursor(dwn->display, XC_bottom_right_corner);
dwn->cursor_resize_left = XCreateFontCursor(dwn->display, XC_left_side);
dwn->cursor_resize_right = XCreateFontCursor(dwn->display, XC_right_side);
dwn->cursor_resize_top = XCreateFontCursor(dwn->display, XC_top_side);
dwn->cursor_resize_bottom = XCreateFontCursor(dwn->display, XC_bottom_side);
dwn->cursor_move = XCreateFontCursor(dwn->display, XC_fleur);
XDefineCursor(dwn->display, dwn->root, dwn->cursor_default);
scan_existing_windows();
@ -1055,6 +1142,17 @@ void dwn_cleanup(void)
}
if (dwn->display != NULL) {
if (dwn->cursor_default) XFreeCursor(dwn->display, dwn->cursor_default);
if (dwn->cursor_resize_top_left) XFreeCursor(dwn->display, dwn->cursor_resize_top_left);
if (dwn->cursor_resize_top_right) XFreeCursor(dwn->display, dwn->cursor_resize_top_right);
if (dwn->cursor_resize_bottom_left) XFreeCursor(dwn->display, dwn->cursor_resize_bottom_left);
if (dwn->cursor_resize_bottom_right) XFreeCursor(dwn->display, dwn->cursor_resize_bottom_right);
if (dwn->cursor_resize_left) XFreeCursor(dwn->display, dwn->cursor_resize_left);
if (dwn->cursor_resize_right) XFreeCursor(dwn->display, dwn->cursor_resize_right);
if (dwn->cursor_resize_top) XFreeCursor(dwn->display, dwn->cursor_resize_top);
if (dwn->cursor_resize_bottom) XFreeCursor(dwn->display, dwn->cursor_resize_bottom);
if (dwn->cursor_move) XFreeCursor(dwn->display, dwn->cursor_move);
XCloseDisplay(dwn->display);
}

View File

@ -405,24 +405,27 @@ void panel_render(Panel *panel)
x += width + WIDGET_SPACING;
panel_render_layout_indicator(panel, x, &width);
x += width + WIDGET_SPACING;
panel_render_taskbar(panel, x, &width);
int taskbar_start = x + width + WIDGET_SPACING;
int systray_actual_width = systray_get_width();
int systray_x = panel->width - systray_actual_width - PANEL_PADDING;
/* Render fade controls before systray */
int fade_width = fade_controls_get_width();
int fade_x = systray_x - fade_width - WIDGET_SPACING;
fade_controls_render(panel, fade_x);
systray_render(panel, systray_x, &width);
int ai_x = 0;
if (dwn->ai_enabled) {
int ai_width = panel_text_width("[AI]", 4);
ai_x = fade_x - ai_width - WIDGET_SPACING;
}
panel_render_taskbar(panel, taskbar_start, &width);
if (dwn->ai_enabled) {
int ai_x = systray_x - 60;
panel_render_ai_status(panel, ai_x, &width);
}
fade_controls_render(panel, fade_x);
systray_render(panel, systray_x, &width);
} else {
int date_width = 0;
@ -535,8 +538,14 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
int current_x = x;
int systray_actual_width = systray_get_width();
int ai_width = dwn->ai_enabled ? 60 : 0;
int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2;
int fade_width = fade_controls_get_width();
int ai_width = 0;
if (dwn->ai_enabled) {
ai_width = panel_text_width("[AI]", 4);
}
int right_reserve = PANEL_PADDING + systray_actual_width + WIDGET_SPACING
+ fade_width + WIDGET_SPACING
+ (ai_width > 0 ? ai_width + WIDGET_SPACING : 0);
int available_width = panel->width - x - right_reserve;
if (available_width < 0) available_width = 0;
int item_count = 0;
@ -790,8 +799,14 @@ Client *panel_hit_test_taskbar(Panel *panel, int x, int y)
}
int systray_actual_width = systray_get_width();
int ai_width = dwn->ai_enabled ? 60 : 0;
int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2;
int fade_width = fade_controls_get_width();
int ai_width = 0;
if (dwn->ai_enabled) {
ai_width = panel_text_width("[AI]", 4);
}
int right_reserve = PANEL_PADDING + systray_actual_width + WIDGET_SPACING
+ fade_width + WIDGET_SPACING
+ (ai_width > 0 ? ai_width + WIDGET_SPACING : 0);
int available_width = panel->width - taskbar_start - right_reserve;
if (available_width < 0) available_width = 0;
int item_count = 0;

View File

@ -1371,21 +1371,37 @@ void fade_controls_render(Panel *panel, int x)
int fade_controls_get_width(void)
{
/* Calculate approximate width for both controls */
return 80; /* Approximate width for "S:0.5 I:100%" + spacing */
float speed = config_get_fade_speed();
char speed_label[16];
snprintf(speed_label, sizeof(speed_label), "S:%.1f", speed);
float intensity = config_get_fade_intensity();
char intensity_label[16];
snprintf(intensity_label, sizeof(intensity_label), "I:%d%%", (int)(intensity * 100));
return get_text_width(speed_label) + SYSTRAY_SPACING + get_text_width(intensity_label);
}
int fade_controls_hit_test(int x)
{
/* Check if x position hits any fade control icon */
/* Return 1 for speed, 2 for intensity, 0 for none */
if (x >= fade_speed_icon_x && x < fade_speed_icon_x + 40) {
return 1; /* Speed control */
float speed = config_get_fade_speed();
char speed_label[16];
snprintf(speed_label, sizeof(speed_label), "S:%.1f", speed);
int speed_width = get_text_width(speed_label);
if (x >= fade_speed_icon_x && x < fade_speed_icon_x + speed_width) {
return 1;
}
if (x >= fade_intensity_icon_x && x < fade_intensity_icon_x + 50) {
return 2; /* Intensity control */
float intensity = config_get_fade_intensity();
char intensity_label[16];
snprintf(intensity_label, sizeof(intensity_label), "I:%d%%", (int)(intensity * 100));
int intensity_width = get_text_width(intensity_label);
if (x >= fade_intensity_icon_x && x < fade_intensity_icon_x + intensity_width) {
return 2;
}
return 0;
}