From 5f7c6f8d08f336c5d18e67c145319a70abe4f61c Mon Sep 17 00:00:00 2001 From: Johannes Herman Date: Tue, 29 Jul 2025 19:35:59 +0200 Subject: sticky and unclutter --- config.def.h | 5 +- dwl.c | 114 +++++++++++++++++++++++++--- patches/sticky.patch | 85 +++++++++++++++++++++ patches/unclutter.patch | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 patches/sticky.patch create mode 100644 patches/unclutter.patch diff --git a/config.def.h b/config.def.h index 2f36fa9..36fa943 100644 --- a/config.def.h +++ b/config.def.h @@ -10,7 +10,7 @@ static const int smartborders = 1; /* 1 means no outer gap when th static const int smartgaps = 1; /* 1 means no outer gap when there is only one window */ static int gaps = 1; /* 1 means gaps between windows are added */ static const unsigned int gappx = 10; /* gap pixel between windows */ -static const unsigned int borderpx = 2; /* border pixel of windows */ +static const unsigned int borderpx = 3; /* border pixel of windows */ static const float rootcolor[] = COLOR(0x282828ff); static const float bordercolor[] = COLOR(0x7c6f64ff); static const float focuscolor[] = COLOR(0x458588ff); @@ -114,6 +114,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right */ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; +static const int cursor_timeout = 2; + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ #define MODKEY WLR_MODIFIER_LOGO @@ -148,6 +150,7 @@ static const Key keys[] = { { MODKEY, XKB_KEY_Return, zoom, {0} }, { MODKEY, XKB_KEY_Tab, view, {0} }, { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY, XKB_KEY_p, togglesticky, {0} }, { MODKEY, XKB_KEY_c, killclient, {0} }, { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, // { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, diff --git a/dwl.c b/dwl.c index 0769dd0..396a566 100644 --- a/dwl.c +++ b/dwl.c @@ -73,7 +73,11 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) -#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->swallowedby) + +#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->swallowedby) || C->issticky)) + +// #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && (((C)->tags & (M)->tagset[(M)->seltags]) || C->issticky)) + #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) @@ -141,7 +145,7 @@ struct Client { #endif unsigned int bw; uint32_t tags; - int isfloating, isurgent, isfullscreen; + int isfloating, isurgent, isfullscreen, issticky; int isterm, noswallow; uint32_t resize; /* configure serial of a pending resize */ pid_t pid; @@ -300,6 +304,8 @@ static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); +static void handlecursoractivity(void); +static int hidecursor(void *data); static void handlesig(int signo); static void incnmaster(const Arg *arg); static void inputdevice(struct wl_listener *listener, void *data); @@ -336,6 +342,7 @@ static void setcursor(struct wl_listener *listener, void *data); static void setcursorshape(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); +static void setsticky(Client *c, int sticky); static void setgamma(struct wl_listener *listener, void *data); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); @@ -352,6 +359,7 @@ static void tagmon(const Arg *arg); static Client *termforwin(Client *c); static void tile(Monitor *m); static void togglefloating(const Arg *arg); +static void togglesticky(const Arg *arg); static void togglefullscreen(const Arg *arg); static void toggleswallow(const Arg *arg); static void toggleautoswallow(const Arg *arg); @@ -411,6 +419,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint; static struct wlr_cursor *cursor; static struct wlr_xcursor_manager *cursor_mgr; +static struct wl_event_source *hide_source; +static bool cursor_hidden = false; +static struct { + enum wp_cursor_shape_device_v1_shape shape; + struct wlr_surface *surface; + int hotspot_x; + int hotspot_y; +} last_cursor; static struct wlr_scene_rect *root_bg; static struct wlr_session_lock_manager_v1 *session_lock_mgr; @@ -631,6 +647,7 @@ buttonpress(struct wl_listener *listener, void *data) const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); switch (event->state) { case WL_POINTER_BUTTON_STATE_PRESSED: @@ -1494,6 +1511,18 @@ focustop(Monitor *m) return NULL; } + +Client * +focustopnotsticky(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m) && (!c->issticky || !c->isfloating || c->isfullscreen)) + return c; + } + return NULL; +} + void fullscreennotify(struct wl_listener *listener, void *data) { @@ -1546,6 +1575,32 @@ handlesig(int signo) } } +void +handlecursoractivity(void) +{ + wl_event_source_timer_update(hide_source, cursor_timeout * 1000); + + if (!cursor_hidden) + return; + + cursor_hidden = false; + + if (last_cursor.shape) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(last_cursor.shape)); + else + wlr_cursor_set_surface(cursor, last_cursor.surface, + last_cursor.hotspot_x, last_cursor.hotspot_y); +} + +int +hidecursor(void *data) +{ + wlr_cursor_unset_image(cursor); + cursor_hidden = true; + return 1; +} + void incnmaster(const Arg *arg) { @@ -1885,6 +1940,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d wlr_cursor_move(cursor, device, dx, dy); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + handlecursoractivity(); /* Update selmon (even while dragging a window) */ if (sloppyfocus) @@ -1909,7 +1965,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ - if (!surface && !seat->drag) + if (!surface && !seat->drag && !cursor_hidden) wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); pointerfocus(c, surface, sx, sy, time); @@ -2307,6 +2363,7 @@ run(char *startup_cmd) * monitor when displayed here */ wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + handlecursoractivity(); /* Run the Wayland event loop. This does not return until you exit the * compositor. Starting the backend rigged up all of the necessary event @@ -2330,9 +2387,16 @@ setcursor(struct wl_listener *listener, void *data) * use the provided surface as the cursor image. It will set the * hardware cursor on the output that it's currently on and continue to * do so as the cursor moves between outputs. */ - if (event->seat_client == seat->pointer_state.focused_client) - wlr_cursor_set_surface(cursor, event->surface, - event->hotspot_x, event->hotspot_y); + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = 0; + last_cursor.surface = event->surface; + last_cursor.hotspot_x = event->hotspot_x; + last_cursor.hotspot_y = event->hotspot_y; + + if (!cursor_hidden) + wlr_cursor_set_surface(cursor, event->surface, + event->hotspot_x, event->hotspot_y); + } } void @@ -2344,9 +2408,14 @@ setcursorshape(struct wl_listener *listener, void *data) /* This can be sent by any client, so we check to make sure this one is * actually has pointer focus first. If so, we can tell the cursor to * use the provided cursor shape. */ - if (event->seat_client == seat->pointer_state.focused_client) - wlr_cursor_set_xcursor(cursor, cursor_mgr, - wlr_cursor_shape_v1_name(event->shape)); + if (event->seat_client == seat->pointer_state.focused_client) { + last_cursor.shape = event->shape; + last_cursor.surface = NULL; + + if (!cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, + wlr_cursor_shape_v1_name(event->shape)); + } } void @@ -2400,6 +2469,17 @@ setgamma(struct wl_listener *listener, void *data) wlr_output_schedule_frame(m->wlr_output); } +void +setsticky(Client *c, int sticky) +{ + if(sticky && !c->issticky) { + c->issticky = 1; + } else if(!sticky && c->issticky) { + c->issticky = 0; + arrange(c->mon); + } +} + void setlayout(const Arg *arg) { @@ -2623,6 +2703,9 @@ setup(void) relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), + hidecursor, cursor); + /* * Creates a cursor, which is a wlroots utility for tracking the cursor * image shown on screen. @@ -3029,6 +3112,16 @@ toggleautoswallow(const Arg *arg) enableautoswallow = !enableautoswallow; } +void +togglesticky(const Arg *arg) +{ + Client *c = focustop(selmon); + if(!c) + return; + + setsticky(c, !c->issticky); +} + void toggletag(const Arg *arg) { @@ -3249,7 +3342,7 @@ view(const Arg *arg) selmon->seltags ^= 1; /* toggle sel tagset */ if (arg->ui & TAGMASK) selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; - focusclient(focustop(selmon), 1); + focusclient(focustopnotsticky(selmon), 1); arrange(selmon); printstatus(); } @@ -3277,6 +3370,7 @@ virtualpointer(struct wl_listener *listener, void *data) wlr_cursor_attach_input_device(cursor, device); if (event->suggested_output) wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); + handlecursoractivity(); } Monitor * diff --git a/patches/sticky.patch b/patches/sticky.patch new file mode 100644 index 0000000..ece8308 --- /dev/null +++ b/patches/sticky.patch @@ -0,0 +1,85 @@ +From f113cdc0b4cecceaaf28679489852ae61a1aa3f5 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 19 Jul 2024 16:29:43 +0200 +Subject: [PATCH] sticky + +--- + dwl.c | 27 +++++++++++++++++++++++++-- + 1 file changed, 25 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5bf995e..820f4af 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -73,7 +73,7 @@ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) + #define MIN(A, B) ((A) < (B) ? (A) : (B)) + #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) +-#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) ++#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && (((C)->tags & (M)->tagset[(M)->seltags]) || C->issticky)) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +@@ -139,7 +139,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, issticky; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -326,6 +326,7 @@ static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); + static void setfloating(Client *c, int floating); + static void setfullscreen(Client *c, int fullscreen); ++static void setsticky(Client *c, int sticky); + static void setgamma(struct wl_listener *listener, void *data); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); +@@ -339,6 +340,7 @@ static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); ++static void togglesticky(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); +@@ -2351,6 +2353,17 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setsticky(Client *c, int sticky) ++{ ++ if(sticky && !c->issticky) { ++ c->issticky = 1; ++ } else if(!sticky && c->issticky) { ++ c->issticky = 0; ++ arrange(c->mon); ++ } ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2738,6 +2751,16 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglesticky(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ if(!c) ++ return; ++ ++ setsticky(c, !c->issticky); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/patches/unclutter.patch b/patches/unclutter.patch new file mode 100644 index 0000000..4c9aede --- /dev/null +++ b/patches/unclutter.patch @@ -0,0 +1,192 @@ +From 68914f40359ebccc7b684a1f74d82419b1796cdf Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Thu, 25 Jul 2024 17:59:05 +0200 +Subject: [PATCH] =?UTF-8?q?hide=20the=20mouse=20cursor=20if=20it=20isn?= + =?UTF-8?q?=E2=80=99t=20being=20used?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + config.def.h | 2 ++ + dwl.c | 74 +++++++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 67 insertions(+), 9 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..790c73d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -106,6 +106,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right + */ + static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + ++static const int cursor_timeout = 5; ++ + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +diff --git a/dwl.c b/dwl.c +index 775dadf..621779e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -288,6 +288,8 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); ++static void handlecursoractivity(void); ++static int hidecursor(void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -389,6 +391,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; ++static struct wl_event_source *hide_source; ++static bool cursor_hidden = false; ++static struct { ++ enum wp_cursor_shape_device_v1_shape shape; ++ struct wlr_surface *surface; ++ int hotspot_x; ++ int hotspot_y; ++} last_cursor; + + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; +@@ -609,8 +619,9 @@ axisnotify(struct wl_listener *listener, void *data) + * for example when you move the scroll wheel. */ + struct wlr_pointer_axis_event *event = data; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +- /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented +- * by checking the event's orientation and the delta of the event */ ++ handlecursoractivity(); ++ /* TODO: allow usage of scroll whell for mousebindings, it can be implemented ++ * checking the event's orientation and the delta of the event */ + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(seat, + event->time_msec, event->orientation, event->delta, +@@ -627,6 +638,7 @@ buttonpress(struct wl_listener *listener, void *data) + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: +@@ -1563,6 +1575,32 @@ handlesig(int signo) + quit(NULL); + } + ++void ++handlecursoractivity(void) ++{ ++ wl_event_source_timer_update(hide_source, cursor_timeout * 1000); ++ ++ if (!cursor_hidden) ++ return; ++ ++ cursor_hidden = false; ++ ++ if (last_cursor.shape) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(last_cursor.shape)); ++ else ++ wlr_cursor_set_surface(cursor, last_cursor.surface, ++ last_cursor.hotspot_x, last_cursor.hotspot_y); ++} ++ ++int ++hidecursor(void *data) ++{ ++ wlr_cursor_unset_image(cursor); ++ cursor_hidden = true; ++ return 1; ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1903,6 +1941,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) +@@ -1927,7 +1966,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +- if (!surface && !seat->drag) ++ if (!surface && !seat->drag && !cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +@@ -2284,6 +2323,7 @@ run(char *startup_cmd) + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ handlecursoractivity(); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event +@@ -2307,9 +2347,16 @@ setcursor(struct wl_listener *listener, void *data) + * use the provided surface as the cursor image. It will set the + * hardware cursor on the output that it's currently on and continue to + * do so as the cursor moves between outputs. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_surface(cursor, event->surface, +- event->hotspot_x, event->hotspot_y); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = 0; ++ last_cursor.surface = event->surface; ++ last_cursor.hotspot_x = event->hotspot_x; ++ last_cursor.hotspot_y = event->hotspot_y; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_surface(cursor, event->surface, ++ event->hotspot_x, event->hotspot_y); ++ } + } + + void +@@ -2321,9 +2368,14 @@ setcursorshape(struct wl_listener *listener, void *data) + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_xcursor(cursor, cursor_mgr, +- wlr_cursor_shape_v1_name(event->shape)); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = event->shape; ++ last_cursor.surface = NULL; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(event->shape)); ++ } + } + + void +@@ -2614,6 +2666,9 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + ++ hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), ++ hidecursor, cursor); ++ + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, +@@ -2998,6 +3053,7 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); ++ handlecursoractivity(); + } + + Monitor * +-- +2.49.0 + -- cgit v1.2.3