feat: add composable window snapping

This commit is contained in:
2025-12-28 11:21:03 +01:00
parent 395583aea9
commit beda6ec573
17 changed files with 210 additions and 64 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -18,7 +18,8 @@ build/client.o: src/client.c include/client.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-server.h \ /usr/include/dbus-1.0/dbus/dbus-server.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \ /usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \ /usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/layout.h /usr/include/dbus-1.0/dbus/dbus-threads.h include/layout.h \
include/panel.h
include/client.h: include/client.h:
include/dwn.h: include/dwn.h:
include/atoms.h: include/atoms.h:
@@ -46,3 +47,4 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h: /usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h: /usr/include/dbus-1.0/dbus/dbus-threads.h:
include/layout.h: include/layout.h:
include/panel.h:
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -18,7 +18,8 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \ /usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \ /usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \ /usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
include/applauncher.h include/decorations.h include/demo.h include/applauncher.h include/decorations.h include/demo.h \
include/layout.h
include/keys.h: include/keys.h:
include/dwn.h: include/dwn.h:
include/client.h: include/client.h:
@@ -49,3 +50,4 @@ include/news.h:
include/applauncher.h: include/applauncher.h:
include/decorations.h: include/decorations.h:
include/demo.h: include/demo.h:
include/layout.h:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+21
View File
@@ -62,6 +62,26 @@ typedef enum {
CLIENT_MAXIMIZED = (1 << 5) CLIENT_MAXIMIZED = (1 << 5)
} ClientFlags; } ClientFlags;
typedef enum {
SNAP_H_NONE = 0,
SNAP_H_LEFT,
SNAP_H_RIGHT,
SNAP_H_FULL
} SnapHorizontal;
typedef enum {
SNAP_V_NONE = 0,
SNAP_V_TOP,
SNAP_V_BOTTOM,
SNAP_V_FULL
} SnapVertical;
typedef struct {
SnapHorizontal horizontal;
SnapVertical vertical;
long timestamp;
} SnapConstraint;
typedef struct Client Client; typedef struct Client Client;
typedef struct Workspace Workspace; typedef struct Workspace Workspace;
typedef struct Monitor Monitor; typedef struct Monitor Monitor;
@@ -80,6 +100,7 @@ struct Client {
unsigned int workspace; unsigned int workspace;
char title[256]; char title[256];
char class[64]; char class[64];
SnapConstraint snap;
Client *next; Client *next;
Client *prev; Client *prev;
Client *mru_next; Client *mru_next;
+2
View File
@@ -83,6 +83,8 @@ void key_show_shortcuts(void);
void key_start_tutorial(void); void key_start_tutorial(void);
void key_snap_left(void); void key_snap_left(void);
void key_snap_right(void); void key_snap_right(void);
void key_snap_up(void);
void key_snap_down(void);
void key_start_demo(void); void key_start_demo(void);
void tutorial_start(void); void tutorial_start(void);
+3
View File
@@ -17,6 +17,9 @@ void layout_arrange_monocle(int workspace);
int layout_get_usable_area(int *x, int *y, int *width, int *height); int layout_get_usable_area(int *x, int *y, int *width, int *height);
int layout_count_tiled_clients(int workspace); int layout_count_tiled_clients(int workspace);
void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
int area_w, int area_h, int gap);
const char *layout_get_name(LayoutType layout); const char *layout_get_name(LayoutType layout);
const char *layout_get_symbol(LayoutType layout); const char *layout_get_symbol(LayoutType layout);
+28 -12
View File
@@ -250,13 +250,37 @@
<td><kbd>Super</kbd> + <kbd>D</kbd></td> <td><kbd>Super</kbd> + <kbd>D</kbd></td>
<td>Decrease master window count</td> <td>Decrease master window count</td>
</tr> </tr>
</tbody>
</table>
</div>
<h2 id="snapping">Window Snapping (Composable)</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
Snapping shortcuts are composable: press Super+Left then Super+Up for top-left quarter. Press the same key twice to expand to full in that axis.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr> <tr>
<td><kbd>Super</kbd> + <kbd>Left</kbd></td> <td><kbd>Super</kbd> + <kbd>Left</kbd></td>
<td>Snap window to left half (50% width)</td> <td>Snap left 50% (press twice for full width)</td>
</tr> </tr>
<tr> <tr>
<td><kbd>Super</kbd> + <kbd>Right</kbd></td> <td><kbd>Super</kbd> + <kbd>Right</kbd></td>
<td>Snap window to right half (50% width)</td> <td>Snap right 50% (press twice for full width)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Up</kbd></td>
<td>Snap top 50% (press twice for full height)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Down</kbd></td>
<td>Snap bottom 50% (press twice for full height)</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -291,7 +315,7 @@
</div> </div>
<h2 id="news">News Ticker</h2> <h2 id="news">News Ticker</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;"> <p style="color: var(--text-muted); margin-bottom: 1rem;">
Navigate the scrolling news ticker displayed in the bottom panel. The scrolling news ticker is displayed in the bottom panel.
</p> </p>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="shortcuts-table"> <table class="shortcuts-table">
@@ -302,14 +326,6 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Down</kbd></td>
<td>Next news article</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Up</kbd></td>
<td>Previous news article</td>
</tr>
<tr> <tr>
<td><kbd>Super</kbd> + <kbd>Return</kbd></td> <td><kbd>Super</kbd> + <kbd>Return</kbd></td>
<td>Open current article in browser</td> <td>Open current article in browser</td>
@@ -380,7 +396,7 @@
<kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master <kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master
</li> </li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);"> <li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+<kbd>Left</kbd>/<kbd>Right</kbd> Snap 50% <kbd>Super</kbd>+Arrows Composable snap
</li> </li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);"> <li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace <kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace
+12
View File
@@ -12,6 +12,7 @@
#include "decorations.h" #include "decorations.h"
#include "notifications.h" #include "notifications.h"
#include "layout.h" #include "layout.h"
#include "panel.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@@ -46,6 +47,9 @@ Client *client_create(Window window)
client->prev = NULL; client->prev = NULL;
client->mru_next = NULL; client->mru_next = NULL;
client->mru_prev = NULL; client->mru_prev = NULL;
client->snap.horizontal = SNAP_H_NONE;
client->snap.vertical = SNAP_V_NONE;
client->snap.timestamp = 0;
XWindowAttributes wa; XWindowAttributes wa;
int orig_width = 640, orig_height = 480; int orig_width = 640, orig_height = 480;
@@ -396,6 +400,7 @@ void client_raise(Client *client)
XRaiseWindow(dwn->display, win); XRaiseWindow(dwn->display, win);
notifications_raise_all(); notifications_raise_all();
panel_raise_all();
} }
void client_lower(Client *client) void client_lower(Client *client)
@@ -443,6 +448,13 @@ void client_move(Client *client, int x, int y)
return; return;
} }
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
if (y < area_y) {
y = area_y;
}
client->x = x; client->x = x;
client->y = y; client->y = y;
client_configure(client); client_configure(client);
+93 -50
View File
@@ -15,6 +15,7 @@
#include "applauncher.h" #include "applauncher.h"
#include "decorations.h" #include "decorations.h"
#include "demo.h" #include "demo.h"
#include "layout.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -530,9 +531,9 @@ void keys_setup_defaults(void)
keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial"); keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial");
keys_bind(MOD_SUPER, XK_Down, key_news_next, "Next news article"); keys_bind(MOD_SUPER, XK_Down, key_snap_down, "Snap window bottom/full");
keys_bind(MOD_SUPER, XK_Up, key_news_prev, "Previous news article"); keys_bind(MOD_SUPER, XK_Up, key_snap_up, "Snap window top/full");
keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article"); keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article");
@@ -802,8 +803,12 @@ void key_show_shortcuts(void)
"Super+L Expand master area\n" "Super+L Expand master area\n"
"Super+I Add to master\n" "Super+I Add to master\n"
"Super+D Remove from master\n" "Super+D Remove from master\n"
"Super+Left Snap window left (50%)\n" "\n"
"Super+Right Snap window right (50%)\n" "=== Window Snapping (composable) ===\n"
"Super+Left Left 50% (2x=full width)\n"
"Super+Right Right 50% (2x=full width)\n"
"Super+Up Top 50% (2x=full height)\n"
"Super+Down Bottom 50% (2x=full height)\n"
"\n" "\n"
"=== AI Features ===\n" "=== AI Features ===\n"
"Super+A Show AI context\n" "Super+A Show AI context\n"
@@ -851,10 +856,6 @@ void key_screenshot(void)
void key_snap_left(void) void key_snap_left(void)
{ {
if (dwn == NULL) {
return;
}
Workspace *ws = workspace_get_current(); Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) { if (ws == NULL || ws->focused == NULL) {
return; return;
@@ -862,42 +863,30 @@ void key_snap_left(void)
Client *c = ws->focused; Client *c = ws->focused;
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 gap = config_get_gap();
if (!client_is_floating(c)) { if (!client_is_floating(c)) {
client_set_floating(c, true); client_set_floating(c, true);
} }
c->x = work_x + gap; if (c->snap.horizontal == SNAP_H_LEFT) {
c->y = work_y + gap; c->snap.horizontal = SNAP_H_FULL;
c->width = (work_width / 2) - (gap * 2); } else {
c->height = work_height - (gap * 2); c->snap.horizontal = SNAP_H_LEFT;
}
if (c->snap.vertical == SNAP_V_NONE) {
c->snap.vertical = SNAP_V_FULL;
}
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
int gap = config_get_gap();
layout_apply_snap_constraint(c, area_x, area_y, area_w, area_h, gap);
client_configure(c); client_configure(c);
decorations_render(c, true); decorations_render(c, true);
} }
void key_snap_right(void) void key_snap_right(void)
{ {
if (dwn == NULL) {
return;
}
Workspace *ws = workspace_get_current(); Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) { if (ws == NULL || ws->focused == NULL) {
return; return;
@@ -905,32 +894,86 @@ void key_snap_right(void)
Client *c = ws->focused; Client *c = ws->focused;
int work_x = 0; if (!client_is_floating(c)) {
int work_y = 0; client_set_floating(c, true);
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();
}
} }
if (c->snap.horizontal == SNAP_H_RIGHT) {
c->snap.horizontal = SNAP_H_FULL;
} else {
c->snap.horizontal = SNAP_H_RIGHT;
}
if (c->snap.vertical == SNAP_V_NONE) {
c->snap.vertical = SNAP_V_FULL;
}
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
int gap = config_get_gap(); int gap = config_get_gap();
layout_apply_snap_constraint(c, area_x, area_y, area_w, area_h, gap);
client_configure(c);
decorations_render(c, true);
}
void key_snap_up(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) { if (!client_is_floating(c)) {
client_set_floating(c, true); client_set_floating(c, true);
} }
c->x = work_x + (work_width / 2) + gap; if (c->snap.vertical == SNAP_V_TOP) {
c->y = work_y + gap; c->snap.vertical = SNAP_V_FULL;
c->width = (work_width / 2) - (gap * 2); } else {
c->height = work_height - (gap * 2); c->snap.vertical = SNAP_V_TOP;
}
if (c->snap.horizontal == SNAP_H_NONE) {
c->snap.horizontal = SNAP_H_FULL;
}
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
int gap = config_get_gap();
layout_apply_snap_constraint(c, area_x, area_y, area_w, area_h, gap);
client_configure(c);
decorations_render(c, true);
}
void key_snap_down(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
}
if (c->snap.vertical == SNAP_V_BOTTOM) {
c->snap.vertical = SNAP_V_FULL;
} else {
c->snap.vertical = SNAP_V_BOTTOM;
}
if (c->snap.horizontal == SNAP_H_NONE) {
c->snap.horizontal = SNAP_H_FULL;
}
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
int gap = config_get_gap();
layout_apply_snap_constraint(c, area_x, area_y, area_w, area_h, gap);
client_configure(c); client_configure(c);
decorations_render(c, true); decorations_render(c, true);
} }
+45
View File
@@ -229,6 +229,51 @@ int layout_count_tiled_clients(int workspace)
return count; return count;
} }
void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
int area_w, int area_h, int gap)
{
if (c == NULL) {
return;
}
int half_w = area_w / 2;
int half_h = area_h / 2;
switch (c->snap.horizontal) {
case SNAP_H_LEFT:
c->x = area_x + gap;
c->width = half_w - gap * 2;
break;
case SNAP_H_RIGHT:
c->x = area_x + half_w + gap;
c->width = half_w - gap * 2;
break;
case SNAP_H_FULL:
c->x = area_x + gap;
c->width = area_w - gap * 2;
break;
case SNAP_H_NONE:
break;
}
switch (c->snap.vertical) {
case SNAP_V_TOP:
c->y = area_y + gap;
c->height = half_h - gap * 2;
break;
case SNAP_V_BOTTOM:
c->y = area_y + half_h + gap;
c->height = half_h - gap * 2;
break;
case SNAP_V_FULL:
c->y = area_y + gap;
c->height = area_h - gap * 2;
break;
case SNAP_V_NONE:
break;
}
}
const char *layout_get_name(LayoutType layout) const char *layout_get_name(LayoutType layout)
{ {
if (layout >= 0 && layout < LAYOUT_COUNT) { if (layout >= 0 && layout < LAYOUT_COUNT) {