{"id":3542,"date":"2025-12-29T00:23:47","date_gmt":"2025-12-29T08:23:47","guid":{"rendered":"https:\/\/rose.dev\/blog\/?p=3542"},"modified":"2026-01-29T11:09:03","modified_gmt":"2026-01-29T19:09:03","slug":"hyprland-tweaks","status":"publish","type":"post","link":"https:\/\/rose.dev\/blog\/2025\/12\/29\/hyprland-tweaks\/","title":{"rendered":"Hyprland Tweaks"},"content":{"rendered":"\n<p>Linux is a real joy compared to Windows. Finally, my computer is my own again. No more nagging ads, settings changing automatically, or random telemetry taking up valuable CPU time. Using Linux is like getting rid of that old 1961 American Rambler and finally joining modern society with an electric car.<\/p>\n\n\n\n<p>Using Hyprland is like ditching your car for a spaceship. Not very practical for commuting, but really fun nonetheless. And, everyone stares at you whenever you land it in the grocery store parking lot, so it&#8217;s all worth it, right?<\/p>\n\n\n\n<p>Since Hyprland is a relatively new Wayland compositor, there are a few things that bothered me when attempting to implement the experience I wanted.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/github.com\/gen3vra\/hypr-disable-touchpad-while-typing\" target=\"_blank\" rel=\"noreferrer noopener\">Disable Touchpad While Typing<\/a><\/h2>\n\n\n\n<p>Hyprland has a built in setting (<code>input:touchpad:disable_while_typing<\/code>) to hopefully prevent palms from messing with the cursor. If you&#8217;re lucky, Hyprland thinks your touchpad is a mouse, so this setting doesn&#8217;t work at all.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-dominant-color=\"454647\" data-has-transparency=\"false\" style=\"--dominant-color: #454647;\" loading=\"lazy\" decoding=\"async\" width=\"740\" height=\"741\" sizes=\"auto, (max-width: 740px) 100vw, 740px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-29-23_13491.avif\" alt=\"Output of &quot;hyprctl devices&quot;\" class=\"wp-image-3550 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-29-23_13491.avif 740w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-29-23_13491-300x300.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-29-23_13491-150x150.avif 150w\" \/><figcaption class=\"wp-element-caption\">hyprctl devices<\/figcaption><\/figure>\n\n\n\n<p>We can re-implement our desired behavior:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Read input from our main keyboard<\/li>\n\n\n\n<li>Disable the touchpad device on input<\/li>\n\n\n\n<li>Re-enable the touchpad device after X ms have elapsed<\/li>\n<\/ul>\n\n\n\n<p>First we&#8217;ll figure out our devices.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">ls \/dev\/input\/by-id\/<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"424e58\" data-has-transparency=\"false\" style=\"--dominant-color: #424e58;\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"84\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-37-29_7057-1024x84.avif\" alt=\"Output of &quot;ls \/dev\/input\/by-id\/&quot;\" class=\"wp-image-3556 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-37-29_7057-1024x84.avif 1024w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-37-29_7057-300x25.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-37-29_7057-768x63.avif 768w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_20-37-29_7057.avif 1187w\" \/><\/figure>\n\n\n\n<p>There may be a few that match what we&#8217;re looking for. <code>usb-Razer_Razer_Blade-if01-event-kbd<\/code> was the one that worked in my case.<\/p>\n\n\n\n<p>And then from the previous screenshot where we ran <code>hyprctl devices<\/code>, we&#8217;ve already discovered the touchpad is <code>elan0406:00-04f3:31a6-touchpad<\/code>.<\/p>\n\n\n\n<p>We could choose any language to write a daemon, but I&#8217;ll pick C. It&#8217;s fast, performant, and has a tiny memory footprint. This will allow the daemon\/program to sit at 0% CPU usage when idle and take up mere megabytes of our RAM.<\/p>\n\n\n\n<pre title=\"\" class=\"wp-block-code\"><code lang=\"c\" class=\"language-c line-numbers\">#include &lt;stdio.h&gt;\n#include &lt;fcntl.h&gt;\n#include &lt;unistd.h&gt;\n#include &lt;linux\/input.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;time.h&gt;\n#include &lt;errno.h&gt;\n\n#define KEYBOARD_DEV \"\/dev\/input\/by-id\/usb-Razer_Razer_Blade-if01-event-kbd\"\n#define DISABLE_CMD \"hyprctl keyword \\\"device[elan0406:00-04f3:31a6-touchpad]:enabled\\\" false &gt;\/dev\/null 2&gt;&amp;1\"\n#define ENABLE_CMD  \"hyprctl keyword \\\"device[elan0406:00-04f3:31a6-touchpad]:enabled\\\" true &gt;\/dev\/null 2&gt;&amp;1\"\n#define TIMEOUT_MS 300\n\nint ignore_key(int keycode) {\n    return (keycode == KEY_LEFTCTRL ||\n            keycode == KEY_RIGHTCTRL ||\n            keycode == KEY_LEFTALT ||\n            keycode == KEY_RIGHTALT ||\n            keycode == KEY_LEFTMETA ||\n            keycode == KEY_RIGHTMETA ||\n            keycode == KEY_FN ||\n            keycode == KEY_FN_ESC ||\n            keycode == KEY_TAB ||\n            keycode == KEY_LEFTSHIFT || \n            keycode == KEY_RIGHTSHIFT ||\n            keycode == KEY_ENTER\n        );\n}\n\nint main() {\n    system(ENABLE_CMD);\n    \n    int fd = open(KEYBOARD_DEV, O_RDONLY | O_NONBLOCK);\n    if (fd &lt; 0) {\n        perror(\"Failed to open keyboard device\");\n        return 1;\n    }\n    \n    struct input_event ev;\n    int touchpad_disabled = 0;\n    struct timespec last_keypress = {0, 0};\n    \n    while (1) {\n        ssize_t r = read(fd, &amp;ev, sizeof(ev));\n        \n        if (r == sizeof(ev)) {\n            if (ev.type == EV_KEY &amp;&amp; ev.value == 1) {\n                \/\/ Ignore modifier keys\n                if (ignore_key(ev.code)) {\n                    goto skip_key;\n                }\n                \n                if (!touchpad_disabled) {\n                    system(DISABLE_CMD);\n                    touchpad_disabled = 1;\n                }\n                clock_gettime(CLOCK_MONOTONIC, &amp;last_keypress);\n            }\n        skip_key:;\n        } else if (r &lt; 0) {\n            if (errno != EAGAIN &amp;&amp; errno != EWOULDBLOCK) {\n                perror(\"Read error\");\n                close(fd);\n                \n                sleep(1);\n                fd = open(KEYBOARD_DEV, O_RDONLY | O_NONBLOCK);\n                if (fd &lt; 0) {\n                    perror(\"Failed to reopen keyboard device\");\n                    return 1;\n                }\n            }\n        } else if (r == 0) {\n            fprintf(stderr, \"Device disconnected, attempting to reconnect...\\n\");\n            close(fd);\n            sleep(1);\n            fd = open(KEYBOARD_DEV, O_RDONLY | O_NONBLOCK);\n            if (fd &lt; 0) {\n                perror(\"Failed to reopen keyboard device\");\n                return 1;\n            }\n        }\n        \n        if (touchpad_disabled) {\n            struct timespec now;\n            clock_gettime(CLOCK_MONOTONIC, &amp;now);\n            long elapsed_ms = (now.tv_sec - last_keypress.tv_sec) * 1000 +\n                              (now.tv_nsec - last_keypress.tv_nsec) \/ 1000000;\n            \n            if (elapsed_ms &gt;= TIMEOUT_MS) {\n                system(ENABLE_CMD);\n                touchpad_disabled = 0;\n            }\n        }\n        \n        usleep(10000);\n    }\n    \n    close(fd);\n    return 0;\n}<\/code><\/pre>\n\n\n\n<p>Let&#8217;s use it: <code>gcc typingtpblock.c -o typingtpblock &amp;&amp; sudo mv .\/typingtpblock \/usr\/local\/bin\/<\/code><\/p>\n\n\n\n<p>Now we just need to run it on start somehow according to your distribution. Adding it to your <code>Startup_Apps.conf<\/code> is a fine choice.<\/p>\n\n\n\n<p><code>exec-once = \/usr\/local\/bin\/typingtpblock<\/code><\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gen3vra\/hypr-disable-touchpad-while-typing\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/gen3vra\/hypr-disable-touchpad-while-typing<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/github.com\/gen3vra\/hypr-fullscreen-window-watcher\" target=\"_blank\" rel=\"noreferrer noopener\">Fullscreen Window Gaps<\/a><\/h2>\n\n\n\n<p>Hyprland is a tiling Wayland compositor. You can adjust the gaps and spacing between windows for your preferred look.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">general {\n  border_size = 1\n  gaps_in = 3\n  gaps_out = 2\n}\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"a1a0a3\" data-has-transparency=\"false\" style=\"--dominant-color: #a1a0a3;\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633-1024x640.avif\" alt=\"Windows with padding in between\" class=\"wp-image-3576 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633-1024x640.avif 1024w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633-300x188.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633-768x480.avif 768w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633-1536x960.avif 1536w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-10-47_19633.avif 1920w\" \/><figcaption class=\"wp-element-caption\">gapsin:3 gapsout:2<\/figcaption><\/figure>\n\n\n\n<p>Here&#8217;s a more exaggerated value of 20 for each.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"9f9ea3\" data-has-transparency=\"false\" style=\"--dominant-color: #9f9ea3;\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219-1024x640.avif\" alt=\"Windows with padding in between\" class=\"wp-image-3577 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219-1024x640.avif 1024w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219-300x188.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219-768x480.avif 768w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219-1536x960.avif 1536w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-13-04_2219.avif 1920w\" \/><\/figure>\n\n\n\n<p>Unfortunately, when you fullscreen an application the gaps you chose will stay the same. This means no matter your gap preference, unless it&#8217;s 0, you can see behind the window.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"e4e5e7\" data-has-transparency=\"false\" style=\"--dominant-color: #e4e5e7;\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594-1024x640.avif\" alt=\"Example of window with visible gaps even though it's fullscreen\" class=\"wp-image-3580 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594-1024x640.avif 1024w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594-300x188.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594-768x480.avif 768w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594-1536x960.avif 1536w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_21-20-26_8594.avif 1920w\" \/><\/figure>\n\n\n\n<p>You also have to disable rounded corners, otherwise there will be tiny gaps in all four quadrants. <\/p>\n\n\n\n<p>Additionally, by default there&#8217;s <strong>no visual variation<\/strong> between a window that exists by itself on a workspace, and a fullscreen window. This can lead to confusion unless you explicitly style fullscreen windows borders differently.<\/p>\n\n\n\n<p>We can add some configurations to our <code>hyprland.conf<\/code> to differentiate it a bit.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">windowrule = bordercolor rgb(ffc1e4), fullscreen:1\nwindowrule = rounding 0, fullscreen:1\nwindowrule = bordersize 2, fullscreen:1 # or 0 for none<\/code><\/pre>\n\n\n\n<p>As stated above, if we&#8217;ve set any gap size at all, there will still be space between the fullscreen window and the screen edge. This is not ideal.<\/p>\n\n\n\n<p>Let&#8217;s fix it. You&#8217;d think we can just do something similar to the above, right?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">windowrule = gapsin 0, fullscreen:1\nwindowrule = gapsout 0, fullscreen:1\n<\/code><\/pre>\n\n\n\n<p>Wrong! These are not valid properties. You must set them in the <code>general<\/code> or <code>workspace<\/code> namespace.<\/p>\n\n\n\n<p>Okay, so we want an application that can do the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keep track of the fullscreen state<\/li>\n\n\n\n<li>Change the configuration when fullscreen<\/li>\n\n\n\n<li>Leave all other windows alone<\/li>\n<\/ul>\n\n\n\n<p>We could bind a fullscreen shortcut to run a script that would both update the gap settings and toggle fullscreen for the active window. This seems fine and recommended. Unfortunately this is a bad solution, because there are way too many edge cases to handle.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Double clicking a titlebar to maximize would not trigger our solution<\/li>\n\n\n\n<li>Maximizing, then closing the application window would not update our tracked boolean, making the next maximize do nothing until pressed twice<\/li>\n\n\n\n<li>Maximizing, then tabbing to another workspace would mean our settings changes remain, making all normal windows have no gap<br><br>We could try to track window closes and any potential edge case, but it becomes messy and complex quickly, without solving the problem cleanly.<\/li>\n<\/ul>\n\n\n\n<p>The solution is yet another lightweight daemon. We can track fullscreen changes directly from the compositor socket itself, ensuring we catch everything. Once we know the fullscreen state and which window specifically, it&#8217;s trivial to hand that information off to a script that handles setting changes for us.<\/p>\n\n\n\n<p>But wait, how does this solve the problem of settings applying to all our other windows which aren&#8217;t fullscreen? The hint was mentioned above.<\/p>\n\n\n\n<p>Hyprland has individual workspace separated settings, so you can do something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">workspace = 1, gapsin:3, gapsout:2\nworkspace = 2, gapsin:10, gapsout:10 # example\nworkspace = 3, gapsin:5, gapsout:12 # example\nworkspace = 4, gapsin:20, gapsout:9 # example<\/code><\/pre>\n\n\n\n<p>This is important because logically, if a window were fullscreened on a certain workspace, no other windows are visible. That means an individual workspace config essentially becomes that window&#8217;s config.<\/p>\n\n\n\n<p>The last piece we need is to find out where we can get window information from. The <code>hyprctl activewindow -j<\/code> command is perfectly suitable for this.<\/p>\n\n\n\n<p>I&#8217;m going to write the daemon in C again for the same reasons mentioned above.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"c\" class=\"language-c line-numbers\">#define _GNU_SOURCE\n#include &lt;errno.h&gt;\n#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;string.h&gt;\n#include &lt;sys\/socket.h&gt;\n#include &lt;sys\/un.h&gt;\n#include &lt;unistd.h&gt;\n#define BUF_SIZE 4096\nstatic void tag_window(const char *addr, int add) {\n  if (!addr || !addr[0])\n    return;\n  char cmd[512];\n  int ret =\n      snprintf(cmd, sizeof(cmd),\n               \"hyprctl dispatch tagwindow %s%sfullscreen_mode address:%s &gt; \/dev\/null 2&gt;&amp;1\",\n               add ? \"+\" : \"-- -\", add ? \"\" : \"\", addr);\n  if (ret &lt; 0 || ret &gt;= sizeof(cmd))\n    return;\n  system(cmd);\n}\nstatic void run_fullscreen_handler(const char *addr, int fs, int workspace) {\n  if (!addr || !addr[0])\n    return;\n  char cmd[512];\n  int ret = snprintf(\n      cmd, sizeof(cmd),\n      \"\/home\/user\/.config\/hypr\/UserScripts\/FullscreenHandler.sh %s %d %d &gt; \/dev\/null 2&gt;&amp;1\",\n      addr, fs, workspace);\n  if (ret &lt; 0 || ret &gt;= sizeof(cmd))\n    return;\n  system(cmd);\n}\nstatic void query_active_window(void) {\n  FILE *fp = popen(\"hyprctl activewindow -j\", \"r\");\n  if (!fp) {\n    fprintf(stderr, \"Failed to query active window\\n\");\n    return;\n  }\n  char buf[BUF_SIZE];\n  char address[128] = {0};\n  int fullscreen = -1;\n  int workspace = -1;\n  int in_workspace = 0;\n  while (fgets(buf, sizeof(buf), fp)) {\n    if (strstr(buf, \"\\\"address\\\"\")) {\n      sscanf(buf, \" \\\"address\\\": \\\"%[^\\\"]\\\"\", address);\n    }\n    if (strstr(buf, \"\\\"fullscreen\\\"\")) {\n      sscanf(buf, \" \\\"fullscreen\\\": %d\", &amp;fullscreen);\n    }\n    \/\/ Handle json workspace object\n    if (strstr(buf, \"\\\"workspace\\\"\")) {\n      in_workspace = 1;\n    }\n    if (in_workspace &amp;&amp; strstr(buf, \"\\\"id\\\"\")) {\n      sscanf(buf, \" \\\"id\\\": %d\", &amp;workspace);\n      in_workspace = 0; \n    }\n  }\n  pclose(fp);\n  if (fullscreen == -1 || !address[0] || workspace == -1)\n    return;\n  \/\/printf(\"fullscreen=%d window=%s workspace=%d\\n\", fullscreen, address, workspace);\n  \/\/fflush(stdout);\n  if (fullscreen == 1) {\n    tag_window(address, 1);\n  } else if (fullscreen == 0) {\n    tag_window(address, 0);\n  }\n  run_fullscreen_handler(address, fullscreen, workspace);\n}\nint main(void) {\n  const char *runtime = getenv(\"XDG_RUNTIME_DIR\");\n  const char *sig = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n  if (!runtime || !sig) {\n    fprintf(stderr, \"Hyprland environment not detected\\n\");\n    return 1;\n  }\n  char sockpath[512];\n  int ret = snprintf(sockpath, sizeof(sockpath), \"%s\/hypr\/%s\/.socket2.sock\",\n                     runtime, sig);\n  if (ret &lt; 0 || ret &gt;= sizeof(sockpath)) {\n    fprintf(stderr, \"Socket path too long\\n\");\n    return 1;\n  }\n  int fd = socket(AF_UNIX, SOCK_STREAM, 0);\n  if (fd &lt; 0) {\n    perror(\"socket\");\n    return 1;\n  }\n  struct sockaddr_un addr = {0};\n  addr.sun_family = AF_UNIX;\n  strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path) - 1);\n  if (connect(fd, (struct sockaddr *)&amp;addr, sizeof(addr)) &lt; 0) {\n    perror(\"connect\");\n    close(fd);\n    return 1;\n  }\n\n  \/\/ Normalize workspaces\n  char cmd[512];\n  int resetRet = snprintf(\n      cmd, sizeof(cmd),\n      \"\/home\/user\/.config\/hypr\/UserScripts\/FullscreenHandler.sh %s %d %d &gt; \/dev\/null 2&gt;&amp;1\",\n      \"discard\", -1, -1);\n  if (resetRet &lt; 0 || resetRet &gt;= sizeof(cmd))\n    return 1;\n  system(cmd);\n  \n  \/\/ Watch for changes\n  char buf[BUF_SIZE];\n  while (1) {\n    ssize_t n = read(fd, buf, sizeof(buf) - 1);\n    if (n &lt; 0) {\n      if (errno == EINTR)\n        continue;\n      perror(\"read\");\n      break;\n    }\n    if (n == 0)\n      break;\n    buf[n] = '\\0';\n    if (strstr(buf, \"fullscreen&gt;&gt;\")) {\n      query_active_window();\n    }\n  }\n  close(fd);\n  return 0;\n}<\/code><\/pre>\n\n\n\n<p><code>gcc fullscreen-window-watcher.c -o fullscreen-window-watcher &amp;&amp; sudo mv .\/fullscreen-window-watcher \/usr\/local\/bin\/<\/code><\/p>\n\n\n\n<p>This program will be updated with each fullscreen change from Hyprland itself. It then passes the actual action off to <code>FullscreenHandler.sh<\/code> with the window address, fullscreen status, and workspace. It also tags the window in case we want to do any future actions, but you may omit this part without any loss of functionality.<\/p>\n\n\n\n<p>The handler script is quite basic, and will update the actual settings.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash line-numbers\">#!\/bin\/bash\nADDR=\"$1\"\nFS=\"$2\"          # 0, 1, or 2\nWS=\"$3\"          # 1-10, or -1 to reset all\n\n# Config file to edit\nHYPR_CONF=\"$HOME\/.config\/hypr\/UserConfigs\/UserDecorations.conf\"  # adjust if needed\n\n# Normal vs Fullscreen configuration\nNO_BORDER_GAP=\"gapsin:0, gapsout:0\"\nNORMAL_BORDER_GAP=\"gapsin:3, gapsout:2\"\n\nif [ \"$WS\" -eq -1 ]; then\n    for i in {1..10}; do\n        LINE_TO_INSERT=\"workspace = ${i}, $NORMAL_BORDER_GAP\"\n        sed -i \"\/^#${i}:DYNAMIC WORKSPACE PLACEHOLDER \\[ns\\]\/{n;s\/.*\/$LINE_TO_INSERT\/;}\" \"$HYPR_CONF\"\n    done\n    #echo \"Reset all workspaces to normal padding\"\n    exit 0\nfi\n\n# 0 = not fs, 1 = fs, 2 = exclusive fs\nif [ \"$FS\" -eq 1 ]; then\n    LINE_TO_INSERT=\"workspace = ${WS}, $NO_BORDER_GAP\"\nelse\n    LINE_TO_INSERT=\"workspace = ${WS}, $NORMAL_BORDER_GAP\"\nfi\n\n# Use sed to replace the line after the workspace comment, in-place\nsed -i \"\/^#${WS}:DYNAMIC WORKSPACE PLACEHOLDER \\[ns\\]\/{n;s\/.*\/$LINE_TO_INSERT\/;}\" \"$HYPR_CONF\"\n#echo \"Updated workspace $WS with $( [ $FS -eq 1 ] &amp;&amp; echo 'no-border padding' || echo 'normal padding')\"<\/code><\/pre>\n\n\n\n<p>There&#8217;s probably a better way than using <code>sed<\/code>. Regardless, if you structure a section in your UserDecorations.conf as the script expects, it will work perfectly.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"json\" class=\"language-json\">## EXAMPLE ##\n# You CAN use a tag but it has a few ms delay and we can handle everything needed with fullscreen:1 match right now\n#windowrule = bordercolor rgb(00ff00), tag:fullscreen_mode\nwindowrule = bordercolor rgb(ffc1e4), fullscreen:1\nwindowrule = rounding 0, fullscreen:1\n# Can do bordersize 10 for a fun indicator around or something\nwindowrule = bordersize 0, fullscreen:1\n\n# This section is replaced by SED from $UserScripts\/FullscreenHandler.sh\n#1:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 1, gapsin:3, gapsout:2\n#2:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 2, gapsin:3, gapsout:2\n#3:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 3, gapsin:3, gapsout:2\n#4:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 4, gapsin:3, gapsout:2\n#5:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 5, gapsin:3, gapsout:2\n#6:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 6, gapsin:3, gapsout:2\n#7:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 7, gapsin:3, gapsout:2\n#8:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 8, gapsin:3, gapsout:2\n#9:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 9, gapsin:3, gapsout:2\n#10:DYNAMIC WORKSPACE PLACEHOLDER [ns]\nworkspace = 10, gapsin:3, gapsout:2<\/code><\/pre>\n\n\n\n<p>Add a line in our <code>Startup_Apps.conf<\/code>: <code>exec-once = \/usr\/local\/bin\/fullscreen-window-watcher<\/code>, and voil\u00e0. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-dominant-color=\"ededee\" data-has-transparency=\"false\" style=\"--dominant-color: #ededee;\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338-1024x640.avif\" alt=\"Example of window touching screen edges\" class=\"wp-image-3645 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338-1024x640.avif 1024w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338-300x188.avif 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338-768x480.avif 768w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338-1536x960.avif 1536w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-52-53_24338.avif 1920w\" \/><\/figure>\n\n\n\n<p>Regardless of bindings or how we achieved fullscreen, our app now has no gap or border. Additionally, tabbing to other workspaces works perfectly, and exiting the app in any way properly resets the settings. Sleek.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gen3vra\/hypr-fullscreen-window-watcher\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/gen3vra\/hypr-fullscreen-window-watcher<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Performance<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-dominant-color=\"2b2e34\" data-has-transparency=\"false\" style=\"--dominant-color: #2b2e34;\" loading=\"lazy\" decoding=\"async\" width=\"511\" height=\"65\" sizes=\"auto, (max-width: 511px) 100vw, 511px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-56-49_2248.avif\" alt=\"\" class=\"wp-image-3648 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-56-49_2248.avif 511w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-56-49_2248-300x38.avif 300w\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-dominant-color=\"2f3137\" data-has-transparency=\"false\" style=\"--dominant-color: #2f3137;\" loading=\"lazy\" decoding=\"async\" width=\"492\" height=\"60\" sizes=\"auto, (max-width: 492px) 100vw, 492px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-57-12_19687.avif\" alt=\"\" class=\"wp-image-3649 not-transparent\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-57-12_19687.avif 492w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2025\/12\/Screenshot_28-Dec_23-57-12_19687-300x37.avif 300w\" \/><\/figure>\n\n\n\n<p>0% CPU and less than 2MB of RAM for each.<\/p>\n\n\n\n<p><\/p>\n<hr>\r\nIt helps me if you share this post\r\n<br\/>\r\n<br\/>\r\nPublished 2025-12-29 00:23:47 ","protected":false},"excerpt":{"rendered":"<p>Linux is a real joy compared to Windows. Finally, my computer is my own again. No more nagging ads, settings changing automatically, or random telemetry taking up valuable CPU time. Using Linux is like getting rid of that old 1961 American Rambler and finally joining modern society with an electric car. Using Hyprland is like &hellip; <a href=\"https:\/\/rose.dev\/blog\/2025\/12\/29\/hyprland-tweaks\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Hyprland Tweaks<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":""},"categories":[922,832,833],"tags":[1282,1285,1288,1291,1280,1249,1287,1292,1289,1286,1284],"class_list":["post-3542","post","type-post","status-publish","format-standard","hentry","category-release","category-software","category-technology","tag-c-3","tag-compositor","tag-daemon","tag-fullscreen","tag-hyprland","tag-linux","tag-scripts","tag-spacing","tag-touchpad","tag-tweaks","tag-wayland"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/3542","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/comments?post=3542"}],"version-history":[{"count":123,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/3542\/revisions"}],"predecessor-version":[{"id":3730,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/3542\/revisions\/3730"}],"wp:attachment":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/media?parent=3542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/categories?post=3542"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/tags?post=3542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}