diff --git a/.zprofile b/.zprofile index 09003b6..bf73429 100644 --- a/.zprofile +++ b/.zprofile @@ -1,6 +1,7 @@ grep --color=auto < /dev/null &>/dev/null && alias grep='grep --color=auto' export PATH="$HOME/.local/bin:$PATH" export PATH="/usr/pkg/sbin:/usr/pkg/bin:$PATH" +export PATH="$HOME/.cargo/bin:$PATH" export PATH="$PATH:/home/coast/.spicetify" export MANPATH="/usr/pkg/man:$MANPATH" export XDG_DATA_DIRS="/var/lib/flatpak/exports/share:$HOME/.local/share/flatpak/exports/share:/usr/local/share:/usr/share" @@ -8,69 +9,70 @@ export EDITOR="nvim" export EIX_LIMIT=0 #aliasrc -alias \ - nf="neofetch" \ - vim="nvim" \ - htop="btop" \ - mutt="neomutt" \ - mt="neomutt" \ - emoji="cat ~/.local/share/emoji | grep \$1" \ - cst="vi ~/.config/st/config.h" \ - ls="ls --color=auto" \ - ll="ls -hl" \ - l="ls -lh" \ - la="ls -ahl" \ - smi="nvidia-smi" \ - srczsh="source ~/.zshrc" \ - battery="sb-battery" \ - bat="sb-battery" \ - quit="exit" \ - :q="exit" \ - push="git push" \ - weather="curl wttr.in/masjedsoleyman" \ - las="ls" \ - la="ls -a" \ - c="clear" \ - cear="clear" \ - cd..="cd .." \ - ..='echo "cd .."; cd ../' \ - claer="clear" \ - clare="clear" \ - cleae="clear" \ - clera="clear" \ - hotp="htop" \ - copykey='cat ~/.local/share/vault1.key | xclip -sel clipboard' \ - mic="micro" \ - nx="nsxiv" \ - v="vi" \ - stx="startx" \ - e="doas emerge --ask --verbose $1" \ - es="emerge -s $1" \ - fe="flatpak search $1" \ - fei="flatpak install $1" \ +alias nf="neofetch" +alias vim="nvim" +alias emacs="nvim" +alias mutt="neomutt" +alias mt="neomutt" +alias emoji="cat ~/.local/share/emoji | grep" +alias cst="vi ~/.config/st/config.h" +alias ls="ls --color=auto" +alias ll="ls -hl" +alias l="ls -lh" +alias la="ls -ahl" +alias smi="nvidia-smi" +alias srczsh="source ~/.zshrc" +alias battery="sb-battery" +alias bat="sb-battery" +alias quit="exit" +alias push="git push" +alias weather="curl wttr.in/masjedsoleyman" +alias las="ls" +alias la="ls -a" +alias c="clear" +alias cear="clear" +alias cd..="cd .." +alias ..='echo "cd .."; cd ../' +alias claer="clear" +alias clare="clear" +alias cleae="clear" +alias clera="clear" +alias hotp="htop" +alias copykey='cat ~/.local/share/vault1.key | xclip -sel clipboard' +alias mic="micro" +alias nx="nsxiv" +alias v="nvim" +alias vi="nvim" +alias nvim="nvim" +alias stx="startx" +alias e="doas emerge --ask --verbose" +alias es="emerge -s" +alias fe="flatpak search" +alias fei="flatpak install" +alias startx="startw" video() { mpv "$1" --ytdl-format="bestvideo[height>=720]+bestaudio/best[height>=720]" } - videolow() { mpv "$1" --ytdl-format="bestvideo[height<=720]+bestaudio/best[height<=720]" } #else - bindkey -e - eval $(ssh-agent -s) >/dev/null 2>&1 - batstat=$(cat /sys/class/power_supply/BAT1/status 2>/dev/null) charge=$(cat /sys/class/power_supply/BAT1/capacity 2>/dev/null) if [[ "$batstat" == "Discharging" && "$charge" -lt 50 ]]; then echo "Battery: $(sb-battery)" fi - precmd() { precmd() { echo } } + +clear() { + echo "clear command is disabled." +} + diff --git a/.zshrc b/.zshrc index 8d13741..27e4525 100644 --- a/.zshrc +++ b/.zshrc @@ -16,7 +16,7 @@ ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=5" ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=12" LIBCLANG_PATH=/usr/lib/llvm/20/lib64 source ~/.zprofile -#PS1="%B%F{#f0f0f0}[%F{#ff5f5f}%n%F{#f0f0f0}@%M %F{#5fafff}%~%F{#f0f0f0}]%f%F{#fe8019}%B%%%b%f " +PS1="%B%F{#f0f0f0}[%F{#ff5f5f}%n%F{#f0f0f0}@%M %F{#5fafff}%~%F{#f0f0f0}]%f%F{#fe8019}%B%%%b%f " PATH=$PATH:/home/coast/.spicetify eval "$(starship init zsh)" diff --git a/colorlist.palette b/colorlist.palette new file mode 100644 index 0000000..48f67a0 --- /dev/null +++ b/colorlist.palette @@ -0,0 +1,10 @@ +#66cccc // Soft Cyan +#00cccc // Medium Cyan +#00ffff // Bright Cyan +#33aaaa // Teal Cyan +#009999 // Deep Cyan +#11a8cd // Aqua Glow +#00e5e5 // Electric Cyan + + +#a8dcee // sky cyan diff --git a/config/foot/foot.ini b/config/foot/foot.ini new file mode 100644 index 0000000..a79cca9 --- /dev/null +++ b/config/foot/foot.ini @@ -0,0 +1,33 @@ +font=Departure Mono:size=17 + +[main] +pad = 5x5 + +[colors] +alpha=0.9 +foreground=ffffff +background=000000 + +regular0=2e3436 +regular1=cc0000 +regular2=4e9a06 +regular3=c4a000 +regular4=3465a4 +regular5=75507b +regular6=06989a +regular7=d3d7cf +bright0=555753 +bright1=ef2929 +bright2=8ae234 +bright3=fce94f +bright4=729fcf +bright5=ad7fa8 +bright6=34e2e2 +bright7=eeeeec + +[cursor] +style = underline +unfocused-style = unchanged +blink = no +blink-rate = 500 +underline-thickness = 2px diff --git a/config/sway/config b/config/sway/config index dce45f3..643db9a 100644 --- a/config/sway/config +++ b/config/sway/config @@ -1,6 +1,6 @@ -exec stacki3 -exec waybar - +exec stacki3 & +exec waybar & +exec foot --server & #touchpad input "1267:12448:ELAN0709:00_04F3:30A0_Touchpad" { dwt enabled @@ -25,13 +25,10 @@ set $down j set $up k set $right l # Your preferred terminal emulator -set $term kitty +set $term footclient # Your preferred application launcher -set $menu dmenu_run +set $menu wmenu-run -### Output configuration -# -# Default wallpaper (more resolutions are available in /usr/share/backgrounds/sway/) output * bg /home/coast/files/pics/flowers.png stretch # # Example configuration: @@ -40,37 +37,16 @@ output * bg /home/coast/files/pics/flowers.png stretch # # You can get the names of your outputs by running: swaymsg -t get_outputs -### Idle configuration -# -# Example configuration: -# -# exec swayidle -w \ -# timeout 300 'swaylock -f -c 000000' \ -# timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \ -# before-sleep 'swaylock -f -c 000000' -# -# This will lock your screen after 300 seconds of inactivity, then turn off -# your displays after another 300 seconds, and turn your screens back on when -# resumed. It will also lock your screen before your computer goes to sleep. +exec swayidle -w \ + timeout 300 'swaylock -f -c 000000' \ + timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \ + before-sleep 'swaylock -f -c 000000' -### Input configuration -# -# Example configuration: -# -# input type:touchpad { -# dwt enabled -# tap enabled -# natural_scroll enabled -# middle_emulation enabled -# } -# -# input type:keyboard { -# xkb_layout "eu" -# } -# -# You can also configure each device individually. -# Read `man 5 sway-input` for more information about this section. -default_border pixel 4 # 1px border, no titlebar +input * { + xkb_options caps:escape +} + +default_border pixel 4 default_floating_border pixel 4 for_window [class=".*"] border pixel 4 @@ -81,17 +57,10 @@ client.focused_inactive #444444 #000000 #ffffff #444444 #444444 client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 -### Key bindings -# -# Basics: -# - # Start a terminal bindsym $mod+Return exec $term - # Kill focused window bindsym $mod+s kill - # Start your launcher bindsym $mod+r exec $menu # Drag floating windows by holding down $mod and left mouse button. @@ -101,10 +70,8 @@ client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 # mouse button for dragging. floating_modifier $mod normal - # Reload the configuration file bindsym $mod+Shift+c reload - # Exit sway (logs you out of your Wayland session) bindsym $mod+Shift+q exec 'swaymsg exit' # # Moving around: @@ -115,10 +82,10 @@ client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 bindsym $mod+$up focus up bindsym $mod+$right focus right # Or use $mod+[up|down|left|right] - bindsym $mod+Left focus left - bindsym $mod+Down focus down - bindsym $mod+Up focus up - bindsym $mod+Right focus right + bindsym $mod+j focus up + bindsym $mod+k focus down + bindsym $mod+Shift+k focus left + bindsym $mod+Shift+j focus right # Move the focused window with the same, but add Shift bindsym $mod+Shift+$left move left @@ -130,9 +97,7 @@ client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 bindsym $mod+Shift+Down move down bindsym $mod+Shift+Up move up bindsym $mod+Shift+Right move right -# -# Workspaces: -# + # Switch to workspace bindsym $mod+1 workspace number 1 bindsym $mod+2 workspace number 2 @@ -144,6 +109,7 @@ client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 bindsym $mod+8 workspace number 8 bindsym $mod+9 workspace number 9 bindsym $mod+0 workspace number 10 + # Move focused container to workspace bindsym $mod+Shift+1 move container to workspace number 1 bindsym $mod+Shift+2 move container to workspace number 2 @@ -155,6 +121,7 @@ client.urgent #ff0000 #000000 #ffffff #ff0000 #ff0000 bindsym $mod+Shift+8 move container to workspace number 8 bindsym $mod+Shift+9 move container to workspace number 9 bindsym $mod+Shift+0 move container to workspace number 10 + # Note: workspaces can have any name you want, not just numbers. # We just use 1-10 as the default. # @@ -217,7 +184,13 @@ mode "resize" { bindsym Return mode "default" bindsym Escape mode "default" } -bindsym $mod+Shift+p mode "resize" +bindsym $mod+Shift+d mode "resize" + +#launch apps +bindsym $mod+Shift+p exec spotify +bindsym $mod+Shift+b exec firefox + + # # Utilities: # @@ -238,3 +211,5 @@ bindsym $mod+Shift+p mode "resize" # Read `man 5 sway-bar` for more information about this section. include /etc/sway/config.d/* +for_window [app_id="foot"] opacity 0.9 + diff --git a/config/waybar/style.css b/config/waybar/style.css index ffbdb80..87df473 100644 --- a/config/waybar/style.css +++ b/config/waybar/style.css @@ -4,6 +4,7 @@ padding: 0; margin: 0; border: none; + box-shadow: none; background: #000000; color: #ffffff; } @@ -13,44 +14,30 @@ window#waybar { color: #ffffff; } -/* Workspaces */ #workspaces button { background: #000000; color: #888888; padding: 0 4px; margin: 0 1px; - transition-duration: 0s; } #workspaces button.focused { color: #ffffff; font-weight: bold; - transition-duration: 0s; } -/* Layout icon */ #sway-layout { margin-left: 8px; margin-right: 8px; min-width: 20px; } -/* WINDOW TITLE with distinct background */ #sway-window { - background-color: #222222; - color: #ffffff; - padding: 2px 8px; - margin: 0 4px; -} - -/* Filler pushes the window title toward the right modules */ -#custom-filler { - background: transparent; - color: transparent; - min-width: 800px; + font-style: italic; + color: #cccccc; } -/* DWM-style right status bar */ #custom-dwmstatus { margin-right: 10px; } + diff --git a/dmenu/config.h b/dmenu/config.h index 91d480e..de78b06 100644 --- a/dmenu/config.h +++ b/dmenu/config.h @@ -6,7 +6,7 @@ static int topbar = 1; /* -b option; if 0, dmenu appears a static const int user_bh = 0; /* add an defined amount of pixels to the bar height */ static const char *fonts[] = { - "Departure Mono:size=13" + "monospace:size=13" }; static const char *prompt = NULL; /* -p option; prompt to the left of input field */ static const char *colors[SchemeLast][2] = { diff --git a/dmenu/dmenu b/dmenu/dmenu deleted file mode 100755 index 705cb23..0000000 Binary files a/dmenu/dmenu and /dev/null differ diff --git a/dmenu/dmenu.o b/dmenu/dmenu.o deleted file mode 100644 index 94cc565..0000000 Binary files a/dmenu/dmenu.o and /dev/null differ diff --git a/dmenu/drw.o b/dmenu/drw.o deleted file mode 100644 index 69dbfa9..0000000 Binary files a/dmenu/drw.o and /dev/null differ diff --git a/dmenu/stest b/dmenu/stest deleted file mode 100755 index 344348c..0000000 Binary files a/dmenu/stest and /dev/null differ diff --git a/dmenu/stest.o b/dmenu/stest.o deleted file mode 100644 index 3d5045e..0000000 Binary files a/dmenu/stest.o and /dev/null differ diff --git a/dmenu/util.o b/dmenu/util.o deleted file mode 100644 index 5dd48bf..0000000 Binary files a/dmenu/util.o and /dev/null differ diff --git a/dwl-bak/.gitea/issue_template/bug_report.yml b/dwl-bak/.gitea/issue_template/bug_report.yml new file mode 100644 index 0000000..77ce108 --- /dev/null +++ b/dwl-bak/.gitea/issue_template/bug_report.yml @@ -0,0 +1,62 @@ +name: Bug Report +about: Something in dwl isn't working correctly +title: +labels: + - 'Kind/Bug' +body: + - type: markdown + attributes: + value: | + - Only report bugs that can be reproduced on the main (or wlroots-next) branch without patches. + - Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use dwl. + - Report patch issues to their respective authors. + + - type: input + id: dwl_version + attributes: + label: 'dwl version:' + placeholder: '`dwl -v`' + validations: + required: true + + - type: input + id: wlroots_version + attributes: + label: 'wlroots version:' + validations: + required: true + + - type: input + id: distro + attributes: + label: What distro (and version) are you using? + validations: + required: false + + - type: textarea + attributes: + label: Description + value: | + The steps you took to reproduce the problem. + validations: + required: false + + - type: textarea + id: debug_log + attributes: + label: Debug Log + value: | + Run `dwl -d 2> ~/dwl.log` from a TTY and attach the **full** (do not truncate it) file here, or upload it to a pastebin. + Please try to keep the reproduction as brief as possible and exit dwl. + validations: + required: false + + - type: textarea + id: backtrace + attributes: + label: Stack Trace + value: | + - Only required if dwl crashes. + - If the lines mentioning dwl or wlroots have `??`. Please compile both dwl and wlroots from source (enabling debug symbols) and try to reproduce. + validations: + required: false diff --git a/dwl-bak/.gitea/issue_template/enhancement-idea.yml b/dwl-bak/.gitea/issue_template/enhancement-idea.yml new file mode 100644 index 0000000..be1bbf2 --- /dev/null +++ b/dwl-bak/.gitea/issue_template/enhancement-idea.yml @@ -0,0 +1,9 @@ +name: Enhancement idea +about: Suggest a feature or improvement +title: +labels: + - 'Kind/Feature' +body: + - type: textarea + attributes: + label: Description diff --git a/dwl-bak/.gitignore b/dwl-bak/.gitignore new file mode 100644 index 0000000..0dde90e --- /dev/null +++ b/dwl-bak/.gitignore @@ -0,0 +1,6 @@ +dwl +*.o +*-protocol.c +*-protocol.h +.ccls-cache +config.h diff --git a/dwl-bak/.mailmap b/dwl-bak/.mailmap new file mode 100644 index 0000000..911248c --- /dev/null +++ b/dwl-bak/.mailmap @@ -0,0 +1 @@ +Lennart Jablonka diff --git a/dwl-bak/CHANGELOG.md b/dwl-bak/CHANGELOG.md new file mode 100644 index 0000000..3a299c4 --- /dev/null +++ b/dwl-bak/CHANGELOG.md @@ -0,0 +1,200 @@ +# Changelog + +* [0.7](#0.7) +* [0.6](#0.6) +* [0.5](#0.5) + + +## 0.7 + +See also [0.6](#0.6) release notes. 0.7 builds against wlroots 0.18.x. + +### Added + +* Add support for the alpha-modifier-v1 protocol ([wlroots!4616][wlroots!4616]). +* dwl now will survive GPU resets ([#601][601]). + +[wlroots!4616]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4616 +[601]: https://codeberg.org/dwl/dwl/issues/601 + + +### Fixed + +* Crash when re-mapping unmapped clients. + + +### Contributors + +Guido Cella +Lennart Jablonka + + +## 0.6 + +### Added + +* Add `rootcolor` to change the default background color ([#544][544]). +* Implement the wlr-virtual-pointer-unstable-v1 protocol ([#574][574]). +* Implement the pointer-constraints and relative-pointer protocols ([#317][317]) +* Implement the wlr-output-power-management protocol ([#599][599]) + +[544]: https://codeberg.org/dwl/dwl/pulls/544 +[574]: https://codeberg.org/dwl/dwl/pulls/574 +[317]: https://codeberg.org/dwl/dwl/issues/317 +[599]: https://codeberg.org/dwl/dwl/issues/559 + + +### Changed + +* Keyboards are now managed through keyboard groups ([#549][549]). +* Only the first matched keybinding is executed. +* Allow toggling the layout before selecting a different one ([#570][570]). +* Fullscreen clients are now rendered above wlr_layer_surfaces in the top layer + ([#609][609]). +* The default menu was changed from `bemenu-run` to `wmenu-run` ([#553][553]). +* The option `sloppyfocus` now replicates the dwm behavior ([#599][599]). +* Allow configure position of monitors with negative values. (-1, -1) is + used to auto-configure them ([#635][635]). +* dwl now kills the entire process group of `startup_cmd` +* The O_NONBLOCK flag is set for stdout. + +[549]: https://codeberg.org/dwl/dwl/pulls/549 +[570]: https://codeberg.org/dwl/dwl/pulls/570 +[609]: https://codeberg.org/dwl/dwl/pulls/609 +[553]: https://codeberg.org/dwl/dwl/issues/553 +[599]: https://codeberg.org/dwl/dwl/pulls/599 +[635]: https://codeberg.org/dwl/dwl/pulls/635 + + +### Removed + +* The SLOC limit is now removed ([#497][497]). + +[497]: https://codeberg.org/dwl/dwl/pulls/497 + + +### Fixed + +* Clients not having the correct border color when mapping. +* Compliance with the xdg-decoration-unstable-v1 ([#546][546]). +* dwl no longer sends negative values in xdg_toplevel.configure events. +* Crashes with disabled monitors ([#472][472]). + +[546]: https://codeberg.org/dwl/dwl/pulls/546 +[472]: https://codeberg.org/dwl/dwl/issues/472 + + +### Contributors + +Ben Jargowsky +Benjamin Chausse +David Donahue +Devin J. Pohly +Dima Krasner +Emil Miler +Forrest Bushstone +Guido Cella +Peter Hofmann +Rutherther +Squibid +choc +fictitiousexistence +korei999 +sewn +thanatos + + +## 0.5 + +### Added + +* Allow configure x and y position of outputs ([#301][301]) +* Implement repeatable keybindings ([#368][368]) +* Print app id in printstatus() output ([#381][381]) +* Display client count in monocle symbol ([#387][387]) +* Export XCURSOR_SIZE to fix apps using an older version of Qt ([#425][425]) +* Support for wp-fractional-scale-v1 (through wlr_scene: [wlroots!3511][wlroots!3511]) +* dwl now sends `wl_surface.preferred_buffer_scale` (through wlr_scene: [wlroots!4269][wlroots!4269]) +* Add support for xdg-shell v6 ([#465][465]) +* Add support for wp-cursor-shape-v1 ([#444][444]) +* Add desktop file ([#484][484]) +* Add macro to easily configure colors ([#466][466]) +* Color of urgent clients are now red ([#494][494]) +* New flag `-d` and option `log_level` to change the wlroots debug level +* Add CHANGELOG.md ([#501][501]) + +[301]: https://github.com/djpohly/dwl/pull/301 +[368]: https://github.com/djpohly/dwl/pull/368 +[381]: https://github.com/djpohly/dwl/pull/381 +[387]: https://github.com/djpohly/dwl/issues/387 +[425]: https://github.com/djpohly/dwl/pull/425 +[wlroots!4269]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4269 +[wlroots!3511]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3511 +[465]: https://github.com/djpohly/dwl/pull/465 +[444]: https://github.com/djpohly/dwl/pull/444 +[484]: https://github.com/djpohly/dwl/pull/484 +[466]: https://github.com/djpohly/dwl/issues/466 +[494]: https://github.com/djpohly/dwl/pull/494 +[501]: https://github.com/djpohly/dwl/pull/501 + + +### Changed + +* Replace `tags` with `TAGCOUNT` in config.def.h ([#403][403]) +* Pop ups are now destroyed when focusing another client ([#408][408]) +* dwl does not longer respect size hints, instead clip windows if they are + larger than they should be ([#455][455]) +* The version of wlr-layer-shell-unstable-v1 was lowered to 3 (from 4) +* Use the same border color as dwm ([#494][494]) + +[403]: https://github.com/djpohly/dwl/pull/403 +[408]: https://github.com/djpohly/dwl/pull/409 +[455]: https://github.com/djpohly/dwl/pull/455 +[494]: https://github.com/djpohly/dwl/pull/494 + + +### Removed + +* Remove unused `rootcolor` option ([#401][401]) +* Remove support for wlr-input-inhibitor-unstable-v1 ([#430][430]) +* Remove support for KDE idle protocol ([#431][431]) + +[401]: https://github.com/djpohly/dwl/pull/401 +[430]: https://github.com/djpohly/dwl/pull/430 +[431]: https://github.com/djpohly/dwl/pull/431 + + +### Fixed + +* Fix crash when creating a layer surface with all outputs disabled + ([#421][421]) +* Fix other clients being shown as focused if the focused client have pop ups + open ([#408][408]) +* Resize fullscreen clients when updating monitor mode +* dwl no longer crash at exit like sometimes did +* Fullscreen background appearing above clients ([#487][487]) +* Fix a segfault when user provides invalid xkb_rules ([#518][518]) + +[421]: https://github.com/djpohly/dwl/pull/421 +[408]: https://github.com/djpohly/dwl/issues/408 +[487]: https://github.com/djpohly/dwl/issues/487 +[518]: https://github.com/djpohly/dwl/pull/518 + + +### Contributors + +* A Frederick Christensen +* Angelo Antony +* Ben Collerson +* Devin J. Pohly +* Forrest Bushstone +* gan-of-culture +* godalming123 +* Job79 +* link2xt +* Micah Gorrell +* Nikita Ivanov +* Palanix +* pino-desktop +* Weiseguy +* Yves Zoundi diff --git a/dwl-bak/LICENSE b/dwl-bak/LICENSE new file mode 100644 index 0000000..658085a --- /dev/null +++ b/dwl-bak/LICENSE @@ -0,0 +1,692 @@ +dwl - dwm for Wayland + +Copyright © 2020 dwl team + +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/dwl-bak/LICENSE.dwm b/dwl-bak/LICENSE.dwm new file mode 100644 index 0000000..507e4dc --- /dev/null +++ b/dwl-bak/LICENSE.dwm @@ -0,0 +1,39 @@ +Portions of dwl based on dwm code are used under the following license: + +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dwl-bak/LICENSE.sway b/dwl-bak/LICENSE.sway new file mode 100644 index 0000000..3e0cacc --- /dev/null +++ b/dwl-bak/LICENSE.sway @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dwl-bak/LICENSE.tinywl b/dwl-bak/LICENSE.tinywl new file mode 100644 index 0000000..7023690 --- /dev/null +++ b/dwl-bak/LICENSE.tinywl @@ -0,0 +1,127 @@ +dwl is originally based on TinyWL, which is used under the following license: + +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/dwl-bak/Makefile b/dwl-bak/Makefile new file mode 100644 index 0000000..9bc67db --- /dev/null +++ b/dwl-bak/Makefile @@ -0,0 +1,78 @@ +.POSIX: +.SUFFIXES: + +include config.mk + +# flags for compiling +DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ + -DVERSION=\"$(VERSION)\" $(XWAYLAND) +DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ + -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ + -Wfloat-conversion + +# CFLAGS / LDFLAGS +PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +all: dwl +dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h +util.o: util.c util.h + +# wayland-scanner is a tool which generates C headers and rigging for Wayland +# protocols, which are specified in XML. wlroots requires you to rig these up +# to your build system yourself and provide them in the include path. +WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` +WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` + +cursor-shape-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@ +pointer-constraints-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + protocols/wlr-layer-shell-unstable-v1.xml $@ +wlr-output-power-management-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/wlr-output-power-management-unstable-v1.xml $@ +xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ + +config.h: + cp config.def.h $@ +clean: + rm -f dwl *.o *-protocol.h + +dist: clean + mkdir -p dwl-$(VERSION) + cp -R LICENSE* Makefile CHANGELOG.md README.md client.h config.def.h \ + config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop \ + dwl-$(VERSION) + tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) + rm -rf dwl-$(VERSION) + +install: dwl + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 + mkdir -p $(DESTDIR)$(DATADIR)/wayland-sessions + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + +.SUFFIXES: .c .o +.c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< diff --git a/dwl-bak/README.md b/dwl-bak/README.md new file mode 100644 index 0000000..aa95bab --- /dev/null +++ b/dwl-bak/README.md @@ -0,0 +1,201 @@ +# dwl - dwm for Wayland + +Join us on our IRC channel: [#dwl on Libera Chat] +Or on our [Discord server]. + +dwl is a compact, hackable compositor for [Wayland] based on [wlroots]. It is +intended to fill the same space in the Wayland world that dwm does in X11, +primarily in terms of functionality, and secondarily in terms of +philosophy. Like dwm, dwl is: + +- Easy to understand, hack on, and extend with patches +- One C source file (or a very small number) configurable via `config.h` +- Tied to as few external dependencies as possible + +## Getting Started: + +### **dwl branch 0.7 and releases based upon 0.7 build against [wlroots] 0.18** + +### Latest semi-stable [release] +This is probably where you want to start. This builds against the dependent +packages' versions currently shipping in major distributions. If your +distribution's wlroots version is older, use an earlier dwl [release] or [0.x +branch]. + +### Development branch [main] +Active development progresses on the `main` branch. The `main` branch is built +against a late (and often changing) git commit of wlroots. While the adventurous +are welcome to use `main`, it is a rocky road. Using `main` requires that the +user be willing to chase git commits of wlroots. Testing development pull +requests may involve merging unmerged pull requests in [wlroots]' git repository +and/or git commits of wayland. + +### Building dwl +dwl has the following dependencies: +- libinput +- wayland +- wlroots (compiled with the libinput backend) +- xkbcommon +- wayland-protocols (compile-time only) +- pkg-config (compile-time only) + +dwl has the following additional dependencies if XWayland support is enabled: +- libxcb +- libxcb-wm +- wlroots (compiled with X11 support) +- Xwayland (runtime only) + +Install these (and their `-devel` versions if your distro has separate +development packages) and run `make`. If you wish to build against a released +version of wlroots (*you probably do*), use a [release] or a [0.x branch]. If +you want to use the unstable development `main` branch, you need to use the git +version of [wlroots]. + +To enable XWayland, you should uncomment its flags in `config.mk`. + +## Configuration + +All configuration is done by editing `config.h` and recompiling, in the same +manner as dwm. There is no way to separately restart the window manager in +Wayland without restarting the entire display server, so any changes will take +effect the next time dwl is executed. + +As in the dwm community, we encourage users to share patches they have +created. Check out the [dwl-patches] repository! + +## Running dwl + +dwl can be run on any of the backends supported by wlroots. This means you can +run it as a separate window inside either an X11 or Wayland session, as well as +directly from a VT console. Depending on your distro's setup, you may need to +add your user to the `video` and `input` groups before you can run dwl on a +VT. If you are using `elogind` or `systemd-logind` you need to install polkit; +otherwise you need to add yourself in the `seat` group and enable/start the +seatd daemon. + +When dwl is run with no arguments, it will launch the server and begin handling +any shortcuts configured in `config.h`. There is no status bar or other +decoration initially; these are instead clients that can be run within the +Wayland session. Do note that the default background color is black. This can be +modified in `config.h`. + +If you would like to run a script or command automatically at startup, you can +specify the command using the `-s` option. This command will be executed as a +shell command using `/bin/sh -c`. It serves a similar function to `.xinitrc`, +but differs in that the display server will not shut down when this process +terminates. Instead, dwl will send this process a SIGTERM at shutdown and wait +for it to terminate (if it hasn't already). This makes it ideal for execing into +a user service manager like [s6], [anopa], [runit], [dinit], or [`systemd +--user`]. + +Note: The `-s` command is run as a *child process* of dwl, which means that it +does not have the ability to affect the environment of dwl or of any processes +that it spawns. If you need to set environment variables that affect the entire +dwl session, these must be set prior to running dwl. For example, Wayland +requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager +such as `elogind` or `systemd-logind`. If your system doesn't do this +automatically, you will need to configure it prior to launching `dwl`, e.g.: + + export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u) + mkdir -p $XDG_RUNTIME_DIR + dwl + +### Status information + +Information about selected layouts, current window title, app-id, and +selected/occupied/urgent tags is written to the stdin of the `-s` command (see +the `printstatus()` function for details). This information can be used to +populate an external status bar with a script that parses the +information. Failing to read this information will cause dwl to block, so if you +do want to run a startup command that does not consume the status information, +you can close standard input with the `<&-` shell redirection, for example: + + dwl -s 'foot --server <&-' + +If your startup command is a shell script, you can achieve the same inside the +script with the line + + exec <&- + +To get a list of status bars that work with dwl consult our [wiki]. + +## Replacements for X applications + +You can find a [list of useful resources on our wiki]. + +## Background + +dwl is not meant to provide every feature under the sun. Instead, like dwm, it +sticks to features which are necessary, simple, and straightforward to implement +given the base on which it is built. Implemented default features are: + +- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, + client rules, mouse move/resize. Providing a built-in status bar is an + exception to this goal, to avoid dependencies on font rendering and/or drawing + libraries when an external bar could work well. +- Configurable multi-monitor layout support, including position and rotation +- Configurable HiDPI/multi-DPI support +- Idle-inhibit protocol which lets applications such as mpv disable idle + monitoring +- Provide information to external status bars via stdout/stdin +- Urgency hints via xdg-activate protocol +- Support screen lockers via ext-session-lock-v1 protocol +- Various Wayland protocols +- XWayland support as provided by wlroots (can be enabled in `config.mk`) +- Zero flickering - Wayland users naturally expect that "every frame is perfect" +- Layer shell popups (used by Waybar) +- Damage tracking provided by scenegraph API + +Given the Wayland architecture, dwl has to implement features from dwm **and** +the xorg-server. Because of this, it is impossible to maintain the original +project goal of 2000 SLOC and have a reasonably complete compositor with +features comparable to dwm. However, this does not mean that the code will grow +indiscriminately. We will try to keep the code as small as possible. + +Features under consideration (possibly as patches) are: + +- Protocols made trivial by wlroots +- Implement the text-input and input-method protocols to support IME once ibus + implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and + https://codeberg.org/dwl/dwl/pulls/235) + +Feature *non-goals* for the main codebase include: + +- Client-side decoration (any more than is necessary to tell the clients not to) +- Client-initiated window management, such as move, resize, and close, which can + be done through the compositor +- Animations and visual effects + +## Acknowledgements + +dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots +developers. This was made possible in many cases by looking at how sway +accomplished something, then trying to do the same in as suckless a way as +possible. + +Many thanks to suckless.org and the dwm developers and community for the +inspiration, and to the various contributors to the project, including: + +- **Devin J. Pohly for creating and nurturing the fledgling project** +- Alexander Courtis for the XWayland implementation +- Guido Cella for the layer-shell protocol implementation, patch maintenance, + and for helping to keep the project running +- Stivvo for output management and fullscreen support, and patch maintenance + + +[`systemd --user`]: https://wiki.archlinux.org/title/Systemd/User +[#dwl on Libera Chat]: https://web.libera.chat/?channels=#dwl +[0.7-rc1]: https://codeberg.org/dwl/dwl/releases/tag/v0.7-rc1 +[0.x branch]: https://codeberg.org/dwl/dwl/branches +[anopa]: https://jjacky.com/anopa/ +[dinit]: https://davmac.org/projects/dinit/ +[dwl-patches]: https://codeberg.org/dwl/dwl-patches +[list of useful resources on our wiki]: https://codeberg.org/dwl/dwl/wiki/Home#migrating-from-x +[main]: https://codeberg.org/dwl/dwl/src/branch/main +[release]: https://codeberg.org/dwl/dwl/releases +[runit]: http://smarden.org/runit/faq.html#userservices +[s6]: https://skarnet.org/software/s6/ +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/ +[wiki]: https://codeberg.org/dwl/dwl/wiki/Home#compatible-status-bars +[Discord server]: https://discord.gg/jJxZnrGPWN +[Wayland]: https://wayland.freedesktop.org/ diff --git a/dwl-bak/client.h b/dwl-bak/client.h new file mode 100644 index 0000000..42f225f --- /dev/null +++ b/dwl-bak/client.h @@ -0,0 +1,408 @@ +/* + * Attempt to consolidate unavoidable suck into one file, away from dwl.c. This + * file is not meant to be pretty. We use a .h file with static inline + * functions instead of a separate .c module, or function pointers like sway, so + * that they will simply compile out if the chosen #defines leave them unused. + */ + +/* Leave these functions first; they're used in the others */ +static inline int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#endif + return 0; +} + +static inline struct wlr_surface * +client_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->surface; +#endif + return c->surface.xdg->surface; +} + +static inline int +toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) +{ + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; + struct wlr_surface *root_surface; + struct wlr_layer_surface_v1 *layer_surface; + Client *c = NULL; + LayerSurface *l = NULL; + int type = -1; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; +#endif + + if (!s) + return -1; + root_surface = wlr_surface_get_root_surface(s); + +#ifdef XWAYLAND + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { + c = xsurface->data; + type = c->type; + goto end; + } +#endif + + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { + l = layer_surface->data; + type = LayerShell; + goto end; + } + + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) + return -1; + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; + } + } + +end: + if (pl) + *pl = l; + if (pc) + *pc = c; + return type; +} + +/* The others */ +static inline void +client_activate_surface(struct wlr_surface *s, int activated) +{ + struct wlr_xdg_toplevel *toplevel; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); + return; + } +#endif + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); +} + +static inline uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + } + return 0; +} + +static inline const char * +client_get_appid(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->class; +#endif + return c->surface.xdg->toplevel->app_id; +} + +static inline void +client_get_clip(Client *c, struct wlr_box *clip) +{ + struct wlr_box xdg_geom = {0}; + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_surface_get_geometry(c->surface.xdg, &xdg_geom); + clip->x = xdg_geom.x; + clip->y = xdg_geom.y; +} + +static inline void +client_get_geometry(Client *c, struct wlr_box *geom) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + geom->x = c->surface.xwayland->x; + geom->y = c->surface.xwayland->y; + geom->width = c->surface.xwayland->width; + geom->height = c->surface.xwayland->height; + return; + } +#endif + wlr_xdg_surface_get_geometry(c->surface.xdg, geom); +} + +static inline Client * +client_get_parent(Client *c) +{ + Client *p = NULL; +#ifdef XWAYLAND + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } +#endif + if (c->surface.xdg->toplevel->parent) + toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); + return p; +} + +static inline int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + +static inline const char * +client_get_title(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->title; +#endif + return c->surface.xdg->toplevel->title; +} + +static inline int +client_is_float_type(Client *c) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; + size_t i; + if (surface->modal) + return 1; + + for (i = 0; i < surface->window_type_len; i++) + if (surface->window_type[i] == netatom[NetWMWindowTypeDialog] + || surface->window_type[i] == netatom[NetWMWindowTypeSplash] + || surface->window_type[i] == netatom[NetWMWindowTypeToolbar] + || surface->window_type[i] == netatom[NetWMWindowTypeUtility]) + return 1; + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); + } +#endif + + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.max_height)); +} + +static inline int +client_is_rendered_on_mon(Client *c, Monitor *m) +{ + /* This is needed for when you don't want to check formal assignment, + * but rather actual displaying of the pixels. + * Usually VISIBLEON suffices and is also faster. */ + struct wlr_surface_output *s; + int unused_lx, unused_ly; + if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) + return 0; + wl_list_for_each(s, &client_surface(c)->current_outputs, link) + if (s->output == m->wlr_output) + return 1; + return 0; +} + +static inline int +client_is_stopped(Client *c) +{ + int pid; + siginfo_t in = {0}; +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + + wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); + if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { + /* This process is not our child process, while is very unluckely that + * it is stopped, in order to do not skip frames assume that it is. */ + if (errno == ECHILD) + return 1; + } else if (in.si_pid) { + if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) + return 1; + if (in.si_code == CLD_CONTINUED) + return 0; + } + + return 0; +} + +static inline int +client_is_unmanaged(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; +#endif + return 0; +} + +static inline void +client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) +{ + if (kb) + wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, + kb->num_keycodes, &kb->modifiers); + else + wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); +} + +static inline void +client_restack_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + wlr_xwayland_surface_restack(c->surface.xwayland, NULL, + XCB_STACK_MODE_ABOVE); +#endif + return; +} + +static inline void +client_send_close(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_close(c->surface.xwayland); + return; + } +#endif + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); +} + +static inline void +client_set_border_color(Client *c, const float color[static 4]) +{ + int i; + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); +} + +static inline void +client_set_fullscreen(Client *c, int fullscreen) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); + return; + } +#endif + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); +} + +static inline uint32_t +client_set_size(Client *c, uint32_t width, uint32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_configure(c->surface.xwayland, + c->geom.x + c->bw, c->geom.y + c->bw, width, height); + return 0; + } +#endif + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +static inline void +client_set_tiled(Client *c, uint32_t edges) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } +} + +static inline void +client_set_suspended(Client *c, int suspended) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); +} + +static inline int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_or_surface_wants_focus(c->surface.xwayland) + && wlr_xwayland_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#endif + return 0; +} + +static inline int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} diff --git a/dwl-bak/config.def.h b/dwl-bak/config.def.h new file mode 100644 index 0000000..0e40959 --- /dev/null +++ b/dwl-bak/config.def.h @@ -0,0 +1,202 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +static uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; + +/* tagging - TAGCOUNT must be no greater than 31 */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +/* logging */ +static int log_level = WLR_ERROR; + +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +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; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, +}; diff --git a/dwl-bak/config.def.h.orig b/dwl-bak/config.def.h.orig new file mode 100644 index 0000000..8ccbea3 --- /dev/null +++ b/dwl-bak/config.def.h.orig @@ -0,0 +1,195 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +static uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; + +/* tagging - TAGCOUNT must be no greater than 31 */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +/* logging */ +static int log_level = WLR_ERROR; + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +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; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, +}; diff --git a/dwl-bak/config.mk b/dwl-bak/config.mk new file mode 100644 index 0000000..fb99950 --- /dev/null +++ b/dwl-bak/config.mk @@ -0,0 +1,17 @@ +_VERSION = 0.7 +VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` + +PKG_CONFIG = pkg-config + +# paths +PREFIX = /usr/local +MANDIR = $(PREFIX)/share/man +DATADIR = $(PREFIX)/share + +XWAYLAND = +XLIBS = +# Uncomment to build XWayland support +XWAYLAND = -DXWAYLAND +XLIBS = xcb xcb-icccm + +CC = gcc diff --git a/dwl-bak/drwl.h b/dwl-bak/drwl.h new file mode 100644 index 0000000..b06a736 --- /dev/null +++ b/dwl-bak/drwl.h @@ -0,0 +1,311 @@ +/* + * drwl - https://codeberg.org/sewn/drwl + * + * Copyright (c) 2023-2024 sewn + * Copyright (c) 2024 notchoc + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The UTF-8 Decoder included is from Bjoern Hoehrmann: + * Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + */ +#pragma once + +#include +#include +#include + +enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ + +typedef struct fcft_font Fnt; +typedef pixman_image_t Img; + +typedef struct { + Img *image; + Fnt *font; + uint32_t *scheme; +} Drwl; + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 +#define UTF8_INVALID 0xFFFD + +static const uint8_t utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) +{ + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +static int +drwl_init(void) +{ + fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); + return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); +} + +static Drwl * +drwl_create(void) +{ + Drwl *drwl; + + if (!(drwl = calloc(1, sizeof(Drwl)))) + return NULL; + + return drwl; +} + +static void +drwl_setfont(Drwl *drwl, Fnt *font) +{ + if (drwl) + drwl->font = font; +} + +static void +drwl_setimage(Drwl *drwl, Img *image) +{ + if (drwl) + drwl->image = image; +} + +static Fnt * +drwl_font_create(Drwl *drwl, size_t count, + const char *names[static count], const char *attributes) +{ + Fnt *font = fcft_from_name(count, names, attributes); + if (drwl) + drwl_setfont(drwl, font); + return font; +} + +static void +drwl_font_destroy(Fnt *font) +{ + fcft_destroy(font); +} + +static inline pixman_color_t +convert_color(uint32_t clr) +{ + return (pixman_color_t){ + ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + (clr & 0xFF) * 0x101 + }; +} + +static void +drwl_setscheme(Drwl *drwl, uint32_t *scm) +{ + if (drwl) + drwl->scheme = scm; +} + +static Img * +drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) +{ + Img *image; + pixman_region32_t clip; + + image = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, w, h, bits, w * 4); + if (!image) + return NULL; + pixman_region32_init_rect(&clip, 0, 0, w, h); + pixman_image_set_clip_region32(image, &clip); + pixman_region32_fini(&clip); + + if (drwl) + drwl_setimage(drwl, image); + return image; +} + +static void +drwl_rect(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + int filled, int invert) +{ + pixman_color_t clr; + if (!drwl || !drwl->scheme || !drwl->image) + return; + + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + if (filled) + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, + &(pixman_rectangle16_t){x, y, w, h}); + else + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, + (pixman_rectangle16_t[4]){ + { x, y, w, 1 }, + { x, y + h - 1, w, 1 }, + { x, y, 1, h }, + { x + w - 1, y, 1, h }}); +} + +static int +drwl_text(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert) +{ + int ty; + int render = x || y || w || h; + long x_kern; + uint32_t cp = 0, last_cp = 0, state; + pixman_color_t clr; + pixman_image_t *fg_pix = NULL; + int noellipsis = 0; + const struct fcft_glyph *glyph, *eg = NULL; + int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; + + if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + fg_pix = pixman_image_create_solid_fill(&clr); + + drwl_rect(drwl, x, y, w, h, 1, !invert); + + x += lpad; + w -= lpad; + } + + if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) + fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; + + if (render) + eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); + + for (const char *p = text, *pp; pp = p, *p; p++) { + for (state = UTF8_ACCEPT; *p && + utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) + ; + if (!*p || state == UTF8_REJECT) { + cp = UTF8_INVALID; + if (p > pp) + p--; + } + + glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); + if (!glyph) + continue; + + x_kern = 0; + if (last_cp) + fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); + last_cp = cp; + + ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; + + if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && + *(p + 1) != '\0') { + /* cannot fit ellipsis after current codepoint */ + if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { + noellipsis = 1; + } else { + w -= eg->advance.x; + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, + x + eg->x, ty - eg->y, eg->width, eg->height); + } + } + + if ((x_kern + glyph->advance.x) > w) + break; + + x += x_kern; + + if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) + /* pre-rendered glyphs (eg. emoji) */ + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + else if (render) + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + + x += glyph->advance.x; + w -= glyph->advance.x; + } + + if (render) + pixman_image_unref(fg_pix); + + return x + (render ? w : 0); +} + +static unsigned int +drwl_font_getwidth(Drwl *drwl, const char *text) +{ + if (!drwl || !drwl->font || !text) + return 0; + return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); +} + +static void +drwl_image_destroy(Img *image) +{ + pixman_image_unref(image); +} + +static void +drwl_destroy(Drwl *drwl) +{ + if (drwl->font) + drwl_font_destroy(drwl->font); + if (drwl->image) + drwl_image_destroy(drwl->image); + free(drwl); +} + +static void +drwl_fini(void) +{ + fcft_fini(); +} diff --git a/dwl-bak/dwl-patches/LICENSE b/dwl-bak/dwl-patches/LICENSE new file mode 100644 index 0000000..658085a --- /dev/null +++ b/dwl-bak/dwl-patches/LICENSE @@ -0,0 +1,692 @@ +dwl - dwm for Wayland + +Copyright © 2020 dwl team + +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/dwl-bak/dwl-patches/LICENSE.dwm b/dwl-bak/dwl-patches/LICENSE.dwm new file mode 100644 index 0000000..507e4dc --- /dev/null +++ b/dwl-bak/dwl-patches/LICENSE.dwm @@ -0,0 +1,39 @@ +Portions of dwl based on dwm code are used under the following license: + +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dwl-bak/dwl-patches/LICENSE.sway b/dwl-bak/dwl-patches/LICENSE.sway new file mode 100644 index 0000000..3e0cacc --- /dev/null +++ b/dwl-bak/dwl-patches/LICENSE.sway @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dwl-bak/dwl-patches/LICENSE.tinywl b/dwl-bak/dwl-patches/LICENSE.tinywl new file mode 100644 index 0000000..7023690 --- /dev/null +++ b/dwl-bak/dwl-patches/LICENSE.tinywl @@ -0,0 +1,127 @@ +dwl is originally based on TinyWL, which is used under the following license: + +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/dwl-bak/dwl-patches/README.md b/dwl-bak/dwl-patches/README.md new file mode 100644 index 0000000..bdfa62a --- /dev/null +++ b/dwl-bak/dwl-patches/README.md @@ -0,0 +1,87 @@ +# dwl-patches +* A general [dwl wiki](https://codeberg.org/dwl/dwl/wiki) is available at the main [dwl] repository. +* This repository is exclusively for dwl PATCHES. + +*Note: All patches are user-submitted content. The authors of dwl do not continually monitor them. You are responsible for downloading and reviewing a patch before using it!* + +## Reporting Issues +- Issues with existing patches can be generated here in the dwl-patches [issues]. Please be sure to "@" reference the patch author in your issue. + +## Contributing Patches to `dwl-patches` +Since dwl follows [suckless](https://suckless.org/) philosophy, it doesn't provide every feature under the sun. To broaden dwl's functionality, one needs to get familiar with the concept of patching. To get your feet wet, consult [the hacking page](https://suckless.org/hacking/) of the suckless website. + +Patches should normally target the latest dwl [release]. +If you target an older release, specify that in the `Download` link on your `README.md` page. +If you target the unstable `main` branch, specify that in the `Download` link on your `README.md` page. + +0. Starting from a local clone of [dwl] (not dwl-patches) +1. If you do not have it already, add the remote for the main dwl repository in your local copy and fetch it: + `git remote add --fetch upstream https://codeberg.org/dwl/dwl` +2. Use git to create a branch for your new patch and hack away creating your patched version of [dwl]. +3. In your local clone of dwl, create a .patch file + `git format-patch upstream/main... --stdout > PATCHNAME.patch` +4. Now fork [dwl-patches] (not dwl) in Codeberg and clone it locally +5. Configure your `dwl-patches` local clone + `git config --local pull.rebase true` +6. In your local `dwl-patches` clone, add a directory called `patches/PATCHNAME`. Place the `PATCHNAME.patch` you created in step (2) into the `patches/PATCHNAME` directory. +7. Add a `README.md` page to the `PATCHNAME` directory using this template (add/remove sections as you like): + ```markdown + ### Description + Insert a short summary of changes that your patch implements. + + ### Download + - [git branch](/USERNAME/dwl/src/branch/PATCHNAME) + ^^^^^^^^^^ OPTIONAL: Patchers are no longer obligated to maintain public `dwl` branches devoted to their patches + - [0.7](/dwl/dwl-patches/raw/branch/main/patches/PATCHNAME/PATCHNAME.patch) + Use the ^RAW^ patch link here + ^^^ "0.7" is an example. Use the release that your patch targets + - [main YYYY-MM-DD](/dwl/dwl-patches/raw/branch/main/patches/PATCHNAME/PATCHNAME.patch) + ^^^^^^^^^^ Patches targeting the unstable "main" branch include a YYYY-MM-DD indicator + ### Authors - latest at top [Codeberg nick is mandatory; other contact methods optional] + - [YOUR_NICK](https://codeberg.org/USERNAME) + your_email@if_you_wish_to.share.it + your_irc_nick at [Libera IRC dwl channel](https://web.libera.chat/?channels=#dwl) + your_discord_handle at [dwl Discord](https://discord.gg/jJxZnrGPWN) + ``` + You may choose to include screenshots (hosted in your patch's subdirectory) in your `README.md`. The process is described [here](https://docs.codeberg.org/markdown/using-images/). + +8. Use the Codeberg web interface to send a pull request to [dwl-patches] (NOT to [dwl]) +9. WHEN YOUR PULL REQUEST IS APPROVED, your Codeberg account will also be granted commit access to [dwl-patches]. Once you have write access, you can make direct modifications/upates to your patches and you are free to create new patches rather than creating pull requests. + +Individuals who have made known that they no longer intend to maintain their patches will have commit access to the [dwl-pathces] repository removed. + +A returning user who formerly had commit access is welcome to open an issue on [dwl-patches] requesting commit access be reinstated. When doing so, please link to the original issue opened that granted commit access. + +## Updating/Modifying/Adopting Existing Patches +- If the existing patch is already being maintained by another author, do not make modifications to it. +- Create an issue at [issues] @mentioning the current maintainer. +- If you receive no reply for seven days, you may assume the patch abandoned and you may adopt the patch. +- Modify the `README.md` with new links for your raw patch and for your git branch. + - **LEAVE PREVIOUS AUTHOR(S)' NICKS/LINKS INTACT UNDER THE "Authors" HEADING!** + - Add your own nick/link to the top of the "Authors" list. + +## Deprecating Existing Patches +- Patches will not be removed from this archive but may instead be deprecated if the author(s)/maintainer(s) of a patch so desire. +- Please do not open issues or contact maintainers to request deprecation of a patch. +- Deprecation of a patch will only occur if *all* authors or current maintainers of the patch agree to the decision to deprecate. +- In such a circumstance the author(s)/maintainer(s): + - Will create a commit moving the patch to the `stale-patches` directory + - May explain in the associated `README.md` any relevant details of the decision to deprecate the patch. +- This process allows current or future users of the patch the option to adopt, modify, or integrate stale/historical code or portions thereof. + +## stale-patches +Deprecated or unmaintained patches are held in the [stale-patches] directory. +Currently, this directory also contains `.md` description files from ancient patches predating the move to Codeberg. + +If you have the inclination to revive one of these, please follow the same procedures outlined below for contributing new patches. + +In your initial pull request (or in the commit that revives the stale patch if you already have write access), remove the corresponding `.md` file or the patch directory from [stale-patches]. + +[stale-patches]: https://codeberg.org/dwl/dwl-patches/src/branch/main/stale-patches +[dwl]: https://codeberg.org/dwl/dwl +[dwl-patches]: https://codeberg.org/dwl/dwl-patches +[issues]: https://codeberg.org/dwl/dwl-patches/issues +[release]: https://codeberg.org/dwl/dwl/releases +[Codeberg]: https://codeberg.org + +## diff --git a/dwl-bak/dwl-patches/patches/accessnthmon/README.md b/dwl-bak/dwl-patches/patches/accessnthmon/README.md new file mode 100644 index 0000000..b7cd6a9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/accessnthmon/README.md @@ -0,0 +1,9 @@ +### Description +Port of dwm's accessnthmon. Adds functions to tag and focus monitor by index. + +### Download +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/patch/accessnthmonitor) +- [2024-05-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/accessnthmon/accessnthmon.patch) +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/accessnthmon/accessnthmon.patch b/dwl-bak/dwl-patches/patches/accessnthmon/accessnthmon.patch new file mode 100644 index 0000000..f69f57d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/accessnthmon/accessnthmon.patch @@ -0,0 +1,122 @@ +From 5f531bfb1387ded7b8817faf7df760d3b998742b Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Sat, 27 Apr 2024 21:25:16 +0200 +Subject: [PATCH] feat: access nth monitor + +--- + config.def.h | 4 +++- + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..4709c5d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -108,7 +108,9 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} }, \ ++ { WLR_MODIFIER_ALT, KEY, focusnthmon, {.ui = TAG} }, \ ++ { WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT, SKEY, tagnthmon, {.ui = TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +diff --git a/dwl.c b/dwl.c +index bf763df..1d42caf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -278,8 +279,10 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static Monitor *numtomon(int num); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); ++static void focusnthmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); +@@ -329,6 +332,7 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tagnthmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -1233,6 +1237,25 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++Monitor * ++numtomon(int num) ++{ ++ Monitor *m = NULL; ++ int found = 0; ++ int i = 0; ++ ++ wl_list_for_each(m, &mons, link) { ++ if (!m->wlr_output->enabled) ++ i--; ++ else if (i == num) { ++ found = true; ++ break; ++ } ++ i++; ++ } ++ return found ? m : NULL; ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1320,6 +1343,16 @@ focusmon(const Arg *arg) + focusclient(focustop(selmon), 1); + } + ++void ++focusnthmon(const Arg *arg) ++{ ++ Monitor *m = numtomon(arg->i); ++ if (!m || m == selmon) ++ return; ++ selmon = m; ++ focusclient(focustop(selmon), 1); ++} ++ + void + focusstack(const Arg *arg) + { +@@ -2569,6 +2602,19 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tagnthmon(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ Monitor *m = numtomon(arg->i); ++ if (!m || !sel) ++ return; ++ setmon(sel, m, 0); ++ ++ arrange(selmon); ++ arrange(m); ++} ++ + void + tile(Monitor *m) + { +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/alwayscenter/README.md b/dwl-bak/dwl-patches/patches/alwayscenter/README.md new file mode 100644 index 0000000..63d6f86 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/alwayscenter/README.md @@ -0,0 +1,9 @@ +### Description +Automatically center floating windows. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/alwayscenter) +- [2024-06-05](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/alwayscenter/alwayscenter.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl-bak/dwl-patches/patches/alwayscenter/alwayscenter.patch b/dwl-bak/dwl-patches/patches/alwayscenter/alwayscenter.patch new file mode 100644 index 0000000..59a83dc --- /dev/null +++ b/dwl-bak/dwl-patches/patches/alwayscenter/alwayscenter.patch @@ -0,0 +1,39 @@ +From f43a49324c2ddd21100d6308d1adde9d894746e2 Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Wed, 5 Jun 2024 12:05:16 +0200 +Subject: [PATCH] center floating windows + +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 12f441e..c377c67 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -499,6 +499,11 @@ applyrules(Client *c) + } + } + ++ if (mon) { ++ c->geom.x = (mon->w.width - c->geom.width) / 2 + mon->m.x; ++ c->geom.y = (mon->w.height - c->geom.height) / 2 + mon->m.y; ++ } ++ + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); + } +@@ -1787,6 +1792,10 @@ mapnotify(struct wl_listener *listener, void *data) + * If there is no parent, apply rules */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; ++ if (p->mon) { ++ c->geom.x = (p->mon->w.width - c->geom.width) / 2 + p->mon->m.x; ++ c->geom.y = (p->mon->w.height - c->geom.height) / 2 + p->mon->m.y; ++ } + setmon(c, p->mon, p->tags); + } else { + applyrules(c); +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/attachbottom/README.md b/dwl-bak/dwl-patches/patches/attachbottom/README.md new file mode 100644 index 0000000..80e12a0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachbottom/README.md @@ -0,0 +1,10 @@ +### Description +Newly created windows are placed at the bottom of the client tile stack. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/attachbottom) +- [2024-05-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/attachbottom/attachbottom.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Aurel Weinhold](https://github.com/AurelWeinhold) diff --git a/dwl-bak/dwl-patches/patches/attachbottom/attachbottom.patch b/dwl-bak/dwl-patches/patches/attachbottom/attachbottom.patch new file mode 100644 index 0000000..470a79b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachbottom/attachbottom.patch @@ -0,0 +1,29 @@ +From 0dda3ed8634154fd3887b71133b451d66a11b61d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 23:31:41 +1000 +Subject: [PATCH] attachbottom + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index bf763dfc..12e08e2b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1605,7 +1605,11 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ if (clients.prev) ++ // tile at the bottom ++ wl_list_insert(clients.prev, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/attachfocused/README.md b/dwl-bak/dwl-patches/patches/attachfocused/README.md new file mode 100644 index 0000000..a4f6049 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachfocused/README.md @@ -0,0 +1,11 @@ +### Description +Makes windows attach below the currently active window. + +KNOWN ISSUES: Upon closing the last client when using multiple monitors, the last client will briefly flash on all +monitors before closing. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/attachfocused/attachfocused.patch) + +### Authors +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl-bak/dwl-patches/patches/attachfocused/attachfocused.patch b/dwl-bak/dwl-patches/patches/attachfocused/attachfocused.patch new file mode 100644 index 0000000..2b8cd38 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachfocused/attachfocused.patch @@ -0,0 +1,29 @@ +From d03851c14073874f5b3d19a095e184dc24d219cd Mon Sep 17 00:00:00 2001 +From: MayOrMayNotBeACat +Date: Sun, 11 May 2025 20:24:51 -0400 +Subject: [PATCH] Make new windows attach to active client + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index cf3ef70..1907c5f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1726,7 +1726,11 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ Client* focused = focustop(selmon); ++ if (focused) ++ wl_list_insert(&focused->link, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/attachtop/README.md b/dwl-bak/dwl-patches/patches/attachtop/README.md new file mode 100644 index 0000000..0241b7d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachtop/README.md @@ -0,0 +1,13 @@ +### Description +This is a port of attachtop patch for dwm: https://dwm.suckless.org/patches/attachtop + +New client attaches below the last master/on top of the stack. + +Behavior feels very intuitive as it doesn't disrupt existing masters no matter the amount of them, it only pushes the clients in stack down. + +### Download +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/attachtop) +- [2025-06-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/attachtop/attachtop.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) diff --git a/dwl-bak/dwl-patches/patches/attachtop/attachtop.patch b/dwl-bak/dwl-patches/patches/attachtop/attachtop.patch new file mode 100644 index 0000000..d447246 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/attachtop/attachtop.patch @@ -0,0 +1,36 @@ +From 29e6a4bef02c473cc5bd04804fe508bda265077d Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sun, 7 Apr 2024 21:10:17 +0200 +Subject: [PATCH] New client are attached on top of the stack + +--- + dwl.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index bf763df..02e3d07 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1605,7 +1605,18 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ i = 0; ++ wl_list_for_each(w, &clients, link) { ++ if (!VISIBLEON(w, selmon) || w->isfloating) ++ continue; ++ p = w; ++ if (++i >= selmon->nmaster) ++ break; ++ } ++ if (i > 0) ++ wl_list_insert(&p->link, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/autostart/README.md b/dwl-bak/dwl-patches/patches/autostart/README.md new file mode 100644 index 0000000..1fd26d3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/autostart/README.md @@ -0,0 +1,14 @@ +### Description +Allow dwl to execute commands from autostart array in your config.h file. And when you exit dwl all processes from autostart array will be killed. + +Note: Commands from array are executed using execvp(). So if you need to execute shell command you need to prefix it with "sh", "-c" (change sh to any shell you like). + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/autostart) +- [2025-01-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/autostart/autostart.patch) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/autostart/autostart-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Rayan Nakib](https://nakibrayan2.pages.dev/) +- [NFVblog](https://github.com/nf02) diff --git a/dwl-bak/dwl-patches/patches/autostart/autostart-0.7.patch b/dwl-bak/dwl-patches/patches/autostart/autostart-0.7.patch new file mode 100644 index 0000000..12e6d7e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/autostart/autostart-0.7.patch @@ -0,0 +1,154 @@ +From 787f7252d63945996f009828aff3c44afd0f7781 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:11:36 -0600 +Subject: [PATCH] port autostart patch from dwm +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +https://dwm.suckless.org/patches/cool_autostart/ +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 7 +++++++ + dwl.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 61 insertions(+), 5 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8dc6502 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,13 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index 5bf995e..e8b8727 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -249,6 +249,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -432,6 +433,9 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -580,6 +584,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -676,11 +701,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1497,18 +1532,31 @@ void + handlesig(int signo) + { + if (signo == SIGCHLD) { +-#ifdef XWAYLAND + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid +- && (!xwayland || in.si_pid != xwayland->server->pid)) +- waitpid(in.si_pid, NULL, 0); +-#else +- while (waitpid(-1, NULL, WNOHANG) > 0); ++#ifdef XWAYLAND ++ && (!xwayland || in.si_pid != xwayland->server->pid) + #endif ++ ) { ++ pid_t *p, *lim; ++ waitpid(in.si_pid, NULL, 0); ++ if (in.si_pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == in.si_pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +@@ -2224,6 +2272,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/autostart/autostart.patch b/dwl-bak/dwl-patches/patches/autostart/autostart.patch new file mode 100644 index 0000000..71d8718 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/autostart/autostart.patch @@ -0,0 +1,148 @@ +From 3b0b0249d900121a90528616f4d11f733c7a5ca2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:11:36 -0600 +Subject: [PATCH] port autostart patch from dwm +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +https://dwm.suckless.org/patches/cool_autostart/ +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 7 +++++++ + dwl.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 62 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..8dc6502c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,13 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index ad21e1ba..3118e07f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -246,6 +246,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -455,6 +456,9 @@ static struct wlr_xwayland *xwayland; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -599,6 +603,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -695,12 +720,23 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; ++ + cleanuplisteners(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1551,10 +1587,25 @@ gpureset(struct wl_listener *listener, void *data) + void + handlesig(int signo) + { +- if (signo == SIGCHLD) +- while (waitpid(-1, NULL, WNOHANG) > 0); +- else if (signo == SIGINT || signo == SIGTERM) ++ if (signo == SIGCHLD) { ++ pid_t pid, *p, *lim; ++ while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { ++ if (pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } ++ } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); ++ } + } + + void +@@ -2241,6 +2292,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/bar-systray/README.md b/dwl-bak/dwl-patches/patches/bar-systray/README.md new file mode 100644 index 0000000..b504173 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar-systray/README.md @@ -0,0 +1,19 @@ +### Description +Add a system tray to the [bar](/dwl/dwl-patches/src/branch/main/patches/bar). + +To keep dependencies to minimum, icon(svg) loading from the filesystem is not +supported. Applications that expect this will show the initial letter of the +program name, instead of a real icon. + +Menus for the icons are handled by a dmenu-like program of the user's choice. + +### Dependencies +- [bar.patch](/dwl/dwl-patches/src/branch/main/patches/bar) +- [libdbus](https://gitlab.freedesktop.org/dbus/dbus) + +### Download +- [git branch](/janetski/dwl/src/branch/0.7-bar-systray) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar-systray/bar-systray-0.7.patch) + +### Authors +- [janetski](https://codeberg.org/janetski) ([.vetu](https://discordapp.com/users/355488216469471242) on discord) diff --git a/dwl-bak/dwl-patches/patches/bar-systray/bar-systray-0.7.patch b/dwl-bak/dwl-patches/patches/bar-systray/bar-systray-0.7.patch new file mode 100644 index 0000000..1dda4c6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar-systray/bar-systray-0.7.patch @@ -0,0 +1,3023 @@ +From cf228147250f4616d150fbe5276088c5f9969bba Mon Sep 17 00:00:00 2001 +From: vetu104 +Date: Sat, 29 Mar 2025 19:22:37 +0200 +Subject: [PATCH] Add a system tray next to sewn's bar + +--- + Makefile | 23 +- + config.def.h | 5 + + dbus.c | 242 +++++++++++++++ + dbus.h | 10 + + dwl.c | 107 ++++++- + systray/helpers.c | 43 +++ + systray/helpers.h | 12 + + systray/icon.c | 149 +++++++++ + systray/icon.h | 26 ++ + systray/item.c | 403 ++++++++++++++++++++++++ + systray/item.h | 46 +++ + systray/menu.c | 757 ++++++++++++++++++++++++++++++++++++++++++++++ + systray/menu.h | 11 + + systray/tray.c | 237 +++++++++++++++ + systray/tray.h | 37 +++ + systray/watcher.c | 551 +++++++++++++++++++++++++++++++++ + systray/watcher.h | 35 +++ + 17 files changed, 2681 insertions(+), 13 deletions(-) + create mode 100644 dbus.c + create mode 100644 dbus.h + create mode 100644 systray/helpers.c + create mode 100644 systray/helpers.h + create mode 100644 systray/icon.c + create mode 100644 systray/icon.h + create mode 100644 systray/item.c + create mode 100644 systray/item.h + create mode 100644 systray/menu.c + create mode 100644 systray/menu.h + create mode 100644 systray/tray.c + create mode 100644 systray/tray.h + create mode 100644 systray/watcher.c + create mode 100644 systray/watcher.h + +diff --git a/Makefile b/Makefile +index 9bc67db..9d50189 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,17 +12,28 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) dbus-1 + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + ++TRAYOBJS = systray/watcher.o systray/tray.o systray/item.o systray/icon.o systray/menu.o systray/helpers.o ++TRAYDEPS = systray/watcher.h systray/tray.h systray/item.h systray/icon.h systray/menu.h systray/helpers.h ++ + all: dwl +-dwl: dwl.o util.o +- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl: dwl.o util.o dbus.o $(TRAYOBJS) $(TRAYDEPS) ++ $(CC) dwl.o util.o dbus.o $(TRAYOBJS) $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ ++dwl.o: dwl.c client.h dbus.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ $(TRAYDEPS) + util.o: util.c util.h ++dbus.o: dbus.c dbus.h ++systray/watcher.o: systray/watcher.c $(TRAYDEPS) ++systray/tray.o: systray/tray.c $(TRAYDEPS) ++systray/item.o: systray/item.c $(TRAYDEPS) ++systray/icon.o: systray/icon.c $(TRAYDEPS) ++systray/menu.o: systray/menu.c $(TRAYDEPS) ++systray/helpers.o: systray/helpers.c $(TRAYDEPS) + + # wayland-scanner is a tool which generates C headers and rigging for Wayland + # protocols, which are specified in XML. wlroots requires you to rig these up +@@ -49,7 +60,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h systray/*.o + + dist: clean + mkdir -p dwl-$(VERSION) +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..451643e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,8 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int systrayspacing = 2; /* systray spacing */ ++static const int showsystray = 1; /* 0 means no systray */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; +@@ -127,6 +129,7 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* commands */ + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; ++static const char *dmenucmd[] = { "wmenu", NULL }; + + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +@@ -188,4 +191,6 @@ static const Button buttons[] = { + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, ++ { ClkTray, 0, BTN_LEFT, trayactivate, {0} }, ++ { ClkTray, 0, BTN_RIGHT, traymenu, {0} }, + }; +diff --git a/dbus.c b/dbus.c +new file mode 100644 +index 0000000..125312c +--- /dev/null ++++ b/dbus.c +@@ -0,0 +1,242 @@ ++#include "dbus.h" ++ ++#include "util.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++static void ++close_pipe(void *data) ++{ ++ int *pipefd = data; ++ ++ close(pipefd[0]); ++ close(pipefd[1]); ++ free(pipefd); ++} ++ ++static int ++dwl_dbus_dispatch(int fd, unsigned int mask, void *data) ++{ ++ DBusConnection *conn = data; ++ ++ int pending; ++ DBusDispatchStatus oldstatus, newstatus; ++ ++ oldstatus = dbus_connection_get_dispatch_status(conn); ++ newstatus = dbus_connection_dispatch(conn); ++ ++ /* Don't clear pending flag if status didn't change */ ++ if (oldstatus == newstatus) ++ return 0; ++ ++ if (read(fd, &pending, sizeof(int)) < 0) { ++ perror("read"); ++ die("Error in dbus dispatch"); ++ } ++ ++ return 0; ++} ++ ++static int ++dwl_dbus_watch_handle(int fd, uint32_t mask, void *data) ++{ ++ DBusWatch *watch = data; ++ ++ uint32_t flags = 0; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return 0; ++ ++ if (mask & WL_EVENT_READABLE) ++ flags |= DBUS_WATCH_READABLE; ++ if (mask & WL_EVENT_WRITABLE) ++ flags |= DBUS_WATCH_WRITABLE; ++ if (mask & WL_EVENT_HANGUP) ++ flags |= DBUS_WATCH_HANGUP; ++ if (mask & WL_EVENT_ERROR) ++ flags |= DBUS_WATCH_ERROR; ++ ++ dbus_watch_handle(watch, flags); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int fd; ++ struct wl_event_source *watch_source; ++ uint32_t mask = 0, flags; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return TRUE; ++ ++ flags = dbus_watch_get_flags(watch); ++ if (flags & DBUS_WATCH_READABLE) ++ mask |= WL_EVENT_READABLE; ++ if (flags & DBUS_WATCH_WRITABLE) ++ mask |= WL_EVENT_WRITABLE; ++ ++ fd = dbus_watch_get_unix_fd(watch); ++ watch_source = wl_event_loop_add_fd(loop, fd, mask, ++ dwl_dbus_watch_handle, watch); ++ ++ dbus_watch_set_data(watch, watch_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_source *watch_source = dbus_watch_get_data(watch); ++ ++ if (watch_source) ++ wl_event_source_remove(watch_source); ++} ++ ++static int ++dwl_dbus_timeout_handle(void *data) ++{ ++ DBusTimeout *timeout = data; ++ ++ if (dbus_timeout_get_enabled(timeout)) ++ dbus_timeout_handle(timeout); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int r, interval; ++ struct wl_event_source *timeout_source; ++ ++ if (!dbus_timeout_get_enabled(timeout)) ++ return TRUE; ++ ++ interval = dbus_timeout_get_interval(timeout); ++ ++ timeout_source = ++ wl_event_loop_add_timer(loop, dwl_dbus_timeout_handle, timeout); ++ ++ r = wl_event_source_timer_update(timeout_source, interval); ++ if (r < 0) { ++ wl_event_source_remove(timeout_source); ++ return FALSE; ++ } ++ ++ dbus_timeout_set_data(timeout, timeout_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_source *timeout_source; ++ ++ timeout_source = dbus_timeout_get_data(timeout); ++ ++ if (timeout_source) { ++ wl_event_source_timer_update(timeout_source, 0); ++ wl_event_source_remove(timeout_source); ++ } ++} ++ ++static void ++dwl_dbus_dispatch_status(DBusConnection *conn, DBusDispatchStatus status, ++ void *data) ++{ ++ int *pipefd = data; ++ ++ if (status != DBUS_DISPATCH_COMPLETE) { ++ int pending = 1; ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) { ++ perror("write"); ++ die("Error in dispatch status"); ++ } ++ } ++} ++ ++struct wl_event_source * ++startbus(DBusConnection *conn, struct wl_event_loop *loop) ++{ ++ int *pipefd; ++ int pending = 1, flags; ++ struct wl_event_source *bus_source = NULL; ++ ++ pipefd = ecalloc(2, sizeof(int)); ++ ++ /* ++ * Libdbus forbids calling dbus_connection_dipatch from the ++ * DBusDispatchStatusFunction directly. Notify the event loop of ++ * updates via a self-pipe. ++ */ ++ if (pipe(pipefd) < 0) ++ goto fail; ++ if (((flags = fcntl(pipefd[0], F_GETFD)) < 0) || ++ fcntl(pipefd[0], F_SETFD, flags | FD_CLOEXEC) < 0 || ++ ((flags = fcntl(pipefd[1], F_GETFD)) < 0) || ++ fcntl(pipefd[1], F_SETFD, flags | FD_CLOEXEC) < 0) { ++ goto fail; ++ } ++ ++ dbus_connection_set_exit_on_disconnect(conn, FALSE); ++ ++ bus_source = wl_event_loop_add_fd(loop, pipefd[0], WL_EVENT_READABLE, ++ dwl_dbus_dispatch, conn); ++ if (!bus_source) ++ goto fail; ++ ++ dbus_connection_set_dispatch_status_function(conn, ++ dwl_dbus_dispatch_status, ++ pipefd, close_pipe); ++ if (!dbus_connection_set_watch_functions(conn, dwl_dbus_add_watch, ++ dwl_dbus_remove_watch, NULL, ++ loop, NULL)) { ++ goto fail; ++ } ++ if (!dbus_connection_set_timeout_functions(conn, dwl_dbus_add_timeout, ++ dwl_dbus_remove_timeout, ++ NULL, loop, NULL)) { ++ goto fail; ++ } ++ if (dbus_connection_get_dispatch_status(conn) != DBUS_DISPATCH_COMPLETE) ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) ++ goto fail; ++ ++ return bus_source; ++ ++fail: ++ if (bus_source) ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++ ++ return NULL; ++} ++ ++void ++stopbus(DBusConnection *conn, struct wl_event_source *bus_source) ++{ ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++} +diff --git a/dbus.h b/dbus.h +new file mode 100644 +index 0000000..b374b98 +--- /dev/null ++++ b/dbus.h +@@ -0,0 +1,10 @@ ++#ifndef DWLDBUS_H ++#define DWLDBUS_H ++ ++#include ++#include ++ ++struct wl_event_source* startbus (DBusConnection *conn, struct wl_event_loop *loop); ++void stopbus (DBusConnection *conn, struct wl_event_source *bus_source); ++ ++#endif /* DWLDBUS_H */ +diff --git a/dwl.c b/dwl.c +index ece537a..7753ef6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -71,6 +72,9 @@ + + #include "util.h" + #include "drwl.h" ++#include "dbus.h" ++#include "systray/tray.h" ++#include "systray/watcher.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -89,7 +93,7 @@ enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +-enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot, ClkTray }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -218,6 +222,7 @@ struct Monitor { + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ ++ Tray *tray; + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -376,6 +381,9 @@ static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); ++static void trayactivate(const Arg *arg); ++static void traymenu(const Arg *arg); ++static void traynotify(void *data); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); +@@ -451,6 +459,10 @@ static Monitor *selmon; + static char stext[256]; + static struct wl_event_source *status_event_source; + ++static DBusConnection *bus_conn; ++static struct wl_event_source *bus_source; ++static Watcher watcher = {.running = 0}; ++ + static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, +@@ -721,8 +733,8 @@ bufrelease(struct wl_listener *listener, void *data) + void + buttonpress(struct wl_listener *listener, void *data) + { +- unsigned int i = 0, x = 0; +- double cx; ++ unsigned int i = 0, x = 0, ti = 0; ++ double cx, tx = 0; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; +@@ -732,6 +744,7 @@ buttonpress(struct wl_listener *listener, void *data) + Arg arg = {0}; + Client *c; + const Button *b; ++ int traywidth; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + +@@ -751,6 +764,8 @@ buttonpress(struct wl_listener *listener, void *data) + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ traywidth = tray_get_width(selmon->tray); ++ + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -759,8 +774,16 @@ buttonpress(struct wl_listener *listener, void *data) + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; +- else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2) && cx < selmon->b.width - traywidth) { + click = ClkStatus; ++ } else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ unsigned int tray_n_items = watcher_get_n_items(&watcher); ++ tx = selmon->b.width - traywidth; ++ do ++ tx += tray_n_items ? (int)(traywidth / tray_n_items) : 0; ++ while (cx >= tx && ++ti < tray_n_items); ++ click = ClkTray; ++ arg.ui = ti; + } else + click = ClkTitle; + } +@@ -774,7 +797,12 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { +- b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); ++ if (click == ClkTagBar && b->arg.i == 0) ++ b->func(&arg); ++ else if (click == ClkTray && b->arg.i == 0) ++ b->func(&arg); ++ else ++ b->func(&b->arg); + return; + } + } +@@ -840,6 +868,14 @@ cleanup(void) + + destroykeyboardgroup(&kb_group->destroy, NULL); + ++ if (watcher.running) ++ watcher_stop(&watcher); ++ ++ if (showbar && showsystray) { ++ stopbus(bus_conn, bus_source); ++ dbus_connection_unref(bus_conn); ++ } ++ + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); +@@ -868,6 +904,9 @@ cleanupmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + ++ if (showsystray) ++ destroytray(m->tray); ++ + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + +@@ -1506,6 +1545,7 @@ dirtomon(enum wlr_direction dir) + void + drawbar(Monitor *m) + { ++ int traywidth = 0; + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; +@@ -1518,11 +1558,13 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ traywidth = tray_get_width(m->tray); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ drwl_text(m->drw, m->b.width - (tw + traywidth), 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { +@@ -1548,7 +1590,7 @@ drawbar(Monitor *m) + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->b.width - tw - x) > m->b.height) { ++ if ((w = m->b.width - (tw + x + traywidth)) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); +@@ -1560,6 +1602,15 @@ drawbar(Monitor *m) + } + } + ++ if (traywidth > 0) { ++ pixman_image_composite32(PIXMAN_OP_SRC, ++ m->tray->image, NULL, m->drw->image, ++ 0, 0, ++ 0, 0, ++ m->b.width - traywidth, 0, ++ traywidth, m->b.height); ++ } ++ + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, +@@ -1568,6 +1619,26 @@ drawbar(Monitor *m) + wlr_buffer_unlock(&buf->base); + } + ++void ++traynotify(void *data) ++{ ++ Monitor *m = data; ++ ++ drawbar(m); ++} ++ ++void ++trayactivate(const Arg *arg) ++{ ++ tray_leftclicked(selmon->tray, arg->ui); ++} ++ ++void ++traymenu(const Arg *arg) ++{ ++ tray_rightclicked(selmon->tray, arg->ui, dmenucmd); ++} ++ + void + drawbars(void) + { +@@ -2818,6 +2889,15 @@ setup(void) + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + ++ bus_conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); ++ if (!bus_conn) ++ die("Failed to connect to bus"); ++ bus_source = startbus(bus_conn, event_loop); ++ if (!bus_source) ++ die("Failed to start listening to bus events"); ++ if (showbar && showsystray) ++ watcher_start(&watcher, bus_conn, event_loop); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -3160,6 +3240,7 @@ updatebar(Monitor *m) + size_t i; + int rw, rh; + char fontattrs[12]; ++ Tray *tray; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; +@@ -3185,6 +3266,18 @@ updatebar(Monitor *m) + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++ ++ if (showsystray) { ++ if (m->tray) ++ destroytray(m->tray); ++ tray = createtray(m, ++ m->b.height, systrayspacing, colors[SchemeNorm], fonts, fontattrs, ++ &traynotify, &watcher); ++ if (!tray) ++ die("Couldn't create tray for monitor"); ++ m->tray = tray; ++ wl_list_insert(&watcher.trays, &tray->link); ++ } + } + + void +diff --git a/systray/helpers.c b/systray/helpers.c +new file mode 100644 +index 0000000..d1af9f8 +--- /dev/null ++++ b/systray/helpers.c +@@ -0,0 +1,43 @@ ++#include "helpers.h" ++ ++#include ++ ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++int ++request_property(DBusConnection *conn, const char *busname, const char *busobj, ++ const char *prop, const char *iface, PropHandler handler, ++ void *data) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ int r; ++ ++ if (!(msg = dbus_message_new_method_call(busname, busobj, ++ DBUS_INTERFACE_PROPERTIES, ++ "Get")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, handler, data, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ if (msg) ++ dbus_message_unref(msg); ++ return r; ++} +diff --git a/systray/helpers.h b/systray/helpers.h +new file mode 100644 +index 0000000..2c592e0 +--- /dev/null ++++ b/systray/helpers.h +@@ -0,0 +1,12 @@ ++#ifndef HELPERS_H ++#define HELPERS_H ++ ++#include ++ ++typedef void (*PropHandler)(DBusPendingCall *pcall, void *data); ++ ++int request_property (DBusConnection *conn, const char *busname, ++ const char *busobj, const char *prop, const char *iface, ++ PropHandler handler, void *data); ++ ++#endif /* HELPERS_H */ +diff --git a/systray/icon.c b/systray/icon.c +new file mode 100644 +index 0000000..1b97866 +--- /dev/null ++++ b/systray/icon.c +@@ -0,0 +1,149 @@ ++#include "icon.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#define PREMUL_ALPHA(chan, alpha) (chan * alpha + 127) / 255 ++ ++/* ++ * Converts pixels from uint8_t[4] to uint32_t and ++ * straight alpha to premultiplied alpha. ++ */ ++static uint32_t * ++to_pixman(const uint8_t *src, int n_pixels, size_t *pix_size) ++{ ++ uint32_t *dest = NULL; ++ ++ *pix_size = n_pixels * sizeof(uint32_t); ++ dest = malloc(*pix_size); ++ if (!dest) ++ return NULL; ++ ++ for (int i = 0; i < n_pixels; i++) { ++ uint8_t a = src[i * 4 + 0]; ++ uint8_t r = src[i * 4 + 1]; ++ uint8_t g = src[i * 4 + 2]; ++ uint8_t b = src[i * 4 + 3]; ++ ++ /* ++ * Skip premultiplying fully opaque and fully transparent ++ * pixels. ++ */ ++ if (a == 0) { ++ dest[i] = 0; ++ ++ } else if (a == 255) { ++ dest[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ++ ((uint32_t)g << 8) | ((uint32_t)b); ++ ++ } else { ++ dest[i] = ((uint32_t)a << 24) | ++ ((uint32_t)PREMUL_ALPHA(r, a) << 16) | ++ ((uint32_t)PREMUL_ALPHA(g, a) << 8) | ++ ((uint32_t)PREMUL_ALPHA(b, a)); ++ } ++ } ++ ++ return dest; ++} ++ ++Icon * ++createicon(const uint8_t *buf, int width, int height, int size) ++{ ++ Icon *icon = NULL; ++ ++ int n_pixels; ++ pixman_image_t *img = NULL; ++ size_t pixbuf_size; ++ uint32_t *buf_pixman = NULL; ++ uint8_t *buf_orig = NULL; ++ ++ n_pixels = size / 4; ++ ++ icon = calloc(1, sizeof(Icon)); ++ buf_orig = malloc(size); ++ buf_pixman = to_pixman(buf, n_pixels, &pixbuf_size); ++ if (!icon || !buf_orig || !buf_pixman) ++ goto fail; ++ ++ img = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, ++ buf_pixman, width * 4); ++ if (!img) ++ goto fail; ++ ++ memcpy(buf_orig, buf, size); ++ ++ icon->buf_orig = buf_orig; ++ icon->buf_pixman = buf_pixman; ++ icon->img = img; ++ icon->size_orig = size; ++ icon->size_pixman = pixbuf_size; ++ ++ return icon; ++ ++fail: ++ free(buf_orig); ++ if (img) ++ pixman_image_unref(img); ++ free(buf_pixman); ++ free(icon); ++ return NULL; ++} ++ ++void ++destroyicon(Icon *icon) ++{ ++ if (icon->img) ++ pixman_image_unref(icon->img); ++ free(icon->buf_orig); ++ free(icon->buf_pixman); ++ free(icon); ++} ++ ++FallbackIcon * ++createfallbackicon(const char *appname, int fgcolor, struct fcft_font *font) ++{ ++ const struct fcft_glyph *glyph; ++ char initial; ++ ++ if ((unsigned char)appname[0] > 127) { ++ /* first character is not ascii */ ++ initial = '?'; ++ } else { ++ initial = toupper(*appname); ++ } ++ ++ glyph = fcft_rasterize_char_utf32(font, initial, FCFT_SUBPIXEL_DEFAULT); ++ if (!glyph) ++ return NULL; ++ ++ return glyph; ++} ++ ++int ++resize_image(pixman_image_t *image, int new_width, int new_height) ++{ ++ int src_width = pixman_image_get_width(image); ++ int src_height = pixman_image_get_height(image); ++ pixman_transform_t transform; ++ pixman_fixed_t scale_x, scale_y; ++ ++ if (src_width == new_width && src_height == new_height) ++ return 0; ++ ++ scale_x = pixman_double_to_fixed((double)src_width / new_width); ++ scale_y = pixman_double_to_fixed((double)src_height / new_height); ++ ++ pixman_transform_init_scale(&transform, scale_x, scale_y); ++ if (!pixman_image_set_filter(image, PIXMAN_FILTER_BEST, NULL, 0) || ++ !pixman_image_set_transform(image, &transform)) { ++ return -1; ++ } ++ ++ return 0; ++} +diff --git a/systray/icon.h b/systray/icon.h +new file mode 100644 +index 0000000..20f281b +--- /dev/null ++++ b/systray/icon.h +@@ -0,0 +1,26 @@ ++#ifndef ICON_H ++#define ICON_H ++ ++#include ++#include ++ ++#include ++#include ++ ++typedef const struct fcft_glyph FallbackIcon; ++ ++typedef struct { ++ pixman_image_t *img; ++ uint32_t *buf_pixman; ++ uint8_t *buf_orig; ++ size_t size_orig; ++ size_t size_pixman; ++} Icon; ++ ++Icon *createicon (const uint8_t *buf, int width, int height, int size); ++FallbackIcon *createfallbackicon (const char *appname, int fgcolor, ++ struct fcft_font *font); ++void destroyicon (Icon *icon); ++int resize_image (pixman_image_t *orig, int new_width, int new_height); ++ ++#endif /* ICON_H */ +diff --git a/systray/item.c b/systray/item.c +new file mode 100644 +index 0000000..4359a28 +--- /dev/null ++++ b/systray/item.c +@@ -0,0 +1,403 @@ ++#include "item.h" ++ ++#include "helpers.h" ++#include "icon.h" ++#include "watcher.h" ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define RULEBSIZE 256 ++#define MIN(A, B) ((A) < (B) ? (A) : (B)) ++ ++static const char *match_string = ++ "type='signal'," ++ "sender='%s'," ++ "interface='" SNI_NAME ++ "'," ++ "member='NewIcon'"; ++ ++static Watcher * ++item_get_watcher(const Item *item) ++{ ++ if (!item) ++ return NULL; ++ ++ return item->watcher; ++} ++ ++static DBusConnection * ++item_get_connection(const Item *item) ++{ ++ if (!item || !item->watcher) ++ return NULL; ++ ++ return item->watcher->conn; ++} ++ ++static const uint8_t * ++extract_image(DBusMessageIter *iter, dbus_int32_t *width, dbus_int32_t *height, ++ int *size) ++{ ++ DBusMessageIter vals, bytes; ++ const uint8_t *buf; ++ ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, width); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, height); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&vals, &bytes); ++ if (dbus_message_iter_get_arg_type(&bytes) != DBUS_TYPE_BYTE) ++ goto fail; ++ dbus_message_iter_get_fixed_array(&bytes, &buf, size); ++ if (size == 0) ++ goto fail; ++ ++ return buf; ++ ++fail: ++ return NULL; ++} ++ ++static int ++select_image(DBusMessageIter *iter, int target_width) ++{ ++ DBusMessageIter vals; ++ dbus_int32_t cur_width; ++ int i = 0; ++ ++ do { ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ return -1; ++ dbus_message_iter_get_basic(&vals, &cur_width); ++ if (cur_width >= target_width) ++ return i; ++ ++ i++; ++ } while (dbus_message_iter_next(iter)); ++ ++ /* return last index if desired not found */ ++ return --i; ++} ++ ++static void ++menupath_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, opath; ++ char *path_dup = NULL; ++ const char *path; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get menupath\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &opath); ++ if (dbus_message_iter_get_arg_type(&opath) != DBUS_TYPE_OBJECT_PATH) ++ goto fail; ++ dbus_message_iter_get_basic(&opath, &path); ++ ++ path_dup = strdup(path); ++ if (!path_dup) ++ goto fail; ++ ++ item->menu_busobj = path_dup; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ free(path_dup); ++ dbus_error_free(&err); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++/* ++ * Gets the Id dbus property, which is the name of the application, ++ * most of the time... ++ * The initial letter will be used as a fallback icon ++ */ ++static void ++id_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, string; ++ Watcher *watcher; ++ char *id_dup = NULL; ++ const char *id; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get appid\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &string); ++ if (dbus_message_iter_get_arg_type(&string) != DBUS_TYPE_STRING) ++ goto fail; ++ dbus_message_iter_get_basic(&string, &id); ++ ++ id_dup = strdup(id); ++ if (!id_dup) ++ goto fail; ++ item->appid = id_dup; ++ ++ /* Don't trigger update if this item already has a real icon */ ++ if (!item->icon) ++ watcher_update_trays(watcher); ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ dbus_error_free(&err); ++ if (id_dup) ++ free(id_dup); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static void ++pixmap_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, array, select, strct; ++ Icon *icon = NULL; ++ Watcher *watcher; ++ dbus_int32_t width, height; ++ int selected_index, size; ++ const uint8_t *buf; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) ++ goto fail; ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &array); ++ if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&array, &select); ++ if (dbus_message_iter_get_arg_type(&select) != DBUS_TYPE_STRUCT) ++ goto fail; ++ selected_index = select_image(&select, 22); // Get the 22*22 image ++ if (selected_index < 0) ++ goto fail; ++ ++ dbus_message_iter_recurse(&array, &strct); ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_STRUCT) ++ goto fail; ++ for (int i = 0; i < selected_index; i++) ++ dbus_message_iter_next(&strct); ++ buf = extract_image(&strct, &width, &height, &size); ++ if (!buf) ++ goto fail; ++ ++ if (!item->icon) { ++ /* First icon */ ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else if (memcmp(item->icon->buf_orig, buf, ++ MIN(item->icon->size_orig, (size_t)size)) != 0) { ++ /* New icon */ ++ destroyicon(item->icon); ++ item->icon = NULL; ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else { ++ /* Icon didn't change */ ++ } ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (icon) ++ destroyicon(icon); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static DBusHandlerResult ++handle_newicon(Item *item, DBusConnection *conn, DBusMessage *msg) ++{ ++ const char *sender = dbus_message_get_sender(msg); ++ ++ if (sender && strcmp(sender, item->busname) == 0) { ++ request_property(conn, item->busname, item->busobj, ++ "IconPixmap", SNI_IFACE, pixmap_ready_handler, ++ item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ } else { ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Item *item = data; ++ ++ if (dbus_message_is_signal(msg, SNI_IFACE, "NewIcon")) ++ return handle_newicon(item, conn, msg); ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++Item * ++createitem(const char *busname, const char *busobj, Watcher *watcher) ++{ ++ DBusConnection *conn; ++ Item *item; ++ char *busname_dup = NULL; ++ char *busobj_dup = NULL; ++ char match_rule[RULEBSIZE]; ++ ++ item = calloc(1, sizeof(Item)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!item || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ conn = watcher->conn; ++ item->busname = busname_dup; ++ item->busobj = busobj_dup; ++ item->watcher = watcher; ++ ++ request_property(conn, busname, busobj, "IconPixmap", SNI_IFACE, ++ pixmap_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Id", SNI_IFACE, ++ id_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Menu", SNI_IFACE, ++ menupath_ready_handler, item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, busname) >= ++ RULEBSIZE) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, item, NULL)) ++ goto fail; ++ dbus_bus_add_match(conn, match_rule, NULL); ++ ++ return item; ++ ++fail: ++ free(busname_dup); ++ free(busobj_dup); ++ return NULL; ++} ++ ++void ++destroyitem(Item *item) ++{ ++ DBusConnection *conn; ++ char match_rule[RULEBSIZE]; ++ ++ conn = item_get_connection(item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, ++ item->busname) < RULEBSIZE) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, item); ++ } ++ if (item->icon) ++ destroyicon(item->icon); ++ free(item->menu_busobj); ++ free(item->busname); ++ free(item->busobj); ++ free(item->appid); ++ free(item); ++} ++ ++void ++item_activate(Item *item) ++{ ++ DBusConnection *conn; ++ DBusMessage *msg = NULL; ++ dbus_int32_t x = 0, y = 0; ++ ++ conn = item_get_connection(item); ++ ++ if (!(msg = dbus_message_new_method_call(item->busname, item->busobj, ++ SNI_IFACE, "Activate")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, ++ &y, DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, NULL, -1)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (msg) ++ dbus_message_unref(msg); ++} +diff --git a/systray/item.h b/systray/item.h +new file mode 100644 +index 0000000..dc22e25 +--- /dev/null ++++ b/systray/item.h +@@ -0,0 +1,46 @@ ++#ifndef ITEM_H ++#define ITEM_H ++ ++#include "icon.h" ++#include "watcher.h" ++ ++#include ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierItem"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierItem" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ * ++ */ ++#define SNI_NAME "org.kde.StatusNotifierItem" ++#define SNI_OPATH "/StatusNotifierItem" ++#define SNI_IFACE "org.kde.StatusNotifierItem" ++ ++typedef struct Item { ++ struct wl_list icons; ++ char *busname; ++ char *busobj; ++ char *menu_busobj; ++ char *appid; ++ Icon *icon; ++ FallbackIcon *fallback_icon; ++ ++ Watcher *watcher; ++ ++ int fgcolor; ++ ++ int ready; ++ ++ struct wl_list link; ++} Item; ++ ++Item *createitem (const char *busname, const char *busobj, Watcher *watcher); ++void destroyitem (Item *item); ++ ++void item_activate (Item *item); ++void item_show_menu (Item *item); ++ ++#endif /* ITEM_H */ +diff --git a/systray/menu.c b/systray/menu.c +new file mode 100644 +index 0000000..ff3bfb5 +--- /dev/null ++++ b/systray/menu.c +@@ -0,0 +1,757 @@ ++#include "menu.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define DBUSMENU_IFACE "com.canonical.dbusmenu" ++#define BUFSIZE 512 ++#define LABEL_MAX 64 ++ ++typedef struct { ++ struct wl_array layout; ++ DBusConnection *conn; ++ struct wl_event_loop *loop; ++ char *busname; ++ char *busobj; ++ const char **menucmd; ++} Menu; ++ ++typedef struct { ++ char label[LABEL_MAX]; ++ dbus_int32_t id; ++ struct wl_array submenu; ++ int has_submenu; ++} MenuItem; ++ ++typedef struct { ++ struct wl_event_loop *loop; ++ struct wl_event_source *fd_source; ++ struct wl_array *layout_node; ++ Menu *menu; ++ pid_t menu_pid; ++ int fd; ++} MenuShowContext; ++ ++static int extract_menu (DBusMessageIter *av, struct wl_array *menu); ++static int real_show_menu (Menu *menu, struct wl_array *m); ++static void submenus_destroy_recursive (struct wl_array *m); ++ ++static void ++menuitem_init(MenuItem *mi) ++{ ++ wl_array_init(&mi->submenu); ++ mi->id = -1; ++ *mi->label = '\0'; ++ mi->has_submenu = 0; ++} ++ ++static void ++submenus_destroy_recursive(struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, layout_node) { ++ if (mi->has_submenu) { ++ submenus_destroy_recursive(&mi->submenu); ++ wl_array_release(&mi->submenu); ++ } ++ } ++} ++ ++static void ++menu_destroy(Menu *menu) ++{ ++ submenus_destroy_recursive(&menu->layout); ++ wl_array_release(&menu->layout); ++ free(menu->busname); ++ free(menu->busobj); ++ free(menu); ++} ++ ++static void ++menu_show_ctx_finalize(MenuShowContext *ctx, int error) ++{ ++ if (ctx->fd_source) ++ wl_event_source_remove(ctx->fd_source); ++ ++ if (ctx->fd >= 0) ++ close(ctx->fd); ++ ++ if (ctx->menu_pid >= 0) { ++ if (waitpid(ctx->menu_pid, NULL, WNOHANG) == 0) ++ kill(ctx->menu_pid, SIGTERM); ++ } ++ ++ if (error) ++ menu_destroy(ctx->menu); ++ ++ free(ctx); ++} ++ ++static void ++remove_newline(char *buf) ++{ ++ size_t len; ++ ++ len = strlen(buf); ++ if (len > 0 && buf[len - 1] == '\n') ++ buf[len - 1] = '\0'; ++} ++ ++static void ++send_clicked(const char *busname, const char *busobj, int itemid, ++ DBusConnection *conn) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter sub = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *data = ""; ++ const char *eventid = "clicked"; ++ time_t timestamp; ++ ++ timestamp = time(NULL); ++ ++ msg = dbus_message_new_method_call(busname, busobj, DBUSMENU_IFACE, ++ "Event"); ++ if (!msg) ++ goto fail; ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &itemid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, ++ &eventid) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_STRING_AS_STRING, ++ &sub) || ++ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &data) || ++ !dbus_message_iter_close_container(&iter, &sub) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, ++ ×tamp)) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(conn, msg, NULL, -1)) ++ goto fail; ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&iter, &sub); ++ if (msg) ++ dbus_message_unref(msg); ++} ++ ++static void ++menuitem_selected(const char *label, struct wl_array *m, Menu *menu) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, m) { ++ if (strcmp(mi->label, label) == 0) { ++ if (mi->has_submenu) { ++ real_show_menu(menu, &mi->submenu); ++ ++ } else { ++ send_clicked(menu->busname, menu->busobj, ++ mi->id, menu->conn); ++ menu_destroy(menu); ++ } ++ ++ return; ++ } ++ } ++} ++ ++static int ++read_pipe(int fd, uint32_t mask, void *data) ++{ ++ MenuShowContext *ctx = data; ++ ++ char buf[BUFSIZE]; ++ ssize_t bytes_read; ++ ++ bytes_read = read(fd, buf, BUFSIZE); ++ /* 0 == Got EOF, menu program closed without writing to stdout */ ++ if (bytes_read <= 0) ++ goto fail; ++ ++ buf[bytes_read] = '\0'; ++ remove_newline(buf); ++ ++ menuitem_selected(buf, ctx->layout_node, ctx->menu); ++ menu_show_ctx_finalize(ctx, 0); ++ return 0; ++ ++fail: ++ menu_show_ctx_finalize(ctx, 1); ++ return 0; ++} ++ ++static MenuShowContext * ++prepare_show_ctx(struct wl_event_loop *loop, int monitor_fd, int dmenu_pid, ++ struct wl_array *layout_node, Menu *menu) ++{ ++ MenuShowContext *ctx = NULL; ++ struct wl_event_source *fd_src = NULL; ++ ++ ctx = calloc(1, sizeof(MenuShowContext)); ++ if (!ctx) ++ goto fail; ++ ++ fd_src = wl_event_loop_add_fd(menu->loop, monitor_fd, WL_EVENT_READABLE, ++ read_pipe, ctx); ++ if (!fd_src) ++ goto fail; ++ ++ ctx->fd_source = fd_src; ++ ctx->fd = monitor_fd; ++ ctx->menu_pid = dmenu_pid; ++ ctx->layout_node = layout_node; ++ ctx->menu = menu; ++ ++ return ctx; ++ ++fail: ++ if (fd_src) ++ wl_event_source_remove(fd_src); ++ free(ctx); ++ return NULL; ++} ++ ++static int ++write_dmenu_buf(char *buf, struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ int r; ++ size_t curlen = 0; ++ ++ *buf = '\0'; ++ ++ wl_array_for_each(mi, layout_node) { ++ curlen += strlen(mi->label) + ++ 2; /* +2 is newline + nul terminator */ ++ if (curlen + 1 > BUFSIZE) { ++ r = -1; ++ goto fail; ++ } ++ ++ strcat(buf, mi->label); ++ strcat(buf, "\n"); ++ } ++ remove_newline(buf); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Failed to construct dmenu input\n"); ++ return r; ++} ++ ++static int ++real_show_menu(Menu *menu, struct wl_array *layout_node) ++{ ++ MenuShowContext *ctx = NULL; ++ char buf[BUFSIZE]; ++ int to_pipe[2], from_pipe[2]; ++ pid_t pid; ++ ++ if (pipe(to_pipe) < 0 || pipe(from_pipe) < 0) ++ goto fail; ++ ++ pid = fork(); ++ if (pid < 0) { ++ goto fail; ++ } else if (pid == 0) { ++ dup2(to_pipe[0], STDIN_FILENO); ++ dup2(from_pipe[1], STDOUT_FILENO); ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ close(from_pipe[0]); ++ ++ if (execvp(menu->menucmd[0], (char *const *)menu->menucmd)) { ++ perror("Error spawning menu program"); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ ctx = prepare_show_ctx(menu->loop, from_pipe[0], pid, layout_node, ++ menu); ++ if (!ctx) ++ goto fail; ++ ++ if (write_dmenu_buf(buf, layout_node) < 0 || ++ write(to_pipe[1], buf, strlen(buf)) < 0) { ++ goto fail; ++ } ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ return 0; ++ ++fail: ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ menu_show_ctx_finalize(ctx, 1); ++ return -1; ++} ++ ++static void ++createmenuitem(MenuItem *mi, dbus_int32_t id, const char *label, ++ int toggle_state, int has_submenu) ++{ ++ char *tok; ++ char temp[LABEL_MAX]; ++ ++ if (toggle_state == 0) ++ strcpy(mi->label, "☐ "); ++ else if (toggle_state == 1) ++ strcpy(mi->label, "✓ "); ++ else ++ strcpy(mi->label, " "); ++ ++ /* Remove "mnemonics" (underscores which mark keyboard shortcuts) */ ++ strcpy(temp, label); ++ tok = strtok(temp, "_"); ++ do { ++ strcat(mi->label, tok); ++ } while ((tok = strtok(NULL, "_"))); ++ ++ if (has_submenu) { ++ mi->has_submenu = 1; ++ strcat(mi->label, " →"); ++ } ++ ++ mi->id = id; ++} ++ ++/** ++ * Populates the passed in menuitem based on the dictionary contents. ++ * ++ * @param[in] dict ++ * @param[in] itemid ++ * @param[in] mi ++ * @param[out] has_submenu ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++read_dict(DBusMessageIter *dict, dbus_int32_t itemid, MenuItem *mi, ++ int *has_submenu) ++{ ++ DBusMessageIter member, val; ++ const char *children_display = NULL, *label = NULL, *toggle_type = NULL; ++ const char *key; ++ dbus_bool_t visible = TRUE, enabled = TRUE; ++ dbus_int32_t toggle_state = 1; ++ int r; ++ ++ do { ++ dbus_message_iter_recurse(dict, &member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&member, &key); ++ ++ dbus_message_iter_next(&member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&member, &val); ++ ++ if (strcmp(key, "visible") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &visible); ++ ++ } else if (strcmp(key, "enabled") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &enabled); ++ ++ } else if (strcmp(key, "toggle-type") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_type); ++ ++ } else if (strcmp(key, "toggle-state") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_state); ++ ++ } else if (strcmp(key, "children-display") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &children_display); ++ ++ if (strcmp(children_display, "submenu") == 0) ++ *has_submenu = 1; ++ ++ } else if (strcmp(key, "label") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &label); ++ } ++ } while (dbus_message_iter_next(dict)); ++ ++ /* Skip hidden etc items */ ++ if (!label || !visible || !enabled) ++ return 1; ++ ++ /* ++ * 4 characters for checkmark and submenu indicator, ++ * 1 for nul terminator ++ */ ++ if (strlen(label) + 5 > LABEL_MAX) { ++ fprintf(stderr, "Too long menu entry label: %s! Skipping...\n", ++ label); ++ return 1; ++ } ++ ++ if (toggle_type && strcmp(toggle_type, "checkmark") == 0) ++ createmenuitem(mi, itemid, label, toggle_state, *has_submenu); ++ else ++ createmenuitem(mi, itemid, label, -1, *has_submenu); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Error parsing menu data\n"); ++ return r; ++} ++ ++/** ++ * Extracts a menuitem from a DBusMessage ++ * ++ * @param[in] strct ++ * @param[in] mi ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++extract_menuitem(DBusMessageIter *strct, MenuItem *mi) ++{ ++ DBusMessageIter val, dict; ++ dbus_int32_t itemid; ++ int has_submenu = 0; ++ int r; ++ ++ dbus_message_iter_recurse(strct, &val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &itemid); ++ ++ if (!dbus_message_iter_next(&val) || ++ dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&val, &dict); ++ if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = read_dict(&dict, itemid, mi, &has_submenu); ++ if (r < 0) { ++ goto fail; ++ ++ } else if (r == 0 && has_submenu) { ++ dbus_message_iter_next(&val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) ++ goto fail; ++ r = extract_menu(&val, &mi->submenu); ++ if (r < 0) ++ goto fail; ++ } ++ ++ return r; ++ ++fail: ++ return r; ++} ++ ++static int ++extract_menu(DBusMessageIter *av, struct wl_array *layout_node) ++{ ++ DBusMessageIter variant, menuitem; ++ MenuItem *mi; ++ int r; ++ ++ dbus_message_iter_recurse(av, &variant); ++ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ ++ do { ++ dbus_message_iter_recurse(&variant, &menuitem); ++ if (dbus_message_iter_get_arg_type(&menuitem) != ++ DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = extract_menuitem(&menuitem, mi); ++ if (r < 0) ++ goto fail; ++ else if (r == 0) { ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ } ++ /* r > 0: no action was performed on mi */ ++ } while (dbus_message_iter_next(&variant)); ++ ++ return 0; ++ ++fail: ++ return r; ++} ++ ++static void ++layout_ready(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, strct; ++ dbus_uint32_t revision; ++ int r; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { ++ r = -1; ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&iter, &revision); ++ ++ if (!dbus_message_iter_next(&iter) || ++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&iter, &strct); ++ ++ /* ++ * id 0 is the root, which contains nothing of interest. ++ * Traverse past it. ++ */ ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_INT32 || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ ++ /* Root traversed over, extract the menu */ ++ wl_array_init(&menu->layout); ++ r = extract_menu(&strct, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ r = real_show_menu(menu, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ menu_destroy(menu); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static int ++request_layout(Menu *menu) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter strings = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusPendingCall *pending = NULL; ++ dbus_int32_t parentid, depth; ++ int r; ++ ++ parentid = 0; ++ depth = -1; ++ ++ /* menu busobj request answer didn't arrive yet. */ ++ if (!menu->busobj) { ++ r = -1; ++ goto fail; ++ } ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "GetLayout"); ++ if (!msg) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, ++ &parentid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &depth) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &strings) || ++ !dbus_message_iter_close_container(&iter, &strings)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, layout_ready, menu, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ dbus_message_iter_abandon_container_if_open(&iter, &strings); ++ if (msg) ++ dbus_message_unref(msg); ++ menu_destroy(menu); ++ return r; ++} ++ ++static void ++about_to_show_handle(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (request_layout(menu) < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++ menu_destroy(menu); ++} ++ ++void ++menu_show(DBusConnection *conn, struct wl_event_loop *loop, const char *busname, ++ const char *busobj, const char **menucmd) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ Menu *menu = NULL; ++ char *busname_dup = NULL, *busobj_dup = NULL; ++ dbus_int32_t parentid = 0; ++ ++ menu = calloc(1, sizeof(Menu)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!menu || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ menu->conn = conn; ++ menu->loop = loop; ++ menu->busname = busname_dup; ++ menu->busobj = busobj_dup; ++ menu->menucmd = menucmd; ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "AboutToShow"); ++ if (!msg) ++ goto fail; ++ ++ if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, &parentid, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, about_to_show_handle, menu, ++ NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (pending) ++ dbus_pending_call_unref(pending); ++ if (msg) ++ dbus_message_unref(msg); ++ free(menu); ++} +diff --git a/systray/menu.h b/systray/menu.h +new file mode 100644 +index 0000000..7f48ada +--- /dev/null ++++ b/systray/menu.h +@@ -0,0 +1,11 @@ ++#ifndef MENU_H ++#define MENU_H ++ ++#include ++#include ++ ++/* The menu is built on demand and not kept around */ ++void menu_show (DBusConnection *conn, struct wl_event_loop *loop, ++ const char *busname, const char *busobj, const char **menucmd); ++ ++#endif /* MENU_H */ +diff --git a/systray/tray.c b/systray/tray.c +new file mode 100644 +index 0000000..7f9b1b0 +--- /dev/null ++++ b/systray/tray.c +@@ -0,0 +1,237 @@ ++#include "tray.h" ++ ++#include "icon.h" ++#include "item.h" ++#include "menu.h" ++#include "watcher.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#define PIXMAN_COLOR(hex) \ ++ { .red = ((hex >> 24) & 0xff) * 0x101, \ ++ .green = ((hex >> 16) & 0xff) * 0x101, \ ++ .blue = ((hex >> 8) & 0xff) * 0x101, \ ++ .alpha = (hex & 0xff) * 0x101 } ++ ++static Watcher * ++tray_get_watcher(const Tray *tray) ++{ ++ if (!tray) ++ return NULL; ++ ++ return tray->watcher; ++} ++ ++static pixman_image_t * ++createcanvas(int width, int height, int bgcolor) ++{ ++ pixman_image_t *src, *dest; ++ pixman_color_t bgcolor_pix = PIXMAN_COLOR(bgcolor); ++ ++ dest = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, ++ 0); ++ src = pixman_image_create_solid_fill(&bgcolor_pix); ++ ++ pixman_image_composite32(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0, ++ 0, width, height); ++ ++ pixman_image_unref(src); ++ return dest; ++} ++ ++void ++tray_update(Tray *tray) ++{ ++ Item *item; ++ Watcher *watcher; ++ int icon_size, i = 0, canvas_width, canvas_height, n_items, spacing; ++ pixman_image_t *canvas = NULL, *img; ++ ++ watcher = tray_get_watcher(tray); ++ n_items = watcher_get_n_items(watcher); ++ ++ if (!n_items) { ++ if (tray->image) { ++ pixman_image_unref(tray->image); ++ tray->image = NULL; ++ } ++ tray->cb(tray->monitor); ++ return; ++ } ++ ++ icon_size = tray->height; ++ spacing = tray->spacing; ++ canvas_width = n_items * (icon_size + spacing) + spacing; ++ canvas_height = tray->height; ++ ++ canvas = createcanvas(canvas_width, canvas_height, tray->scheme[1]); ++ if (!canvas) ++ goto fail; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ int slot_x_start = spacing + i * (icon_size + spacing); ++ int slot_x_end = slot_x_start + icon_size + spacing; ++ int slot_x_width = slot_x_end - slot_x_start; ++ ++ int slot_y_start = 0; ++ int slot_y_end = canvas_height; ++ int slot_y_width = slot_y_end - slot_y_start; ++ ++ if (item->icon) { ++ /* Real icon */ ++ img = item->icon->img; ++ if (resize_image(img, icon_size, icon_size) < 0) ++ goto fail; ++ pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, ++ canvas, 0, 0, 0, 0, ++ slot_x_start, 0, canvas_width, ++ canvas_height); ++ ++ } else if (item->appid) { ++ /* Font glyph alpha mask */ ++ const struct fcft_glyph *g; ++ int pen_y, pen_x; ++ pixman_color_t fg_color = PIXMAN_COLOR(tray->scheme[0]); ++ pixman_image_t *fg; ++ ++ if (item->fallback_icon) { ++ g = item->fallback_icon; ++ } else { ++ g = createfallbackicon(item->appid, ++ item->fgcolor, ++ tray->font); ++ if (!g) ++ goto fail; ++ item->fallback_icon = g; ++ } ++ ++ pen_x = slot_x_start + (slot_x_width - g->width) / 2; ++ pen_y = slot_y_start + (slot_y_width - g->height) / 2; ++ ++ fg = pixman_image_create_solid_fill(&fg_color); ++ pixman_image_composite32(PIXMAN_OP_OVER, fg, g->pix, ++ canvas, 0, 0, 0, 0, pen_x, ++ pen_y, canvas_width, ++ canvas_height); ++ pixman_image_unref(fg); ++ } ++ i++; ++ } ++ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ tray->image = canvas; ++ tray->cb(tray->monitor); ++ ++ return; ++ ++fail: ++ if (canvas) ++ pixman_image_unref(canvas); ++ return; ++} ++ ++void ++destroytray(Tray *tray) ++{ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ if (tray->font) ++ fcft_destroy(tray->font); ++ free(tray); ++} ++ ++Tray * ++createtray(void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher) ++{ ++ Tray *tray = NULL; ++ char fontattrs_my[128]; ++ struct fcft_font *font = NULL; ++ ++ sprintf(fontattrs_my, "%s:%s", fontattrs, "weight:bold"); ++ ++ tray = calloc(1, sizeof(Tray)); ++ font = fcft_from_name(1, fonts, fontattrs_my); ++ if (!tray || !font) ++ goto fail; ++ ++ tray->monitor = monitor; ++ tray->height = height; ++ tray->spacing = spacing; ++ tray->scheme = colorscheme; ++ tray->cb = cb; ++ tray->watcher = watcher; ++ tray->font = font; ++ ++ return tray; ++ ++fail: ++ if (font) ++ fcft_destroy(font); ++ free(tray); ++ return NULL; ++} ++ ++int ++tray_get_width(const Tray *tray) ++{ ++ if (tray && tray->image) ++ return pixman_image_get_width(tray->image); ++ else ++ return 0; ++} ++ ++int ++tray_get_icon_width(const Tray *tray) ++{ ++ if (!tray) ++ return 0; ++ ++ return tray->height; ++} ++ ++void ++tray_rightclicked(Tray *tray, unsigned int index, const char **menucmd) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ menu_show(watcher->conn, watcher->loop, item->busname, ++ item->menu_busobj, menucmd); ++ return; ++ } ++ count++; ++ } ++} ++ ++void ++tray_leftclicked(Tray *tray, unsigned int index) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ item_activate(item); ++ return; ++ } ++ count++; ++ } ++} +diff --git a/systray/tray.h b/systray/tray.h +new file mode 100644 +index 0000000..af4e5e3 +--- /dev/null ++++ b/systray/tray.h +@@ -0,0 +1,37 @@ ++#ifndef TRAY_H ++#define TRAY_H ++ ++#include "watcher.h" ++ ++#include ++#include ++ ++#include ++ ++typedef void (*TrayNotifyCb)(void *data); ++ ++typedef struct { ++ pixman_image_t *image; ++ struct fcft_font *font; ++ uint32_t *scheme; ++ TrayNotifyCb cb; ++ Watcher *watcher; ++ void *monitor; ++ int height; ++ int spacing; ++ ++ struct wl_list link; ++} Tray; ++ ++Tray *createtray (void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher); ++void destroytray (Tray *tray); ++ ++int tray_get_width (const Tray *tray); ++int tray_get_icon_width (const Tray *tray); ++void tray_update (Tray *tray); ++void tray_leftclicked (Tray *tray, unsigned int index); ++void tray_rightclicked (Tray *tray, unsigned int index, const char **menucmd); ++ ++#endif /* TRAY_H */ +diff --git a/systray/watcher.c b/systray/watcher.c +new file mode 100644 +index 0000000..8dd84b9 +--- /dev/null ++++ b/systray/watcher.c +@@ -0,0 +1,551 @@ ++#include "watcher.h" ++ ++#include "item.h" ++#include "tray.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++static const char *const match_rule = ++ "type='signal'," ++ "interface='" DBUS_INTERFACE_DBUS ++ "'," ++ "member='NameOwnerChanged'"; ++ ++static const char *const snw_xml = ++ "\n" ++ "\n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ "\n"; ++ ++static void ++unregister_item(Watcher *watcher, Item *item) ++{ ++ wl_list_remove(&item->link); ++ destroyitem(item); ++ ++ watcher_update_trays(watcher); ++} ++ ++static Item * ++item_name_to_ptr(const Watcher *watcher, const char *busname) ++{ ++ Item *item; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!item || !item->busname) ++ return NULL; ++ if (strcmp(item->busname, busname) == 0) ++ return item; ++ } ++ ++ return NULL; ++} ++ ++static DBusHandlerResult ++handle_nameowner_changed(Watcher *watcher, DBusConnection *conn, ++ DBusMessage *msg) ++{ ++ char *name, *old_owner, *new_owner; ++ Item *item; ++ ++ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, ++ DBUS_TYPE_STRING, &old_owner, ++ DBUS_TYPE_STRING, &new_owner, ++ DBUS_TYPE_INVALID)) { ++ return DBUS_HANDLER_RESULT_HANDLED; ++ } ++ ++ if (*new_owner != '\0' || *name == '\0') ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ item = item_name_to_ptr(watcher, name); ++ if (!item) ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ unregister_item(watcher, item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, ++ "NameOwnerChanged")) ++ return handle_nameowner_changed(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static DBusHandlerResult ++respond_register_item(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED; ++ ++ DBusMessage *reply = NULL; ++ Item *item; ++ const char *sender, *param, *busobj, *registree_name; ++ ++ if (!(sender = dbus_message_get_sender(msg)) || ++ !dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, ¶m, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, ++ "Malformed message"); ++ goto send; ++ } ++ ++ switch (*param) { ++ case '/': ++ registree_name = sender; ++ busobj = param; ++ break; ++ case ':': ++ registree_name = param; ++ busobj = SNI_OPATH; ++ break; ++ default: ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Bad argument: \"%s\"", ++ param); ++ goto send; ++ } ++ ++ if (*registree_name != ':' || ++ !dbus_validate_bus_name(registree_name, NULL)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Invalid busname %s", ++ registree_name); ++ goto send; ++ } ++ ++ if (item_name_to_ptr(watcher, registree_name)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "%s already tracked", ++ registree_name); ++ goto send; ++ } ++ ++ item = createitem(registree_name, busobj, watcher); ++ wl_list_insert(&watcher->items, &item->link); ++ watcher_update_trays(watcher); ++ ++ reply = dbus_message_new_method_return(msg); ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ res = DBUS_HANDLER_RESULT_NEED_MEMORY; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return res; ++} ++ ++static int ++get_registered_items(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter names = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ Item *item; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &names)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!dbus_message_iter_append_basic(&names, DBUS_TYPE_STRING, ++ &item->busname)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ } ++ ++ dbus_message_iter_close_container(iter, &names); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &names); ++ return r; ++} ++ ++static int ++get_registered_items_variant(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", ++ &variant) || ++ get_registered_items(watcher, &variant) < 0) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_isregistered(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_bool_t is_registered = TRUE; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_BOOLEAN_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, ++ &is_registered)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_version(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_int32_t protovers = 0; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_INT32_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32, ++ &protovers)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static DBusHandlerResult ++respond_get_prop(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *iface, *prop; ++ ++ if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, err.name, err.message); ++ dbus_error_free(&err); ++ goto send; ++ } ++ ++ if (strcmp(iface, SNW_IFACE) != 0) { ++ reply = dbus_message_new_error_printf( ++ msg, DBUS_ERROR_UNKNOWN_INTERFACE, ++ "Unknown interface \"%s\"", iface); ++ goto send; ++ } ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (strcmp(prop, "ProtocolVersion") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_version(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "IsStatusNotifierHostRegistered") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_isregistered(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "RegisteredStatusNotifierItems") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_registered_items_variant(watcher, &iter) < 0) ++ goto fail; ++ ++ } else { ++ dbus_message_unref(reply); ++ reply = dbus_message_new_error_printf( ++ reply, DBUS_ERROR_UNKNOWN_PROPERTY, ++ "Property \"%s\" does not exist", prop); ++ } ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ goto fail; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_all_props(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ DBusMessageIter array = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter dict = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *prop; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ dbus_message_iter_init_append(reply, &iter); ++ ++ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", ++ &array)) ++ goto fail; ++ ++ prop = "ProtocolVersion"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_version(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "IsStatusNotifierHostRegistered"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_isregistered(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "RegisteredStatusNotifierItems"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_registered_items_variant(watcher, &dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ if (!dbus_message_iter_close_container(&iter, &array) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&array, &dict); ++ dbus_message_iter_abandon_container_if_open(&iter, &array); ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_introspect(DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &snw_xml, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++snw_message_handler(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, ++ "Introspect")) ++ return respond_introspect(conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "GetAll")) ++ return respond_all_props(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "Get")) ++ return respond_get_prop(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, SNW_IFACE, ++ "RegisterStatusNotifierItem")) ++ return respond_register_item(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static const DBusObjectPathVTable snw_vtable = { .message_function = ++ snw_message_handler }; ++ ++void ++watcher_start(Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ int r, flags; ++ ++ wl_list_init(&watcher->items); ++ wl_list_init(&watcher->trays); ++ watcher->conn = conn; ++ watcher->loop = loop; ++ ++ flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; ++ r = dbus_bus_request_name(conn, SNW_NAME, ++ flags, NULL); ++ if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) ++ goto fail; ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, watcher, NULL)) { ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ dbus_bus_add_match(conn, match_rule, &err); ++ if (dbus_error_is_set(&err)) { ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ if (!dbus_connection_register_object_path(conn, SNW_OPATH, &snw_vtable, ++ watcher)) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ watcher->running = 1; ++ return; ++ ++fail: ++ fprintf(stderr, "Couldn't start watcher, systray not available\n"); ++ dbus_error_free(&err); ++ return; ++} ++ ++void ++watcher_stop(Watcher *watcher) ++{ ++ dbus_connection_unregister_object_path(watcher->conn, SNW_OPATH); ++ dbus_bus_remove_match(watcher->conn, match_rule, NULL); ++ dbus_connection_remove_filter(watcher->conn, filter_bus, watcher); ++ dbus_bus_release_name(watcher->conn, SNW_NAME, NULL); ++ watcher->running = 0; ++} ++ ++int ++watcher_get_n_items(const Watcher *watcher) ++{ ++ return wl_list_length(&watcher->items); ++} ++ ++void ++watcher_update_trays(Watcher *watcher) ++{ ++ Tray *tray; ++ ++ wl_list_for_each(tray, &watcher->trays, link) ++ tray_update(tray); ++} +diff --git a/systray/watcher.h b/systray/watcher.h +new file mode 100644 +index 0000000..127eb64 +--- /dev/null ++++ b/systray/watcher.h +@@ -0,0 +1,35 @@ ++#ifndef WATCHER_H ++#define WATCHER_H ++ ++#include ++#include ++#include ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierWatcher"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierWatcher" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ */ ++#define SNW_NAME "org.kde.StatusNotifierWatcher" ++#define SNW_OPATH "/StatusNotifierWatcher" ++#define SNW_IFACE "org.kde.StatusNotifierWatcher" ++ ++typedef struct { ++ struct wl_list items; ++ struct wl_list trays; ++ struct wl_event_loop *loop; ++ DBusConnection *conn; ++ int running; ++} Watcher; ++ ++void watcher_start (Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop); ++void watcher_stop (Watcher *watcher); ++ ++int watcher_get_n_items (const Watcher *watcher); ++void watcher_update_trays (Watcher *watcher); ++ ++#endif /* WATCHER_H */ +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/bar/README.md b/dwl-bak/dwl-patches/patches/bar/README.md new file mode 100644 index 0000000..4955753 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar/README.md @@ -0,0 +1,34 @@ +### Description + +Add a bar identical to dwm's bar. + +To use a status-bar, you can pass in status text via stdin: +``` +slstatus -s | dwl +``` + +### Dependencies +* tllist (build dependency, required & pulled automatically by fcft) +* fcft +* pixman + +### Download +- [main 2025-07-29](/dwl/dwl-patches/raw/branch/main/patches/bar/bar.patch) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar/bar-0.7.patch) +- [0.6](/dwl/dwl-patches/raw/branch/main/patches/bar/bar-0.6.patch) + +It is required to remove, regenerate or update `config.h` after applying the patch, +since it makes changes to the configuration structure. +For example, in the `pertag` patch, `TAGCOUNT` must be replaced with `LENGTH(tags)`. + +Below is a preview of the patch. + +![bar patch preview](bar.png) + +### Authors +- [sewn](https://codeberg.org/sewn) + +### Credits +- [MadcowOG](https://github.com/MadcowOG) +- [kolumni](https://github.com/kolunmi/dwlb) + diff --git a/dwl-bak/dwl-patches/patches/bar/bar-0.6.patch b/dwl-bak/dwl-patches/patches/bar/bar-0.6.patch new file mode 100644 index 0000000..70c9f12 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar/bar-0.6.patch @@ -0,0 +1,1245 @@ +From 85e40afc2ad8acba453ce8c57233542e340c1c2b Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:42:04 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 31 +++- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 442 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 691 insertions(+), 95 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 0d651e7..2a11396 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5d1dc2b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..b06a736 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 145fd01..ab1bc31 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -57,6 +58,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +69,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -75,14 +78,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -96,6 +102,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -185,10 +192,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -196,6 +212,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -207,6 +228,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -249,6 +273,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +313,8 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -309,7 +342,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +363,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +376,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -408,6 +443,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -548,6 +592,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -590,17 +639,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WLR_BUTTON_PRESSED: + cursor_mode = CurPressed; +@@ -608,17 +742,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -689,6 +840,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -704,6 +857,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -714,6 +873,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -743,7 +903,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -980,8 +1140,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1312,6 +1479,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1347,13 +1588,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1364,12 +1605,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1666,7 +1906,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1689,7 +1929,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -1982,46 +2222,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- const char *appid, *title; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- title = client_get_title(c); +- appid = client_get_appid(c); +- printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +- printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} + + void + powermgrsetmode(struct wl_listener *listener, void *data) +@@ -2175,30 +2375,17 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + +- /* Mark stdout as non-blocking to avoid people who does not close stdin +- * nor consumes it in their startup script getting dwl frozen */ +- if (fd_set_nonblock(STDOUT_FILENO) < 0) +- close(STDOUT_FILENO); +- +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2262,7 +2449,7 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2285,7 +2472,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2310,7 +2497,7 @@ setlayout(const Arg *arg) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2383,6 +2570,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2569,6 +2757,11 @@ setup(void) + + wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2593,6 +2786,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2611,6 +2805,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[1024]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2621,7 +2839,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2666,6 +2884,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2694,7 +2920,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2707,7 +2933,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2755,7 +2981,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2855,6 +3081,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2865,12 +3098,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2883,10 +3149,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2899,7 +3165,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2940,6 +3206,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -2948,9 +3215,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3089,10 +3359,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/bar/bar-0.7.patch b/dwl-bak/dwl-patches/patches/bar/bar-0.7.patch new file mode 100644 index 0000000..523ca36 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar/bar-0.7.patch @@ -0,0 +1,1245 @@ +From 1431cf1e9e03c8e59050af3b37514a6a2293d71d Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:42:04 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 31 +++- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 442 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 691 insertions(+), 95 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 3358bae..9bc67db 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5d1dc2b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..b06a736 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index a2711f6..ece537a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +59,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -68,6 +70,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -76,14 +79,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -97,6 +103,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -186,10 +193,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -197,6 +213,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -208,6 +229,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -250,6 +274,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -285,6 +316,8 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -313,7 +346,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -335,9 +367,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -346,6 +380,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -413,6 +448,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -553,6 +597,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -595,17 +644,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -613,17 +747,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -697,6 +848,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -712,6 +865,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -722,6 +881,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -751,7 +911,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1022,8 +1182,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1336,6 +1503,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1371,13 +1612,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1388,12 +1629,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1715,7 +1955,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1738,7 +1978,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2032,46 +2272,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- const char *appid, *title; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- title = client_get_title(c); +- appid = client_get_appid(c); +- printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +- printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} + + void + powermgrsetmode(struct wl_listener *listener, void *data) +@@ -2226,30 +2426,17 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + +- /* Mark stdout as non-blocking to avoid people who does not close stdin +- * nor consumes it in their startup script getting dwl frozen */ +- if (fd_set_nonblock(STDOUT_FILENO) < 0) +- close(STDOUT_FILENO); +- +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2315,7 +2502,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2338,7 +2525,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2363,7 +2550,7 @@ setlayout(const Arg *arg) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2436,6 +2623,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2625,6 +2813,11 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2649,6 +2842,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2667,6 +2861,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[1024]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2677,7 +2895,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2722,6 +2940,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2750,7 +2976,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2763,7 +2989,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2811,7 +3037,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2911,6 +3137,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2921,12 +3154,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2939,10 +3205,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2955,7 +3221,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2996,6 +3262,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3004,9 +3271,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3145,10 +3415,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/bar/bar.patch b/dwl-bak/dwl-patches/patches/bar/bar.patch new file mode 100644 index 0000000..c9527c1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bar/bar.patch @@ -0,0 +1,1266 @@ +From 8906a73dbc8996dd1bfff15f5b26aaee6d45fd61 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Tue, 29 Jul 2025 15:21:19 +0300 +Subject: [PATCH] Implement dwm bar clone + +Signed-off-by: sewn +--- + Makefile | 2 +- + config.def.h | 33 ++-- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 441 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 695 insertions(+), 92 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 95c2afa..1b7472d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + +-/* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++/* tagging */ ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..90cc35b +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2025 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 12f441e..bf340d8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +60,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +71,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -77,14 +80,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + + typedef union { + int i; +@@ -94,6 +100,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -183,10 +190,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -194,6 +210,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -205,6 +226,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -247,6 +271,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +313,8 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -310,7 +343,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +363,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +376,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -406,6 +441,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -521,7 +565,7 @@ arrange(Monitor *m) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ +@@ -576,6 +620,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -618,17 +667,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -636,17 +770,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -721,6 +872,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -736,6 +889,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -748,6 +907,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -814,7 +974,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1066,7 +1226,7 @@ createmon(struct wl_listener *listener, void *data) + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; +@@ -1088,8 +1248,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1399,6 +1566,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1433,13 +1674,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1450,12 +1691,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1768,7 +2008,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1791,7 +2031,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2084,44 +2324,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} +- + void + powermgrsetmode(struct wl_listener *listener, void *data) + { +@@ -2250,22 +2452,14 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + + /* Mark stdout as non-blocking to avoid the startup script +@@ -2275,7 +2469,7 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2341,7 +2535,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2364,7 +2558,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2376,9 +2570,9 @@ setlayout(const Arg *arg) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; +- strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2451,6 +2645,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2645,6 +2840,11 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2669,6 +2869,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2687,6 +2888,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[256]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2697,7 +2922,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2742,6 +2967,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2770,7 +3003,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2783,7 +3016,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2831,7 +3064,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2931,6 +3164,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2941,12 +3181,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2959,10 +3232,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2975,7 +3248,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -3016,6 +3289,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3024,9 +3298,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3159,10 +3436,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.50.0 + diff --git a/dwl-bak/dwl-patches/patches/bar/bar.png b/dwl-bak/dwl-patches/patches/bar/bar.png new file mode 100644 index 0000000..276256b Binary files /dev/null and b/dwl-bak/dwl-patches/patches/bar/bar.png differ diff --git a/dwl-bak/dwl-patches/patches/barborder/README.md b/dwl-bak/dwl-patches/patches/barborder/README.md new file mode 100644 index 0000000..6eef3ba --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barborder/README.md @@ -0,0 +1,10 @@ +### Description + +Add a border around the [bar](/dwl/dwl-patches/wiki/bar) similar to how a client is given a border. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barborder/barborder.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl-bak/dwl-patches/patches/barborder/barborder.patch b/dwl-bak/dwl-patches/patches/barborder/barborder.patch new file mode 100644 index 0000000..106a2ac --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barborder/barborder.patch @@ -0,0 +1,136 @@ +From 7d95ce0fba8f172748bbd71b4c03ce12acd54eea Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 14:11:37 +0300 +Subject: [PATCH] add border to bar + +--- + config.def.h | 3 ++- + dwl.c | 32 +++++++++++++++++++------------- + 2 files changed, 21 insertions(+), 14 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..4763482 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +-static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int borderpx = 1; /* border pixel of windows & bar */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; +@@ -18,6 +18,7 @@ static uint32_t colors[][3] = { + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, ++ [SchemeBar] = { 0, 0, 0x557700ff }, + }; + + /* tagging - TAGCOUNT must be no greater than 31 */ +diff --git a/dwl.c b/dwl.c +index ece537a..c637da4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -85,7 +85,7 @@ + #define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeBar }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +@@ -750,7 +750,7 @@ buttonpress(struct wl_listener *listener, void *data) + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { +- cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ cx = (cursor->x - selmon->m.x - borderpx) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -1506,10 +1506,12 @@ dirtomon(enum wlr_direction dir) + void + drawbar(Monitor *m) + { +- int x, w, tw = 0; ++ int x, y = borderpx, w, tw = 0; ++ int mh = m->b.height - borderpx * 2, mw = m->b.width - borderpx * 2; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; ++ uint32_t borderscm[] = { colors[SchemeBar][ColBorder] }; + Client *c; + Buffer *buf; + +@@ -1518,11 +1520,15 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ drwl_setscheme(m->drw, borderscm); ++ drwl_rect(m->drw, 0, 0, m->b.width, m->b.height, 1, 0); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ drwl_text(m->drw, borderpx + mw - tw, y, tw, mh, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { +@@ -1532,31 +1538,31 @@ drawbar(Monitor *m) + if (c->isurgent) + urg |= c->tags; + } +- x = 0; ++ x = borderpx; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ drwl_rect(m->drw, x + boxs, y + boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); +- x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ x = drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->b.width - tw - x) > m->b.height) { ++ if ((w = mw - tw - x + borderpx) > mh) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ drwl_rect(m->drw, x + boxs, y + boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); +- drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ drwl_rect(m->drw, x, y, w, mh, 1, 1); + } + } + +@@ -3183,7 +3189,7 @@ updatebar(Monitor *m) + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; +- m->b.height = m->drw->font->height + 2; ++ m->b.height = m->drw->font->height + 2 + borderpx * 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); + } + +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/barcolors/README.md b/dwl-bak/dwl-patches/patches/barcolors/README.md new file mode 100644 index 0000000..640961b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barcolors/README.md @@ -0,0 +1,9 @@ +### Description +Add support for colored status text to the [bar](/dwl/dwl-patches/src/branch/main/patches/bar). Text can be colored in the same manner as with dwlb, namely by wrapping it between `^fg(color)` and `^fg()` or `^bg(color)` and `^bg()`, where `color` is a 6-digit hexadecimal value. + +### Download +- [git branch](/kerberoge/dwl/src/branch/barcolors) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barcolors/barcolors.patch) + +### Authors +- [kerberoge](https://codeberg.org/kerberoge) diff --git a/dwl-bak/dwl-patches/patches/barcolors/barcolors.patch b/dwl-bak/dwl-patches/patches/barcolors/barcolors.patch new file mode 100644 index 0000000..b95f78e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barcolors/barcolors.patch @@ -0,0 +1,144 @@ +From d2b529d9ebee6b2e625afd5c89c2ede5bb0ca91b Mon Sep 17 00:00:00 2001 +From: Kerberoge +Date: Sun, 25 Aug 2024 22:41:55 +0200 +Subject: [PATCH 1/1] updated barcolors + +--- + dwl.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 87 insertions(+), 6 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ece537a..6663399 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -83,6 +83,7 @@ + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + #define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) ++#define PREFIX(str, prefix) !strncmp(str, prefix, strlen(prefix)) + + /* enums */ + enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +@@ -318,6 +319,7 @@ static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void drawbar(Monitor *m); + static void drawbars(void); ++static int drawstatus(Monitor *m); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -448,7 +450,7 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + +-static char stext[256]; ++static char stext[512]; + static struct wl_event_source *status_event_source; + + static const struct wlr_buffer_impl buffer_impl = { +@@ -1519,11 +1521,8 @@ drawbar(Monitor *m) + return; + + /* draw status first so it can be overdrawn by tags later */ +- if (m == selmon) { /* status is only drawn on selected monitor */ +- drwl_setscheme(m->drw, colors[SchemeNorm]); +- tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); +- } ++ if (m == selmon) /* status is only drawn on selected monitor */ ++ tw = drawstatus(m); + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) +@@ -1577,6 +1576,88 @@ drawbars(void) + drawbar(m); + } + ++int ++drawstatus(Monitor *m) ++{ ++ int x, tw, iw; ++ char rstext[512] = ""; ++ char *p, *argstart, *argend, *itext; ++ uint32_t scheme[3], *color; ++ ++ /* calculate real width of stext */ ++ for (p = stext; *p; p++) { ++ if (PREFIX(p, "^^")) { ++ strncat(rstext, p, 2); ++ p++; ++ } else if (PREFIX(p, "^fg(") || PREFIX(p, "^bg(")) { ++ argend = strchr(p, ')'); ++ if (!argend) { /* ignore this command */ ++ argstart = strchr(p, '(') + 1; ++ strncat(rstext, p, argstart - p); ++ p = argstart - 1; ++ } else { ++ p = argend; ++ } ++ } else { ++ strncat(rstext, p, 1); ++ } ++ } ++ tw = TEXTW(m, rstext) - m->lrpad; ++ ++ x = m->b.width - tw; ++ itext = stext; ++ scheme[0] = colors[SchemeNorm][0]; ++ scheme[1] = colors[SchemeNorm][1]; ++ drwl_setscheme(m->drw, scheme); ++ for (p = stext; *p; p++) { ++ if (PREFIX(p, "^^")) { ++ p++; ++ } else if (PREFIX(p, "^fg(") || PREFIX(p, "^bg(")) { ++ argstart = strchr(p, '(') + 1; ++ argend = strchr(argstart, ')'); ++ if (!argend) { /* ignore this command */ ++ p = argstart - 1; ++ continue; ++ } ++ ++ *p = '\0'; ++ iw = TEXTW(m, itext) - m->lrpad; ++ if (*itext) /* only draw text if there is something to draw */ ++ x = drwl_text(m->drw, x, 0, iw, m->b.height, 0, itext, 0); ++ *p = '^'; ++ ++ if (PREFIX(p, "^fg(")) ++ color = &scheme[0]; ++ else ++ color = &scheme[1]; ++ ++ if (argend != argstart) { ++ *argend = '\0'; ++ *color = strtoul(argstart, NULL, 16); ++ *color = *color << 8 | 0xff; /* add alpha channel */ ++ *argend = ')'; ++ } else { ++ *color = 0; /* reset */ ++ } ++ ++ /* reset color back to normal if none was provided */ ++ if (!scheme[0]) ++ scheme[0] = colors[SchemeNorm][0]; ++ if (!scheme[1]) ++ scheme[1] = colors[SchemeNorm][1]; ++ ++ itext = argend + 1; ++ drwl_setscheme(m->drw, scheme); ++ p = argend; ++ } ++ } ++ iw = TEXTW(m, itext) - m->lrpad; ++ if (*itext) ++ drwl_text(m->drw, x, 0, iw, m->b.height, 0, itext, 0); ++ ++ return tw; ++} ++ + void + focusclient(Client *c, int lift) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/barpadding/README.md b/dwl-bak/dwl-patches/patches/barpadding/README.md new file mode 100644 index 0000000..1d0f17b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barpadding/README.md @@ -0,0 +1,10 @@ +### Description + +Add vertical and horizontal space between the [bar](/dwl/dwl-patches/wiki/bar) and the edge of the screen. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barpadding/barpadding.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl-bak/dwl-patches/patches/barpadding/barpadding.patch b/dwl-bak/dwl-patches/patches/barpadding/barpadding.patch new file mode 100644 index 0000000..e5cc185 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/barpadding/barpadding.patch @@ -0,0 +1,73 @@ +From f01cea73042155e856b2f41452724fe5c895eee4 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:59:03 +0300 +Subject: [PATCH] add vertical and horizontal spacing to bar + +https://dwm.suckless.org/patches/barpadding/ +--- + config.def.h | 2 ++ + dwl.c | 14 +++++++------- + 2 files changed, 9 insertions(+), 7 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..756b1ae 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -9,6 +9,8 @@ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ ++static const int vertpad = 10; /* vertical padding of bar */ ++static const int sidepad = 10; /* horizontal padding of bar */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index ece537a..380549a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -598,8 +598,8 @@ arrangelayers(Monitor *m) + return; + + if (m->scene_buffer->node.enabled) { +- usable_area.height -= m->b.real_height; +- usable_area.y += topbar ? m->b.real_height : 0; ++ usable_area.height -= m->b.real_height + vertpad; ++ usable_area.y += topbar ? m->b.real_height + vertpad : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ +@@ -750,7 +750,7 @@ buttonpress(struct wl_listener *listener, void *data) + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { +- cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ cx = (cursor->x - selmon->m.x - sidepad) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -1562,8 +1562,8 @@ drawbar(Monitor *m) + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); +- wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, +- m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x + sidepad, ++ m->m.y + (topbar ? vertpad : m->m.height - m->b.real_height - vertpad)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); + } +@@ -3162,8 +3162,8 @@ updatebar(Monitor *m) + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); +- m->b.width = rw; +- m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ m->b.width = rw - (2 * sidepad); ++ m->b.real_width = (int)((float)rw / m->wlr_output->scale) - (2 * sidepad); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/bartruecenteredtitle/README.md b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/README.md new file mode 100644 index 0000000..94bfcd9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/README.md @@ -0,0 +1,11 @@ +### Description +A homegrown port of dwm's _truecenteredtitle_ patch, with the addition of a config option to toggle its effects.
Requires [the bar patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar) to be applied beforehand. + +![](centeredtitle.webp) + +### Download +- [v0.7/v0.6](/dwl/dwl-patches/raw/branch/main/patches/bartruecenteredtitle/bartruecenteredtitle.patch)
Works on both v0.7 and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) +- [sewn](https://codeberg.org/sewn) diff --git a/dwl-bak/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch new file mode 100644 index 0000000..ce02a51 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch @@ -0,0 +1,46 @@ +From 17501c9f28226b1f332d6842be0d7f50ba618a29 Mon Sep 17 00:00:00 2001 +From: moonsabre +Date: Fri, 14 Mar 2025 16:04:25 -0700 +Subject: [PATCH] Bar title centering parameter. + +--- + config.def.h | 1 + + dwl.c | 9 +++++++-- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..8ac3a8b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -9,6 +9,7 @@ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ ++static const int centeredtitle = 1; /* 1 means centered title */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index ece537a..9eb816b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1551,9 +1551,14 @@ drawbar(Monitor *m) + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ tw = TEXTW(selmon, client_get_title(c)); ++ drwl_text(m->drw, x, 0, w, m->b.height, ++ !centeredtitle || tw > w ? m->lrpad / 2 : (w - tw) / 2, ++ client_get_title(c), 0); + if (c && c->isfloating) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ drwl_rect(m->drw, ++ !centeredtitle || tw > w ? x + boxs : x + ((w - tw) / 2 - boxs * 8), ++ boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp new file mode 100644 index 0000000..b1ad696 Binary files /dev/null and b/dwl-bak/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp differ diff --git a/dwl-bak/dwl-patches/patches/better-resize/README.md b/dwl-bak/dwl-patches/patches/better-resize/README.md new file mode 100644 index 0000000..9c43862 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/better-resize/README.md @@ -0,0 +1,48 @@ +### Description +This patch allows you to configure window resizing more flexibly. +It introduces three options with the following possible values: + +- warp_cursor: +``` + 0 - the mouse cursor remains in its original position at the start of the resize. + 1 - the cursor is automatically warped to the selected corner before resizing begins. +``` + +- lock_cursor: +``` + 0 - the cursor can move freely during the resize. + 1 - the cursor position is completely frozen for the entire duration of the resize. +``` + +- resize_corner: +``` + 0: top-left + 1: top-right + 2: bottom-left + 3: bottom-right + 4: selects the corner based on the current mouse quadrant +``` + +### Demos +All demos below use resize_corner = 4: +| no warp + no lock | warp + lock | +|-|-| +| ![demo-nowarp-nolock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-nowarp-nolock.gif?raw=true) | ![demo-warp-lock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-warp-lock.gif?raw=true) | + +| no warp + lock | warp + no lock | +|-|-| +| ![demo-nowarp-lock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-nowarp-lock.gif?raw=true) | ![demo-warp-nolock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-warp-nolock.gif?raw=true) | + +### Known Issues (warp + lock) +The combination of warp_cursor and lock_cursor is not recommended without outer gaps. +If the selected resize corner aligns exactly with a screen corner, the cursor gets locked there and cannot be moved outward, so resizing only works inward. +To resize outward, you must restart the resize operation with the cursor positioned somewhere away from the screen corner. +This happens because the locked cursor cannot move past the screen edge, and therefore cannot generate a non-zero delta to resize outward. + +On multihead setups, if the resize corner is near another screen, the window may switch monitors upon completion of the resize. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/better-resize/better-resize-0.7.patch) + +### Authors +- [mmistika](https://codeberg.org/mmistika) diff --git a/dwl-bak/dwl-patches/patches/better-resize/better-resize-0.7.patch b/dwl-bak/dwl-patches/patches/better-resize/better-resize-0.7.patch new file mode 100644 index 0000000..1269800 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/better-resize/better-resize-0.7.patch @@ -0,0 +1,107 @@ +From f109808140cd6323b3a100663a10e048ae32e3a0 Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Thu, 17 Jul 2025 11:59:18 +0200 +Subject: [PATCH] Add configurable window resize + +Signed-off-by: mmistika +--- + config.def.h | 12 ++++++++++++ + dwl.c | 45 +++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 49 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..e404549 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* window resizing */ ++/* resize_corner: ++ * 0: top-left ++ * 1: top-right ++ * 2: bottom-left ++ * 3: bottom-right ++ * 4: closest to the cursor ++ */ ++static const int resize_corner = 4; ++static const int warp_cursor = 1; /* 1: warp to corner, 0: don’t warp */ ++static const int lock_cursor = 0; /* 1: lock cursor, 0: don't lock */ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index c717c1d..aacd074 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -407,6 +407,7 @@ static KeyboardGroup *kb_group; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ ++static int rzcorner; + + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; +@@ -1873,8 +1874,24 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ int cdx = (int)round(cursor->x) - grabcx; ++ int cdy = (int)round(cursor->y) - grabcy; ++ ++ const struct wlr_box box = { ++ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), ++ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), ++ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), ++ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy) ++ }; ++ resize(grabc, box, 1); ++ ++ if (!lock_cursor) { ++ grabcx += cdx; ++ grabcy += cdy; ++ } else { ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ + return; + } + +@@ -1920,12 +1937,24 @@ moveresize(const Arg *arg) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ const char *cursors[] = { "nw-resize", "ne-resize", "sw-resize", "se-resize" }; ++ ++ rzcorner = resize_corner; ++ grabcx = (int)round(cursor->x); ++ grabcy = (int)round(cursor->y); ++ ++ if (rzcorner == 4) ++ /* identify the closest corner index */ ++ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 : 1) ++ + (grabcy - grabc->geom.y < grabc->geom.y + grabc->geom.height - grabcy ? 0 : 2); ++ ++ if (warp_cursor) { ++ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; ++ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height : grabc->geom.y; ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]); + break; + } + } +-- +2.50.1 + diff --git a/dwl-bak/dwl-patches/patches/borders/README.md b/dwl-bak/dwl-patches/patches/borders/README.md new file mode 100644 index 0000000..b046307 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/borders/README.md @@ -0,0 +1,37 @@ +### Description +Adds 2 more borders to each side (top, bottom, left, right) of every window. + + + +
+Preview +
+With the following config:
+
+```c
+static const unsigned int borderpx         = 9;  /* border pixel of windows */
+static const unsigned int borderspx        = 3;  /* width of the border that start from outside the windows */
+static const unsigned int borderepx        = 3;  /* width of the border that start from inside the windows */
+```
+
+and `border_color_type` set to `BrdOriginal`:
+
+
+and `border_color_type` set to `BrdStart`:
+
+
+and `border_color_type` set to `BrdEnd`:
+
+
+and `border_color_type` set to `BrdStartEnd`:
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/borders) +- [2024-06-04](https://codeberg.org/dwl/dwl-patches/raw/commit/1a6825f2b8cd23044312c8040d0bf63ee7f85bc5/patches/borders/borders.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/borders/borders.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/borders/borders.patch b/dwl-bak/dwl-patches/patches/borders/borders.patch new file mode 100644 index 0000000..c8cc847 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/borders/borders.patch @@ -0,0 +1,213 @@ +From b12cfff672f0705d8259cf26b3a574faa5ca43ae Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 4 Jun 2024 16:02:25 -0500 +Subject: [PATCH] implement borders patch + +tihs patch adds 2 extra borders relative to the client, they don't +change the size of the client +--- + client.h | 16 +++++++++++++--- + config.def.h | 8 ++++++++ + dwl.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++---- + 3 files changed, 70 insertions(+), 7 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..33fd579 100644 +--- a/client.h ++++ b/client.h +@@ -325,11 +325,21 @@ client_send_close(Client *c) + } + + static inline void +-client_set_border_color(Client *c, const float color[static 4]) ++client_set_border_color(Client *c, const float color[static 4], const float colors[static 4], const float colore[static 4]) + { + int i; +- for (i = 0; i < 4; i++) +- wlr_scene_rect_set_color(c->border[i], color); ++ for (i = 0; i < 4; i++) { ++ if (border_color_type == BrdOriginal) { ++ wlr_scene_rect_set_color(c->border[i], color); ++ } else if (border_color_type == BrdStart) { ++ wlr_scene_rect_set_color(c->borders[i], colors); ++ } else if (border_color_type == BrdEnd) { ++ wlr_scene_rect_set_color(c->bordere[i], colore); ++ } else if (border_color_type == BrdStartEnd) { ++ wlr_scene_rect_set_color(c->borders[i], colors); ++ wlr_scene_rect_set_color(c->bordere[i], colore); ++ } ++ } + } + + static inline void +diff --git a/config.def.h b/config.def.h +index 8847e58..2d6bbe5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,8 +7,16 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int borderspx = 0; /* width of the border that start from outside the windows */ ++static const unsigned int borderepx = 0; /* width of the border that start from inside the windows */ ++static const unsigned int borderspx_offset = 0; /* offset of the border that start from outside the windows */ ++static const unsigned int borderepx_negative_offset = 0; /* offset of the border that start from inside the windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); ++static const float borderscolor[] = COLOR(0x444444ff); /* color of the border that start from outside the windows */ ++static const float borderecolor[] = COLOR(0x444444ff); /* color of the border that start from inside the windows */ ++static const int border_color_type = BrdOriginal; /* borders to be colored (focuscolor, urgentcolor) */ ++static const int borders_only_floating = 0; + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index bf763df..303832a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -86,6 +86,7 @@ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif ++enum { BrdOriginal, BrdStart, BrdEnd, BrdStartEnd }; + + typedef union { + int i; +@@ -109,6 +110,8 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *borders[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *bordere[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -136,6 +139,8 @@ typedef struct { + struct wl_listener set_hints; + #endif + unsigned int bw; ++ unsigned int bws; ++ unsigned int bwe; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +@@ -973,6 +978,8 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->bws = borders_only_floating ? 0 : borderspx; ++ c->bwe = borders_only_floating ? 0 : borderepx; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -1268,7 +1275,7 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, focuscolor, focuscolor, focuscolor); + } + + /* Deactivate old client if focus is changing */ +@@ -1285,7 +1292,7 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, bordercolor, borderscolor, borderecolor); + + client_activate_surface(old, 0); + } +@@ -1597,6 +1604,12 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + c->isurgent ? urgentcolor : bordercolor); + c->border[i]->node.data = c; ++ ++ c->borders[i] = wlr_scene_rect_create(c->scene, 0, 0, borderscolor); ++ c->borders[i]->node.data = c; ++ ++ c->bordere[i] = wlr_scene_rect_create(c->scene, 0, 0, borderecolor); ++ c->bordere[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ +@@ -1618,6 +1631,12 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ ++ if (borders_only_floating) { ++ c->bws = c->isfloating ? borderspx : 0; ++ c->bwe = c->isfloating ? borderepx : 0; ++ } ++ + printstatus(); + + unset_fullscreen: +@@ -2051,6 +2070,24 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + ++ wlr_scene_rect_set_size(c->borders[0], c->geom.width - 2 * borderspx_offset, c->bws); ++ wlr_scene_rect_set_size(c->borders[1], c->geom.width - 2 * borderspx_offset, c->bws); ++ wlr_scene_rect_set_size(c->borders[2], c->bws, c->geom.height - 2 * c->bws - 2 * borderspx_offset); ++ wlr_scene_rect_set_size(c->borders[3], c->bws, c->geom.height - 2 * c->bws - 2 * borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[0]->node, borderspx_offset, borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[1]->node, borderspx_offset, c->geom.height - c->bws - borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[2]->node, borderspx_offset, c->bws + borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[3]->node, c->geom.width - c->bws - borderspx_offset, c->bws + borderspx_offset); ++ ++ wlr_scene_rect_set_size(c->bordere[0], c->geom.width - (c->bw - c->bwe) * 2 + borderepx_negative_offset * 2, c->bwe); ++ wlr_scene_rect_set_size(c->bordere[1], c->geom.width - (c->bw - c->bwe) * 2 + borderepx_negative_offset * 2, c->bwe); ++ wlr_scene_rect_set_size(c->bordere[2], c->bwe, c->geom.height - 2 * c->bw + 2 * borderepx_negative_offset); ++ wlr_scene_rect_set_size(c->bordere[3], c->bwe, c->geom.height - 2 * c->bw + 2 * borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[0]->node, c->bw - c->bwe - borderepx_negative_offset, c->bw - c->bwe - borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[1]->node, c->bw - c->bwe - borderepx_negative_offset, c->geom.height - c->bw + borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[2]->node, c->bw - c->bwe - borderepx_negative_offset, c->bw - borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[3]->node, c->geom.width - c->bw + borderepx_negative_offset, c->bw - borderepx_negative_offset); ++ + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); +@@ -2151,6 +2188,12 @@ setfloating(Client *c, int floating) + c->isfloating = floating; + if (!c->mon) + return; ++ ++ if (borders_only_floating) { ++ c->bws = c->isfloating ? borderspx : 0; ++ c->bwe = c->isfloating ? borderepx : 0; ++ } ++ + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); +@@ -2165,6 +2208,8 @@ setfullscreen(Client *c, int fullscreen) + if (!c->mon) + return; + c->bw = fullscreen ? 0 : borderpx; ++ c->bws = fullscreen ? 0 : borderspx; ++ c->bwe = fullscreen ? 0 : borderepx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); +@@ -2819,7 +2864,7 @@ urgent(struct wl_listener *listener, void *data) + printstatus(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, urgentcolor, urgentcolor, urgentcolor); + } + + void +@@ -3023,7 +3068,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, urgentcolor, urgentcolor, urgentcolor); + } + + void +-- +2.44.1 diff --git a/dwl-bak/dwl-patches/patches/bottomstack/README.md b/dwl-bak/dwl-patches/patches/bottomstack/README.md new file mode 100644 index 0000000..fa9ede6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bottomstack/README.md @@ -0,0 +1,27 @@ +### Description +bstack and bstackhoriz are two stack layouts for dwl. +### Scheme +``` +bstack (TTT) bstackhoriz (===) ++-----------------+ +-----------------+ +| | | | +| | | | +| | | | ++-----+-----+-----+ +-----------------+ +| | | | +-----------------+ +| | | | +-----------------+ ++-----+-----+-----+ +-----------------+ +``` + + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6-b/bottomstack) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/20de07dc8759200c8a4c9651475acb331d245890/patches/bottomstack/bottomstack.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/bottomstack/bottomstack.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/5368aa392c7ebf8d7d24c232b80cfae1be457d41/bottomstack/bottomstack.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [DanielMowitz](https://github.com/DanielMowitz) +- [Abanoub8](https://github.com/Abanoub8) + diff --git a/dwl-bak/dwl-patches/patches/bottomstack/bottomstack.patch b/dwl-bak/dwl-patches/patches/bottomstack/bottomstack.patch new file mode 100644 index 0000000..c2b9331 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/bottomstack/bottomstack.patch @@ -0,0 +1,140 @@ +From b352fb08f40b1ee2d8c4748be4922df711e3aaa9 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 10:44:29 -0500 +Subject: [PATCH] implement bottomstack + +--- + config.def.h | 4 +++ + dwl.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 88 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5aac3e9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,8 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "TTT", bstack }, ++ { "===", bstackhoriz }, + }; + + /* monitors */ +@@ -139,6 +141,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_u, setlayout, {.v = &layouts[3]} }, ++ { MODKEY, XKB_KEY_o, setlayout, {.v = &layouts[4]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index dc0437e..5648d5f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -57,6 +57,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -351,6 +352,8 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void bstack(Monitor *m); ++static void bstackhoriz(Monitor *m); + + /* variables */ + static const char broken[] = "broken"; +@@ -3160,3 +3163,84 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++static void ++bstack(Monitor *m) ++{ ++ int w, h, mh, mx, tx, ty, tw; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) { ++ mh = (int)round(m->nmaster ? m->mfact * m->w.height : 0); ++ tw = m->w.width / (n - m->nmaster); ++ ty = m->w.y + mh; ++ } else { ++ mh = m->w.height; ++ tw = m->w.width; ++ ty = m->w.y; ++ } ++ ++ i = mx = 0; ++ tx = m-> w.x; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating) ++ continue; ++ if (i < m->nmaster) { ++ w = (m->w.width - mx) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box) { .x = m->w.x + mx, .y = m->w.y, .width = w, .height = mh }, 0); ++ mx += c->geom.width; ++ } else { ++ h = m->w.height - mh; ++ resize(c, (struct wlr_box) { .x = tx, .y = ty, .width = tw, .height = h }, 0); ++ if (tw != m->w.width) ++ tx += c->geom.width; ++ } ++ i++; ++ } ++} ++ ++static void ++bstackhoriz(Monitor *m) { ++ int w, mh, mx, tx, ty, th; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n ++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) { ++ mh = (int)round(m->nmaster ? m->mfact * m->w.height : 0); ++ th = (m->w.height - mh) / (n - m->nmaster); ++ ty = m->w.y + mh; ++ } else { ++ th = mh = m->w.height; ++ ty = m->w.y; ++ } ++ ++ i = mx = 0; ++ tx = m-> w.x; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c,m) || c->isfloating) ++ continue; ++ if (i < m->nmaster) { ++ w = (m->w.width - mx) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box) { .x = m->w.x + mx, .y = m->w.y, .width = w, .height = mh }, 0); ++ mx += c->geom.width; ++ } else { ++ resize(c, (struct wlr_box) { .x = tx, .y = ty, .width = m->w.width, .height = th }, 0); ++ if (th != m->w.height) ++ ty += c->geom.height; ++ } ++ i++; ++ } ++} +-- +2.45.1 diff --git a/dwl-bak/dwl-patches/patches/btrtile/README.md b/dwl-bak/dwl-patches/patches/btrtile/README.md new file mode 100644 index 0000000..4766aa7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/btrtile/README.md @@ -0,0 +1,103 @@ +### Description + +# btrtile — A Focus-Driven Tiling Layout + +It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized. + +![btrtile in action](https://github.com/julmajustus/dwl-patches/blob/main/demos/btrtiledemo.gif?raw=true) + +--- + +# Why btrtile + +While dwl’s patches folder is full of different layouts, I couldn't find suitable layout that would work well with my workflow and single ultrawide monitor setup. btrtile aims to solve that by introducing a layout strategy that splits clients according to user focus and pointer position. + +--- + +# Features + +- **Combined Tiling and Management** + Combines tiling layout and management of clients under one patchset. + +- **Focus-Driven Splits** + When you add a new client, btrtile checks where your pointer is relative to the focused client’s geometry. + - If the pointer is on the left half (for a horizontally large client), the new client spawns on the left side, and vice versa. + - By default, new splits are 50/50. + +- **Adaptive Splitting** + - If the area to be split is wider than its height, btrtile does a vertical split. + - Otherwise, it does a horizontal split. + +- **Keyboard and Mouse Driven** + - Supports keyboard-based commands for quick ratio adjustments and client swapping. + - Mouse-based resizing and moving are integrated for more intuitive manipulation. + +--- + +# How It Works + +btrtile organizes clients using a binary tree data structure that represents splits either vertically or horizontally. + +When a new client appears: +1. **Focused Client Detection** + btrtile checks your pointer location to find which client (if any) you’re interacting with. +2. **Split Creation** + - If there’s a focused client, btrtile creates a split node around it, placing the new client on the side where your pointer is. + +3. **Ratio Management** + Each split node has a `split_ratio` (defaulting to 0.5). This ratio defines how much space is allocated to each child node. You can adjust this ratio using keyboard or mouse actions. + +--- + +# What It Doesn’t Handle + +- **Suckless philosophy** + - Yea, it's a bloat. I tried to hide the suck inside a single file as much I could. While this approach is not ideal, it's how it's at least for now. + +--- + +# Configuring btrtile + +btrtile adds couple variables to config.h to fine tune the mouse resizing of tiled clients. + +1. **resize_factor** + - A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensivity. + Defaults to 0.0002f. + +2. **resize_interval_ms** + - A time based resize call limiter. Depends on framerate and screen refresh rate. + Defaults to 16ms. (~60 resize updates per second) + +Fine tune these values to find the best values for your setup, smoother resizing can significally increase cpu overhead. +If mouse resizing feels sluggish, you can try compiling dwl with more aggressive optimization flags like -O2/-O3. + +--- + +# Patch recommendations + +1. **Patches that I use with my btrtile** + + - [focusdir](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/focusdir) + Great patch to move focus between clients. + + - [rotatetags](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/rotatetags) + Good patch to rotate the view or shift clients between tags. + + - [warpcursor](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/warpcursor) + Moves cursor location to focused client. + + - [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag) + Allows each tag to have individual layout setups. + + - [gaps](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) + Add gaps between clients. + +--- + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) +- [0.7 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gaps.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch b/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch new file mode 100644 index 0000000..90354d1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch @@ -0,0 +1,922 @@ +From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 13 Feb 2025 23:25:20 +0200 +Subject: [PATCH] btrtile-gaps with multi-tag support + +--- + btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 152 +++++++++++--- + 3 files changed, 717 insertions(+), 29 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..650cab5 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,582 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/02/13 23:25:03 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid, e = m->gaps; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ if (is_root && e) { ++ area.x += gappx; ++ area.y += gappx; ++ area.width -= 2 * gappx; ++ area.height -= 2 * gappx; ++ } ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ ++ if (e) { ++ left_area.width -= gappx / 2; ++ right_area.x += gappx / 2; ++ right_area.width -= gappx / 2; ++ } ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ ++ if (e) { ++ left_area.height -= gappx / 2; ++ right_area.y += gappx / 2; ++ right_area.height -= gappx / 2; ++ } ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..e49a061 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -208,6 +211,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1809,7 +1845,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* 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. */ +@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.3 + diff --git a/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7.patch b/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7.patch new file mode 100644 index 0000000..b85473e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/btrtile/btrtile-v0.7.patch @@ -0,0 +1,903 @@ +From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 13 Feb 2025 23:23:40 +0200 +Subject: [PATCH] btrtile with multi-tag support + +--- + btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 152 +++++++++++--- + 3 files changed, 698 insertions(+), 29 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..03f4680 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,563 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..e49a061 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -208,6 +211,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1809,7 +1845,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* 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. */ +@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.3 + diff --git a/dwl-bak/dwl-patches/patches/buttonbystate/README.md b/dwl-bak/dwl-patches/patches/buttonbystate/README.md new file mode 100644 index 0000000..cbb0eee --- /dev/null +++ b/dwl-bak/dwl-patches/patches/buttonbystate/README.md @@ -0,0 +1,10 @@ +### Description +Adds "state" (`enum wlr_button_state`) to configure a button action on either press or release. +This basically enables release to be used for button actions. + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_buttonbystate) +- [2024-04-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/buttonbystate/buttonbystate.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) diff --git a/dwl-bak/dwl-patches/patches/buttonbystate/buttonbystate.patch b/dwl-bak/dwl-patches/patches/buttonbystate/buttonbystate.patch new file mode 100644 index 0000000..d669aff --- /dev/null +++ b/dwl-bak/dwl-patches/patches/buttonbystate/buttonbystate.patch @@ -0,0 +1,78 @@ +From 4141aa9455e4b4a5b4a235475c70e8c100ec663e Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 6 Apr 2024 02:03:49 +0100 +Subject: [PATCH] buttonbystate - allow config for release (and press) + +- Adds "state" (enum wlr_button_state) to configure a button action on + either press or release. This basically enables release to be used + for button actions. +--- + config.def.h | 6 +++--- + dwl.c | 22 ++++++++++++---------- + 2 files changed, 15 insertions(+), 13 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..cc989cf 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -165,7 +165,7 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove}, WLR_BUTTON_PRESSED }, ++ { MODKEY, BTN_MIDDLE, togglefloating, {0}, WLR_BUTTON_PRESSED }, ++ { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize}, WLR_BUTTON_PRESSED }, + }; +diff --git a/dwl.c b/dwl.c +index bf763df..6b60ccf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -99,6 +99,7 @@ typedef struct { + unsigned int button; + void (*func)(const Arg *); + const Arg arg; ++ enum wlr_button_state state; + } Button; + + typedef struct Monitor Monitor; +@@ -595,16 +596,6 @@ buttonpress(struct wl_listener *listener, void *data) + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); +- +- keyboard = wlr_seat_get_keyboard(seat); +- mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; +- for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); +- return; +- } +- } + break; + case WLR_BUTTON_RELEASED: + held_grab = NULL; +@@ -622,6 +613,17 @@ buttonpress(struct wl_listener *listener, void *data) + } + break; + } ++ ++ keyboard = wlr_seat_get_keyboard(seat); ++ mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; ++ for (b = buttons; b < END(buttons); b++) { ++ if (b->state == event->state && CLEANMASK(mods) == CLEANMASK(b->mod) && ++ event->button == b->button && b->func) { ++ b->func(&b->arg); ++ return; ++ } ++ } ++ + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/center-terminal/README.md b/dwl-bak/dwl-patches/patches/center-terminal/README.md new file mode 100644 index 0000000..cfd70a2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/center-terminal/README.md @@ -0,0 +1,13 @@ +### Description +Add a keybinding that toggles centering the terminally horizontally when +it's the only window, while still tiling multiple windows. + +This limits the width of long text making it easier to read, and avoids +covering the wallpaper more than necessary. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/center-terminal) +- [2024-02-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/center-terminal/center-terminal.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl-bak/dwl-patches/patches/center-terminal/center-terminal.patch b/dwl-bak/dwl-patches/patches/center-terminal/center-terminal.patch new file mode 100644 index 0000000..214eaf2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/center-terminal/center-terminal.patch @@ -0,0 +1,103 @@ +From 340cc5ef90dfcc495bdad045f3f76ae07405cffd Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Tue, 6 Feb 2024 09:20:48 +0100 +Subject: [PATCH] add a keybinding to center the terminal + +Add a keybinding that toggles centering the terminally horizontally when +it's the only window, while still tiling multiple windows. + +This limits the width of long text making it easier to read, and avoids +covering the wallpaper more than necessary. +--- + config.def.h | 1 + + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8229fcc 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_v, togglecenter, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 12f441e..3b15748 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -138,6 +139,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ bool centered; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -334,6 +336,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglecenter(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -436,6 +439,8 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; + ++static bool center; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -499,6 +504,9 @@ applyrules(Client *c) + } + } + ++ if (!strcasecmp(appid, termcmd[0])) ++ c->centered = true; ++ + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); + } +@@ -2730,6 +2738,11 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { ++ if (n == 1 && center && c->centered) { ++ resize(c, (struct wlr_box){.x = m->w.width / 4, .y = m->w.y, ++ .width = m->w.width / 2, .height = m->w.height - 2 * c->bw}, 0); ++ return; ++ } + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; +@@ -2742,6 +2755,13 @@ tile(Monitor *m) + } + } + ++void ++togglecenter(const Arg *arg) ++{ ++ center = !center; ++ tile(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/centeredmaster/README.md b/dwl-bak/dwl-patches/patches/centeredmaster/README.md new file mode 100644 index 0000000..2ba6dd9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/centeredmaster/README.md @@ -0,0 +1,33 @@ +### Description +This is a port of centeredmaster patch for dwm: + +centeredmaster centers the nmaster area on screen, using mfact * monitor +width & height, with the stacked windows distributed to the left and +right. It can be selected with `Alt+c`. + +With one and two clients in master respectively this results in: + +``` ++------------------------------+ +------------------------------+ +|+--------++--------++--------+| |+--------++--------++--------+| +|| || || || || || || || +|| || || || || || M1 || || +|| || || || || || || || +|| S2 || M || S1 || || |+--------+| || +|| || || || || |+--------+| || +|| || || || || || || || +|| || || || || || M2 || || +|| || || || || || || || +|+--------++--------++--------+| |+--------++--------++--------+| ++------------------------------+ +------------------------------+ +``` + + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/centeredmaster/centeredmaster.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/b104a580a80ebaf9f7e8917fe574e3e97ddd019a/centeredmaster/centeredmaster.patch) +- [0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/centeredmaster/centeredmaster.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/centeredmaster/centeredmaster.patch b/dwl-bak/dwl-patches/patches/centeredmaster/centeredmaster.patch new file mode 100644 index 0000000..c29bd5f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/centeredmaster/centeredmaster.patch @@ -0,0 +1,135 @@ +From b1ca46930756b59c1ebba0b8c7871b85ff84f62f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sat, 8 Feb 2025 16:10:25 +0100 +Subject: [PATCH] Add centeredmaster layout + +This is a port of centeredmaster patch for dwm: + https://dwm.suckless.org/patches/centeredmaster + +centeredmaster centers the nmaster area on screen, using mfact * monitor +width & height, with the stacked windows distributed to the left and +right. It can be selected with [Alt]+[c]. + +With one and two clients in master respectively this results in: + ++------------------------------+ +------------------------------+ +|+--------++--------++--------+| |+--------++--------++--------+| +|| || || || || || || || +|| || || || || || M1 || || +|| || || || || || || || +|| S2 || M || S1 || || |+--------+| || +|| || || || || |+--------+| || +|| || || || || || || || +|| || || || || || M2 || || +|| || || || || || || || +|+--------++--------++--------+| |+--------++--------++--------+| ++------------------------------+ +------------------------------+ +--- + config.def.h | 2 ++ + dwl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 65 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9a3b0c5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "|M|", centeredmaster }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..c2456dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -251,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static void centeredmaster(Monitor *m); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); +@@ -649,6 +650,68 @@ buttonpress(struct wl_listener *listener, void *data) + event->time_msec, event->button, event->state); + } + ++void ++centeredmaster(Monitor *m) ++{ ++ int i, n, h, mw, mx, my, oty, ety, tw; ++ Client *c; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* initialize areas */ ++ mw = m->w.width; ++ mx = 0; ++ my = 0; ++ tw = mw; ++ ++ if (n > m->nmaster) { ++ /* go mfact box in the center if more than nmaster clients */ ++ mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ tw = m->w.width - mw; ++ ++ if (n - m->nmaster > 1) { ++ /* only one client */ ++ mx = (m->w.width - mw) / 2; ++ tw = (m->w.width - mw) / 2; ++ } ++ } ++ ++ i = 0; ++ oty = 0; ++ ety = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) { ++ /* nmaster clients are stacked vertically, in the center ++ * of the screen */ ++ h = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + my, .width = mw, ++ .height = h}, 0); ++ my += c->geom.height; ++ } else { ++ /* stack clients are stacked vertically */ ++ if ((i - m->nmaster) % 2) { ++ h = (m->w.height - ety) / ( (1 + n - i) / 2); ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ety, .width = tw, ++ .height = h}, 0); ++ ety += c->geom.height; ++ } else { ++ h = (m->w.height - oty) / ((1 + n - i) / 2); ++ resize(c, (struct wlr_box){.x = m->w.x + mx + mw, .y = m->w.y + oty, .width = tw, ++ .height = h}, 0); ++ oty += c->geom.height; ++ } ++ } ++ i++; ++ } ++} ++ + void + chvt(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/cfact-snail/README.md b/dwl-bak/dwl-patches/patches/cfact-snail/README.md new file mode 100644 index 0000000..488ab98 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact-snail/README.md @@ -0,0 +1,13 @@ +### Description +This patch implements [cfact][cfact] for [snail][snail] layout. +This patch must be applied on top of cfact and snail patches. + +[cfact]: /dwl/dwl-patches/src/branch/main/patches/cfact +[snail]: /dwl/dwl-patches/src/branch/main/patches/snail + + +### Download + - [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact-snail/cfact-snail.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/cfact-snail/cfact-snail.patch b/dwl-bak/dwl-patches/patches/cfact-snail/cfact-snail.patch new file mode 100644 index 0000000..213c3fd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact-snail/cfact-snail.patch @@ -0,0 +1,68 @@ +From 9f8109182a7d173d2a2cb30c089a7e7b9ffe0a5e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 25 Mar 2025 02:24:32 +0100 +Subject: [PATCH] cfact patch for snail layout + +--- + dwl.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 4f8c493..37aa935 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2699,10 +2699,10 @@ snail(Monitor *m) + * Split the previous horizontally and put the current window on the right + */ + } else if (dir == WLR_DIRECTION_RIGHT) { +- c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ c->geom = (struct wlr_box){.x = prev->geom.x + (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .y = prev->geom.y, ++ .width = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .height = prev->geom.height}; + prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ .width = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .height = prev->geom.height}; + /* + * If it's a stack window or the first narrow window in the master + * area, put the next one below it +@@ -2713,28 +2713,28 @@ snail(Monitor *m) + * Split the previous vertically and put the current window below it + */ + } else if (dir == WLR_DIRECTION_DOWN) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height), ++ .width = prev->geom.width, .height = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height)}; + prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ .width = prev->geom.width, .height = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height)}; + dir = WLR_DIRECTION_LEFT; + /* + * Split the previous horizontally and put the current window on the left + */ + } else if (dir == WLR_DIRECTION_LEFT) { + c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ .width = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .y = prev->geom.y, ++ .width = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .height = prev->geom.height}; + dir = WLR_DIRECTION_UP; + /* + * Split the previous vertically and put the current window above it + */ + } else { + c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ .width = prev->geom.width, .height = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height)}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height), ++ .width = prev->geom.width, .height = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height)}; + dir = WLR_DIRECTION_RIGHT; + } + i++; +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/cfact/README.md b/dwl-bak/dwl-patches/patches/cfact/README.md new file mode 100644 index 0000000..166b4e2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact/README.md @@ -0,0 +1,25 @@ +### Description +A port of the [dwm cfacts patch](https://dwm.suckless.org/patches/cfacts/) (with the limits removed) + +Clients with higher weight are allocated more space! +``` ++---------------------+ +| | 0.5 | +| 1.0 +----------+ ++----------+ | +| | 1.0 | +| +----------+ +| 2.0 | | +| | 1.0 | ++----------+----------+` +``` +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/cfact) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.7.patch) +- [v0.7-gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.7-gaps.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.6.patch) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/cfact/cfact-v0.6.patch b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.6.patch new file mode 100644 index 0000000..c1505fb --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.6.patch @@ -0,0 +1,121 @@ +From 52d8ed5ece7c96ea02441faaa4da2b0f51c8ebc4 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..d1bc596 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -133,6 +133,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..68e35d2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -140,6 +140,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -318,6 +319,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1041,6 +1043,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2218,6 +2221,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2637,6 +2653,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2649,17 +2666,27 @@ tile(Monitor *m) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -3047,6 +3074,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch new file mode 100644 index 0000000..e080238 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch @@ -0,0 +1,155 @@ +From 31d0ceb3f7dea83282e61e556d71d06b7f43d753 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Sat, 16 Nov 2024 08:47:02 -0500 +Subject: [PATCH] Add cfact to gaps + +--- + config.def.h | 3 +++ + dwl.c | 63 +++++++++++++++++++++++++++++++++++++++++----------- + 2 files changed, 53 insertions(+), 13 deletions(-) + +diff --git a/config.def.h b/config.def.h +index b388b4e..a871364 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -136,6 +136,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, +diff --git a/dwl.c b/dwl.c +index dc851df..b5313c1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -140,6 +140,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -319,6 +320,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1045,6 +1047,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2223,6 +2226,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2642,38 +2658,58 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int h, r, e = m->gaps, mw, my, ty; ++ unsigned int e = m->gaps, mw, my, ty; + int i, n = 0; +- Client *c; ++ float mweight = 0, tweight = 0; ++ Client *c, *sel = NULL; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m) && !c->isfullscreen) { ++ if (!sel) ++ sel = c; ++ if (!c->isfloating) ++ n++; ++ } ++ } + +- wl_list_for_each(c, &clients, link) +- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) +- n++; + if (n == 0) + return; ++ + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else +- mw = m->w.width; ++ mw = m->w.width - gappx*e; ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } ++ + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- r = MIN(n, m->nmaster) - i; +- h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, +- .width = mw - 2*gappx*e, .height = h}, 0); ++ .width = mw - gappx*e, ++ .height = (int)((c->cweight / mweight) * (float)(m->w.height - gappx*e)) - gappx*e ++ }, 0); + my += c->geom.height + gappx*e; + } else { +- r = n - i; +- h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ resize(c, (struct wlr_box){.x = m->w.x + mw + gappx*e, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*gappx*e, ++ .height = (int)((c->cweight / tweight) * (float)(m->w.height - gappx*e)) - gappx*e ++ }, 0); + ty += c->geom.height + gappx*e; + } + i++; +@@ -3068,6 +3104,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.47.0 + diff --git a/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7.patch b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7.patch new file mode 100644 index 0000000..7d28a8b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact/cfact-v0.7.patch @@ -0,0 +1,121 @@ +From e82e15860c36a70539625b8fe7b4bd54d0721705 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..d1bc596 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -133,6 +133,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 5bf995e..5a17343 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -322,6 +323,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1060,6 +1062,7 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->cweight = 1.0; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -2268,6 +2271,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2692,6 +2708,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2704,17 +2721,27 @@ tile(Monitor *m) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -3102,6 +3129,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/cfact/cfact.patch b/dwl-bak/dwl-patches/patches/cfact/cfact.patch new file mode 100644 index 0000000..c91d3c0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cfact/cfact.patch @@ -0,0 +1,121 @@ +From 98fe302cd240b519c28c886250273854844ab2c7 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..27ff521 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -128,6 +128,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index fa76db2..9bdb438 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -136,6 +136,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -301,6 +302,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -956,6 +958,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2009,6 +2012,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2468,6 +2484,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2480,17 +2497,27 @@ tile(Monitor *m) + mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -2872,6 +2899,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.43.1 + diff --git a/dwl-bak/dwl-patches/patches/chainkeys/README.md b/dwl-bak/dwl-patches/patches/chainkeys/README.md new file mode 100644 index 0000000..fa6d185 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/chainkeys/README.md @@ -0,0 +1,15 @@ +### Description +Implements chained keybindings (like the dwm +[keychain](https://dwm.suckless.org/patches/keychain/) patch). + +Bindings can share a leading chain key. This chain key will be triggered when +Mod+chain is pressed. A subsequent keypress will be matched against bindings +for that chain key. If it is configured the action will be triggered, otherwise +the keypress will be ignored. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/chainkeys) +- [2024-05-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/chainkeys/chainkeys.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/chainkeys/chainkeys.patch b/dwl-bak/dwl-patches/patches/chainkeys/chainkeys.patch new file mode 100644 index 0000000..a0daefd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/chainkeys/chainkeys.patch @@ -0,0 +1,157 @@ +From 226e204ec7fb6d6840a984ef8e8ec1d2514e985f Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Tue, 2 Jan 2024 10:33:59 +1000 +Subject: [PATCH] chainkeys + +--- + config.def.h | 62 ++++++++++++++++++++++++++-------------------------- + dwl.c | 23 ++++++++++++++++++- + 2 files changed, 53 insertions(+), 32 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8f498d2f..1c182547 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -105,10 +105,10 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + #define MODKEY WLR_MODIFIER_ALT + + #define TAGKEYS(KEY,SKEY,TAG) \ +- { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY, -1, KEY, view, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL, -1, KEY, toggleview, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, SKEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,-1,SKEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -119,30 +119,30 @@ static const char *menucmd[] = { "wmenu-run", NULL }; + + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ /* modifier chain, key function argument */ ++ { MODKEY, -1, XKB_KEY_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, -1, XKB_KEY_j, focusstack, {.i = +1} }, ++ { MODKEY, -1, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY, -1, XKB_KEY_i, incnmaster, {.i = +1} }, ++ { MODKEY, -1, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY, -1, XKB_KEY_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, -1, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, -1, XKB_KEY_Return, zoom, {0} }, ++ { MODKEY, -1, XKB_KEY_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_C, killclient, {0} }, ++ { MODKEY, -1, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, -1, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, -1, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, -1, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_space, togglefloating, {0} }, ++ { MODKEY, -1, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, -1, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_parenright, tag, {.ui = ~0} }, ++ { MODKEY, -1, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, -1, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -152,14 +152,14 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,-1,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } ++#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,-1,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; +diff --git a/dwl.c b/dwl.c +index bf763dfc..05e667f8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,6 +143,7 @@ typedef struct { + + typedef struct { + uint32_t mod; ++ int chain; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +@@ -353,6 +354,7 @@ static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; ++static int chainkey = -1; + static struct wl_display *dpy; + static struct wlr_backend *backend; + static struct wlr_scene *scene; +@@ -1438,11 +1440,30 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && sym == k->keysym ++ && chainkey == -1 ++ && k->chain == -1 ++ && k->func) { + k->func(&k->arg); + return 1; + } ++ else if (sym == k->keysym ++ && chainkey != -1 ++ && k->chain == chainkey ++ && k->func) { ++ k->func(&k->arg); ++ chainkey = -1; ++ return 1; ++ } ++ else if (CLEANMASK(mods) == CLEANMASK(k->mod) ++ && k->chain == (int)sym ++ && chainkey == -1 ++ && k->func) { ++ chainkey = sym; ++ return 1; ++ } + } ++ chainkey = -1; + return 0; + } + +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/client-opacity/README.md b/dwl-bak/dwl-patches/patches/client-opacity/README.md new file mode 100644 index 0000000..ed82d81 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/client-opacity/README.md @@ -0,0 +1,18 @@ +### Description +This patch adds default transparency parameters to config.h which specify the starting transparencies of all windows. + +It also adds opacities to the ruleset, enabling override of the opacities on a per client basis. + +Additionally, it adds some shortcuts: +``` +[MODKEY]+[o] -> increase focus opacity of currently focused window +[MODKEY]+[Shift]+[o] -> decrease focus opacity of currently focused window +``` + + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/client-opacity) +- [2025-01-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/client-opacity/client-opacity.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/client-opacity/client-opacity.patch b/dwl-bak/dwl-patches/patches/client-opacity/client-opacity.patch new file mode 100644 index 0000000..da2faf7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/client-opacity/client-opacity.patch @@ -0,0 +1,181 @@ +From ba3172875d379ff4f2db69753f50067cecfc8293 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 25 Jul 2023 12:48:22 -0600 +Subject: [PATCH] add default transparency for windows and rules for override + the transparency +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 9 ++++++--- + dwl.c | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0eb86874 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float default_opacity = 0.75; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +23,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating alpha monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, default_opacity, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 1.0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +@@ -133,6 +134,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, XKB_KEY_o, setopacity, {.f = +0.1f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, setopacity, {.f = -0.1f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index ad21e1ba..0554fcdf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,6 +138,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ float opacity; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ float opacity; + int monitor; + } Rule; + +@@ -319,6 +321,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void scenebuffersetopacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); + 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); +@@ -326,6 +329,7 @@ static void setfullscreen(Client *c, int fullscreen); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); ++static void setopacity(const Arg *arg); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); +@@ -491,6 +495,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->opacity = r->opacity; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -499,6 +504,8 @@ applyrules(Client *c) + } + } + } ++ if (c->scene_surface) ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); + setmon(c, mon, newtags); + } + +@@ -874,6 +881,9 @@ commitnotify(struct wl_listener *listener, void *data) + + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + ++ if (c->scene_surface) ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); ++ + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +@@ -1120,6 +1130,7 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->opacity = default_opacity; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -2285,6 +2296,15 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++scenebuffersetopacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *data) ++{ ++ Client *c = data; ++ /* xdg-popups are children of Client.scene, we do not have to worry about ++ messing with them. */ ++ wlr_scene_buffer_set_opacity(buffer, c->isfullscreen ? 1 : c->opacity); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2353,6 +2373,7 @@ setfullscreen(Client *c, int fullscreen) + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); + arrange(c->mon); + printstatus(); + } +@@ -2409,6 +2430,23 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + focusclient(focustop(selmon), 1); + } + ++void ++setopacity(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (!sel) ++ return; ++ ++ sel->opacity += arg->f; ++ if (sel->opacity > 1.0) ++ sel->opacity = 1.0f; ++ ++ if (sel->opacity < 0.1) ++ sel->opacity = 0.1f; ++ ++ wlr_scene_node_for_each_buffer(&sel->scene_surface->node, scenebuffersetopacity, sel); ++} ++ + void + setpsel(struct wl_listener *listener, void *data) + { +@@ -3120,6 +3158,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->opacity = default_opacity; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/column/README.md b/dwl-bak/dwl-patches/patches/column/README.md new file mode 100644 index 0000000..9d1b205 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/column/README.md @@ -0,0 +1,9 @@ +### Description +A column layout patch. This patch just puts the visible clients into equal-width columns on the screen. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/column) +- [2024-01-02](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/column/column.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/column/column.patch b/dwl-bak/dwl-patches/patches/column/column.patch new file mode 100644 index 0000000..0f6a531 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/column/column.patch @@ -0,0 +1,79 @@ +From d1eb2061c619d0bbd7a0ecda0fe77409f3a6c399 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Fri, 29 Dec 2023 19:02:11 +1000 +Subject: [PATCH] column layout + +--- + config.def.h | 2 ++ + dwl.c | 28 ++++++++++++++++++++++++++++ + 2 files changed, 30 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a8ed61d9..edb30cae 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "||", col }, + }; + + /* monitors */ +@@ -134,6 +135,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 4d19357f..63d80da7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -243,6 +243,7 @@ static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); + static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); ++static void col(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void createdecoration(struct wl_listener *listener, void *data); +@@ -704,6 +705,33 @@ closemon(Monitor *m) + printstatus(); + } + ++void ++col(Monitor *m) ++{ ++ Client *c; ++ unsigned int n = 0, i = 0; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x + i * m->w.width / n, ++ .y = m->w.y, ++ .width = m->w.width / n, ++ .height = m->w.height ++ }, ++ 0 ++ ); ++ i++; ++ } ++} ++ + void + commitlayersurfacenotify(struct wl_listener *listener, void *data) + { +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/coredump/README.md b/dwl-bak/dwl-patches/patches/coredump/README.md new file mode 100644 index 0000000..5300890 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/coredump/README.md @@ -0,0 +1,11 @@ +### Description +Generate a coredump if dwl exited abnormally (to be more usefull you need to +compile dwl and wlroots with debug symbols) + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/coredump) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/coredump/coredump.patch) +- [coredump-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/coredump/coredump-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/coredump/coredump-0.7.patch b/dwl-bak/dwl-patches/patches/coredump/coredump-0.7.patch new file mode 100644 index 0000000..f39e7af --- /dev/null +++ b/dwl-bak/dwl-patches/patches/coredump/coredump-0.7.patch @@ -0,0 +1,65 @@ +From 2abde87f9159ec3318a0489ac0ed512f166ef8c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 5 Oct 2022 23:07:13 -0500 +Subject: [PATCH] increase RLIMIT_CORE (generate a coredump) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f67..cfbb0bb8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -358,6 +359,8 @@ static void zoom(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; ++static struct rlimit oldrlimit; ++static struct rlimit newrlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2232,6 +2235,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2649,6 +2653,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -3189,6 +3194,10 @@ main(int argc, char *argv[]) + char *startup_cmd = NULL; + int c; + ++ getrlimit(RLIMIT_CORE, &oldrlimit); ++ newrlimit.rlim_cur = newrlimit.rlim_max = oldrlimit.rlim_max; ++ setrlimit(RLIMIT_CORE, &newrlimit); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/coredump/coredump.patch b/dwl-bak/dwl-patches/patches/coredump/coredump.patch new file mode 100644 index 0000000..98ffbd8 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/coredump/coredump.patch @@ -0,0 +1,65 @@ +From 6d5017888891957615160fe7c015adf7a6f0fd45 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 5 Oct 2022 23:07:13 -0500 +Subject: [PATCH] increase RLIMIT_CORE (generate a coredump) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..940fbeff 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -353,6 +354,8 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + static void zoom(const Arg *arg); + + /* variables */ ++static struct rlimit oldrlimit; ++static struct rlimit newrlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2248,6 +2251,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2659,6 +2663,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -3178,6 +3183,10 @@ main(int argc, char *argv[]) + char *startup_cmd = NULL; + int c; + ++ getrlimit(RLIMIT_CORE, &oldrlimit); ++ newrlimit.rlim_cur = newrlimit.rlim_max = oldrlimit.rlim_max; ++ setrlimit(RLIMIT_CORE, &newrlimit); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/cursortheme/README.md b/dwl-bak/dwl-patches/patches/cursortheme/README.md new file mode 100644 index 0000000..ac94800 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cursortheme/README.md @@ -0,0 +1,17 @@ +### Description +Adds ability to change cursor's theme and size. + +```c +static const char *cursor_theme = NULL; +static const char cursor_size[] = "24"; /* Make sure it's a valid integer, otherwise things will break */ +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/cursortheme) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/cursortheme/cursortheme.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/b828e21717fa584affeb3245359c3ab615759fa4/cursortheme/cursortheme.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/c676de59d51e613bd52ac46c77a24b1cac9a61a1/cursortheme/cursortheme.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [egorguslyan](https://github.com/egorguslyan) diff --git a/dwl-bak/dwl-patches/patches/cursortheme/cursortheme.patch b/dwl-bak/dwl-patches/patches/cursortheme/cursortheme.patch new file mode 100644 index 0000000..3c34222 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/cursortheme/cursortheme.patch @@ -0,0 +1,46 @@ +From f08376a2a04929a3907612e6c1f980ad3cdf939f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:10:39 -0500 +Subject: [PATCH] implement cursortheme + +--- + config.def.h | 2 ++ + dwl.c | 8 ++++++-- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..1f9ff56 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,8 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const char *cursor_theme = NULL; ++static const char cursor_size[] = "24"; /* Make sure it's a valid integer, otherwise things will break */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index dc0437e..a91d42b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2522,8 +2522,12 @@ setup(void) + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ +- cursor_mgr = wlr_xcursor_manager_create(NULL, 24); +- setenv("XCURSOR_SIZE", "24", 1); ++ cursor_mgr = wlr_xcursor_manager_create(cursor_theme, atoi(cursor_size)); ++ setenv("XCURSOR_SIZE", cursor_size, 1); ++ if (cursor_theme) ++ setenv("XCURSOR_THEME", cursor_theme, 1); ++ else ++ unsetenv("XCURSOR_THEME"); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around +-- +2.45.1 + + diff --git a/dwl-bak/dwl-patches/patches/customfloat/README.md b/dwl-bak/dwl-patches/patches/customfloat/README.md new file mode 100644 index 0000000..9f2bda5 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/customfloat/README.md @@ -0,0 +1,24 @@ +### Description +Rules for floating windows support default x, y, width, height. Defaults to the center of the screen and the client size. + +If the width or height is less than or equal to 1, then the value will be interpreted as a percentage. For example, 0.5 represents 50%, 0.25 represents 25%, and 1 represents 100%. **NOTE**: Some clients, like Thunar, have minimum width/height + +The variable `center_relative_to_monitor` allows the user to choose whether to center relative to the monitor or relative to the window area. + +
+Explanation of center_relative_to_monitor: +
+The "Monitor area" refers to the space enclosed by the green rectangle, while the "Window area" refers to the space enclosed by the red rectangle.
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/customfloat) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/customfloat/customfloat.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/98cba933c9f4099202e54f39acbf17e05bde828a/customfloat/customfloat.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/bf098459219e7a473d8edb4c0435aeb6a4b82e38/customfloat/customfloat.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Stivvo](https://github.com/Stivvo) diff --git a/dwl-bak/dwl-patches/patches/customfloat/customfloat.patch b/dwl-bak/dwl-patches/patches/customfloat/customfloat.patch new file mode 100644 index 0000000..d295571 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/customfloat/customfloat.patch @@ -0,0 +1,93 @@ +From 4f19f5499610d56f2616da5d44039403ac9d4c06 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 9 Jul 2024 10:52:37 -0500 +Subject: [PATCH] implement customfloat and generate patches + +--- + config.def.h | 7 ++++--- + dwl.c | 27 +++++++++++++++++++++++++++ + 2 files changed, 31 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..dee53f4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +23,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor x y width height */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, -1, 0, 0, 1000, 0.75 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1, 0, 0, 0, 0 },/* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0437e..be0340f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -230,6 +230,10 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int x; ++ int y; ++ float w; ++ float h; + } Rule; + + typedef struct { +@@ -454,6 +458,11 @@ applyrules(Client *c) + int i; + const Rule *r; + Monitor *mon = selmon, *m; ++ int newwidth; ++ int newheight; ++ int newx; ++ int newy; ++ int apply_resize = 0; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) +@@ -471,9 +480,27 @@ applyrules(Client *c) + if (r->monitor == i++) + mon = m; + } ++ if (c->isfloating || !mon->lt[mon->sellt]->arrange) { ++ /* client is floating or in floating layout */ ++ struct wlr_box b = respect_monitor_reserved_area ? mon->w : mon->m; ++ newwidth = (int)round(r->w ? (r->w <= 1 ? b.width * r->w : r->w) : c->geom.width); ++ newheight = (int)round(r->h ? (r->h <= 1 ? b.height * r->h : r->h) : c->geom.height); ++ newx = (int)round(r->x ? (r->x <= 1 ? b.width * r->x + b.x : r->x + b.x) : c->geom.x); ++ newy = (int)round(r->y ? (r->y <= 1 ? b.height * r->y + b.y : r->y + b.y) : c->geom.y); ++ apply_resize = 1; ++ ++ } + } + } + setmon(c, mon, newtags); ++ if (apply_resize) { ++ resize(c, (struct wlr_box){ ++ .x = newx, ++ .y = newy, ++ .width = newwidth, ++ .height = newheight, ++ }, 1); ++ } + } + + void +-- +2.45.1 diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/README.md b/dwl-bak/dwl-patches/patches/dim-unfocused/README.md new file mode 100644 index 0000000..dad3ef0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/README.md @@ -0,0 +1,19 @@ +### Description +Implements dimming of clients which are unfocused. + +The code also allows any color dimming. There is also an additional option in `Rule`, which allows you to keep the client `neverdim`, that is, as if it is focused. + +There are also two functions that can be bound to a `Key` or `Button`, +1. `toggledimming`: Which toggles dimming for all windows (except for `Rule`s) +2. `toggledimmingclient`: Which toggles dimming for the focused window, as if the client had `neverdim` applied to it. This overwrites an applied `Rule`. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused.patch) +- [2024-09-03](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240903.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240714.patch) +- [2024-05-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240516.patch) +- [2024-04-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240416.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/dim-unfocused) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch new file mode 100644 index 0000000..88504cd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch @@ -0,0 +1,213 @@ +From cd3b5580dbf38f54b54d5bfb6039e0039cbd6b21 Mon Sep 17 00:00:00 2001 +From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> +Date: Thu, 16 May 2024 12:26:05 +0200 +Subject: [PATCH] clean git history + +--- + client.h | 6 ++++++ + config.def.h | 13 ++++++++----- + dwl.c | 40 +++++++++++++++++++++++++++++++++++----- + 3 files changed, 49 insertions(+), 10 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..4d83248 100644 +--- a/client.h ++++ b/client.h +@@ -332,6 +332,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 8f498d2..d3950f9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -21,10 +22,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -135,8 +137,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index bf763df..ca88ad0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -109,6 +109,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -137,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -330,6 +332,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -462,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1267,8 +1272,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1286,7 +1293,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1566,7 +1573,7 @@ mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1599,6 +1606,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1617,6 +1628,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2040,7 +2055,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2050,6 +2065,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width, c-> geom.height); ++ wlr_scene_node_set_position(&c->dimmer->node, 0, 0); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2603,6 +2620,19 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ + void + togglefloating(const Arg *arg) + { +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch new file mode 100644 index 0000000..701c450 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch @@ -0,0 +1,177 @@ +diff --git a/client.h b/client.h +index 800b867..4d83248 100644 +--- a/client.h ++++ b/client.h +@@ -332,6 +332,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 8847e58..36ac2a1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -21,10 +22,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -135,8 +137,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index bf763df..abd7112 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -109,6 +109,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -137,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -330,6 +332,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -462,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1267,8 +1272,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1286,7 +1293,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1599,6 +1606,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -2040,7 +2051,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2050,6 +2061,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width, c-> geom.height); ++ wlr_scene_node_set_position(&c->dimmer->node, 0, 0); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2603,6 +2616,17 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ client_set_dimmer_state(focustop(selmon), 0); ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch new file mode 100644 index 0000000..0b60d60 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch @@ -0,0 +1,199 @@ +diff --git a/client.h b/client.h +index f0e5445..04b8d31 100644 +--- a/client.h ++++ b/client.h +@@ -320,6 +320,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..e1b2bf0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..6c65860 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -110,6 +110,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -138,7 +139,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -229,6 +230,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -334,6 +336,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -407,6 +410,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -465,6 +469,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1346,8 +1351,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1365,7 +1372,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1638,7 +1645,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p, *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1670,6 +1677,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1688,6 +1699,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2141,7 +2156,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2151,6 +2166,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2666,6 +2683,19 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch new file mode 100644 index 0000000..fc48b0b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch @@ -0,0 +1,216 @@ +diff --git a/client.h b/client.h +index dabea35..3a31c25 100644 +--- a/client.h ++++ b/client.h +@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..4ca21c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +@@ -172,5 +175,6 @@ static const Key keys[] = { + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 9021e44..e1f32e9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -112,6 +112,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -141,7 +142,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +232,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -466,6 +471,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1366,8 +1372,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1385,7 +1393,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1682,8 +1690,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1717,6 +1724,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1735,6 +1746,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2161,7 +2176,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2171,6 +2186,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2682,6 +2699,27 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ ++void ++toggledimmingclient(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ sel -> neverdim ^= 1; ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused.patch b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused.patch new file mode 100644 index 0000000..f73d886 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dim-unfocused/dim-unfocused.patch @@ -0,0 +1,216 @@ +diff --git a/client.h b/client.h +index dabea35..3a31c25 100644 +--- a/client.h ++++ b/client.h +@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..4ca21c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +@@ -172,5 +175,6 @@ static const Key keys[] = { + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index dc0c861..dcc3ece 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -112,6 +112,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -141,7 +142,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +232,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -466,6 +471,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1365,8 +1371,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1384,7 +1392,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1681,8 +1689,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1716,6 +1723,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1734,6 +1745,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2160,7 +2175,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2170,6 +2185,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2681,6 +2698,27 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ ++void ++toggledimmingclient(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ sel -> neverdim ^= 1; ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md new file mode 100644 index 0000000..c6588ee --- /dev/null +++ b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md @@ -0,0 +1,11 @@ +### Description +This patch changes the default behavior of the [disable-keybindings-on-fullscreen](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/disable-keybindings-on-fullscreen) patch by only taking effect when you explicitly toggle the functionality. +You must apply that patch prior to applying this one. + +### Download +- [git branch](https://github.com/Shringe/dwl/tree/disable-keybindings-on-fullscreen-toggle) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch) + +### Authors +- [Shringe](https://codeberg.org/Shringe) +- shringe_ at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch new file mode 100644 index 0000000..272d906 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch @@ -0,0 +1,71 @@ +From 71809cee0e27f1b3620773e1745afed8023f71c9 Mon Sep 17 00:00:00 2001 +From: Shringe +Date: Mon, 23 Jun 2025 18:50:40 -0500 +Subject: [PATCH] Implemented functionality for patch + +--- + config.def.h | 1 + + dwl.c | 14 ++++++++++++-- + 2 files changed, 13 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..dda4ad0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_G, togglefullscreenkeyinhibit, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index f11de4b..5deb9c7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -341,6 +341,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefullscreenkeyinhibit(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -414,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int fullscreen_key_inhibit_enabled = 0; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1583,8 +1586,9 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- if (c && c->isfullscreen) { +- if (k->func == togglefullscreen) { ++ if (fullscreen_key_inhibit_enabled ++ && c && c->isfullscreen) { ++ if (k->func == togglefullscreenkeyinhibit) { + k->func(&k->arg); + return 1; + } +@@ -2763,6 +2767,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefullscreenkeyinhibit(const Arg *arg) ++{ ++ fullscreen_key_inhibit_enabled = !fullscreen_key_inhibit_enabled; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md new file mode 100644 index 0000000..9c438bb --- /dev/null +++ b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md @@ -0,0 +1,9 @@ +### Description +This patch disables all keybindings except `togglefullscreen` when the focused window is fullscreen. +Might help prevent fat-fingering. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/disable-keybindings-on-fullscreen) +- [2025-02-09](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch new file mode 100644 index 0000000..57a18e1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch @@ -0,0 +1,82 @@ +From 2d6b845701091d3238774747c718df7fef135986 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Sun, 9 Feb 2025 14:59:33 +0200 +Subject: [PATCH] disable keybindings on fullscreen + +--- + dwl.c | 23 ++++++++++++++++++++++- + 1 file changed, 22 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index ec4ca86..8c771e8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -282,6 +282,7 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static Client *firstfocused(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -620,11 +621,15 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *focused; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ focused = firstfocused(); ++ if (focused && focused->isfullscreen) ++ goto skip_click; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -664,6 +669,7 @@ buttonpress(struct wl_listener *listener, void *data) + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ ++skip_click: + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); + } +@@ -1393,6 +1399,13 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++Client * ++firstfocused(void) ++{ ++ Client *c = wl_container_of(fstack.next, c, flink); ++ return c; ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1607,10 +1620,18 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + * processing keys, rather than passing them on to the client for its own + * processing. + */ ++ Client *c = firstfocused(); + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { ++ if (c && c->isfullscreen) { ++ if (k->func == togglefullscreen) { ++ k->func(&k->arg); ++ return 1; ++ } ++ return 0; ++ } + k->func(&k->arg); + return 1; + } +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/dragmfact/README.md b/dwl-bak/dwl-patches/patches/dragmfact/README.md new file mode 100644 index 0000000..3112d44 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragmfact/README.md @@ -0,0 +1,11 @@ +### Description +Change mfact by dragging the mouse. + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/dragmfact) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact-v0.6.patch) +- [2024-02-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch new file mode 100644 index 0000000..2435cbf --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch @@ -0,0 +1,59 @@ +From aeee958aec3e0774f3ea8baefe028e1a8cc2d2ce Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..c9071a5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -171,6 +171,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 145fd01..0a3d140 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -80,7 +80,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1823,6 +1823,9 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1874,6 +1877,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch new file mode 100644 index 0000000..f0ea5d3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch @@ -0,0 +1,59 @@ +From ae44bfc181fc1532d2f0c4deb20e20634050a661 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..c9071a5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -171,6 +171,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 5bf995e..dfb0754 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -81,7 +81,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1872,6 +1872,9 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1923,6 +1926,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/dragmfact/dragmfact.patch b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact.patch new file mode 100644 index 0000000..e2fdbac --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragmfact/dragmfact.patch @@ -0,0 +1,59 @@ +From 435cdf673e5a8080123109dbf874aac2ccef1498 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..3c26522 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -166,6 +166,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index fa76db2..528e102 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -76,7 +76,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrFS, LyrTop, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1639,6 +1639,9 @@ motionnotify(uint32_t time) + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = ROUND(cursor->x) - grabc->geom.x, .height = ROUND(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* Find the client under the pointer and send the event along. */ +@@ -1701,6 +1704,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.43.2 + diff --git a/dwl-bak/dwl-patches/patches/dragresize/README.md b/dwl-bak/dwl-patches/patches/dragresize/README.md new file mode 100644 index 0000000..c890ce7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragresize/README.md @@ -0,0 +1,12 @@ +### Description +implement rio-like window resizing + +select window to resize (mod+middleclick by default) then drag out an area for it to occupy + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/dragresize) +- [2024-07-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragresize/dragresize.patch) +- [2024-06-19](https://codeberg.org/dwl/dwl-patches/raw/commit/8c75e6dbc1728bf70d42547222464f496d9ea613/patches/dragresize/dragresize.patch) + +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/dragresize/dragresize.patch b/dwl-bak/dwl-patches/patches/dragresize/dragresize.patch new file mode 100644 index 0000000..1e4cfe0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/dragresize/dragresize.patch @@ -0,0 +1,210 @@ +From 83ae7d4816a49f46063ab16ffecd32b2e7e61784 Mon Sep 17 00:00:00 2001 +From: choc +Date: Fri, 15 Sep 2023 11:45:16 +0800 +Subject: [PATCH] dragresize: implement rio-like window resizing + +select window to resize then drag out an area for it to occupy +--- + dwl.c | 132 +++++++++++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 98 insertions(+), 34 deletions(-) + +diff --git a/dwl.c b/dwl.c +index dc0437e..843aa7a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -80,7 +80,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurSelect, CurMove, CurResize, CurBind }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -603,15 +603,21 @@ buttonpress(struct wl_listener *listener, void *data) + + switch (event->state) { + case WLR_BUTTON_PRESSED: +- cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); +- if (locked) ++ if (locked) { ++ cursor_mode = CurPressed; + break; ++ } + +- /* Change focus if the button was _pressed_ over a client */ +- xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) +- focusclient(c, 1); ++ if (cursor_mode == CurNormal) ++ cursor_mode = CurPressed; ++ ++ if (cursor_mode != CurResize) { ++ /* Change focus if the button was _pressed_ over a client */ ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ focusclient(c, 1); ++ } + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; +@@ -624,17 +630,42 @@ buttonpress(struct wl_listener *listener, void *data) + } + break; + case WLR_BUTTON_RELEASED: ++ if (locked) { ++ cursor_mode = CurNormal; ++ break; ++ } + /* If you released any buttons, we exit interactive move/resize mode. */ +- /* TODO should reset to the pointer focus's current setcursor */ +- if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ switch (cursor_mode) { ++ case CurPressed: + cursor_mode = CurNormal; +- /* Drop the window off on its new monitor */ +- selmon = xytomon(cursor->x, cursor->y); +- setmon(grabc, selmon, 0); ++ case CurNormal: ++ break; ++ case CurSelect: + return; +- } else { ++ case CurResize: ++ /* a label can only be part of a statement - Wpedantic */ ++ { ++ int nw = abs((int) cursor->x - grabcx); ++ int nh = abs((int) cursor->y - grabcy); ++ if (nw > 1 && nh > 1) { ++ setfloating(grabc, 1); ++ resize(grabc, (struct wlr_box){.x = MIN(ROUND(cursor->x), grabcx), ++ .y = MIN(ROUND(cursor->y), grabcy), ++ .width = nw, .height = nh}, 1); ++ } ++ } ++ /* fallthrough */ ++ default: ++ /* TODO should reset to the pointer focus's current setcursor */ ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ /* Drop the window off on its new monitor */ ++ if (grabc && cursor_mode != CurBind) { ++ selmon = xytomon(cursor->x, cursor->y); ++ setmon(grabc, selmon, 0); ++ grabc = NULL; ++ } + cursor_mode = CurNormal; ++ return; + } + break; + } +@@ -1815,15 +1846,33 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ +- if (cursor_mode == CurMove) { ++ switch (cursor_mode) { ++ case CurSelect: ++ return; ++ case CurMove: + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; +- } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ case CurResize: ++ { ++ int w, h, x, y; ++ if (!grabc) ++ return; ++ w = abs(grabcx - (int)round(cursor->x)); ++ h = abs(grabcy - (int)round(cursor->y)); ++ x = MIN(grabcx, (int)round(cursor->x)) - grabc->geom.x; ++ y = MIN(grabcy, (int)round(cursor->y)) - grabc->geom.y; ++ wlr_scene_rect_set_size(grabc->border[0], w, grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[1], w, grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[2], grabc->bw, h - 2 * grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[3], grabc->bw, h - 2 * grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[0]->node, x, y); ++ wlr_scene_node_set_position(&grabc->border[1]->node, x, y + h - grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[2]->node, x, y + grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[3]->node, x + w - grabc->bw, y + grabc->bw); ++ return; ++ } + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1853,29 +1902,43 @@ motionrelative(struct wl_listener *listener, void *data) + void + moveresize(const Arg *arg) + { +- if (cursor_mode != CurNormal && cursor_mode != CurPressed) +- return; +- xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); +- if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) ++ /* Consider global select bool instead of this + CurSelect enum */ ++ bool selected = (cursor_mode == CurSelect); ++ if (!selected) { ++ if (cursor_mode != CurNormal && cursor_mode != CurPressed) ++ return; ++ xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); ++ } ++ ++ if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) { ++ grabc = NULL; ++ cursor_mode = CurNormal; + return; ++ } + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); ++ /* TODO: factor out selected bool */ + switch (cursor_mode = arg->ui) { ++ case CurResize: ++ if (!selected) break; ++ grabcx = ROUND(cursor->x); ++ grabcy = ROUND(cursor->y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "tcross"); ++ return; + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; ++ setfloating(grabc, 1); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ return; ++ default: ++ grabc = NULL; ++ cursor_mode = CurNormal; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ return; + } ++ ++ cursor_mode = CurSelect; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "crosshair"); + } + + void +@@ -2149,6 +2212,7 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->border[0]->node, 0, 0); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/en-keycodes/README.md b/dwl-bak/dwl-patches/patches/en-keycodes/README.md new file mode 100644 index 0000000..ee3aae4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/en-keycodes/README.md @@ -0,0 +1,11 @@ +### Description +Always use the English keymap to get keycodes, so key bindings work even when using a non-English keyboard layout. + +### Download +- [git branch](https://codeberg.org/ForzCross/dwl/src/branch/en-keycodes.patch) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/en-keycodes/en-keycodes.patch) + +### Authors +- [ForzCross](https://codeberg.org/ForzCross) +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [dimkr](https://codeberg.org/dimkr) () diff --git a/dwl-bak/dwl-patches/patches/en-keycodes/en-keycodes.patch b/dwl-bak/dwl-patches/patches/en-keycodes/en-keycodes.patch new file mode 100644 index 0000000..445d900 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/en-keycodes/en-keycodes.patch @@ -0,0 +1,73 @@ +From cd61fac9cb6e9d0172e2f7a01e6a514d676ba5f0 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 23:53:11 +0100 +Subject: [PATCH] Always use the English keymap to get keycodes + +--- + dwl.c | 23 +++++++++++++++++++---- + 1 file changed, 19 insertions(+), 4 deletions(-) + +diff --git a/dwl.c b/dwl.c +index def2562..c299365 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -413,6 +413,11 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct xkb_rule_names en_rules = {.layout = "us"}; ++static struct xkb_context *en_context; ++static struct xkb_keymap *en_keymap; ++static struct xkb_state *en_state; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -694,6 +699,9 @@ cleanup(void) + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); ++ xkb_state_unref(en_state); ++ xkb_keymap_unref(en_keymap); ++ xkb_context_unref(en_context); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); +@@ -1582,16 +1590,19 @@ keypress(struct wl_listener *listener, void *data) + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; ++ int nsyms, handled; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); +- +- int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); ++ xkb_state_update_key(en_state, keycode, ++ (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ ? XKB_KEY_DOWN : XKB_KEY_UP); ++ nsyms = xkb_state_key_get_syms(en_state, keycode, &syms); ++ ++ handled = 0; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + +@@ -2607,6 +2618,10 @@ setup(void) + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ ++ en_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ en_keymap = xkb_keymap_new_from_names(en_context, &en_rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ en_state = xkb_state_new(en_keymap); + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/envcfg/README.md b/dwl-bak/dwl-patches/patches/envcfg/README.md new file mode 100644 index 0000000..07e5547 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/envcfg/README.md @@ -0,0 +1,8 @@ +### Description +Input device configuration (click method, tap-and-drag, acceleration, etc), border size and colors via environment variables. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/envcfg/envcfg.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl-bak/dwl-patches/patches/envcfg/envcfg.patch b/dwl-bak/dwl-patches/patches/envcfg/envcfg.patch new file mode 100644 index 0000000..ca9328d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/envcfg/envcfg.patch @@ -0,0 +1,302 @@ +From 3c3ea42cd50bfa5111be69b3c1f71afa0443bb53 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sat, 30 Dec 2023 10:49:48 +0200 +Subject: [PATCH] allow environment variables to override config.h + +--- + Makefile | 1 + + config.def.h | 10 +-- + dwl.c | 5 ++ + env.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 228 insertions(+), 5 deletions(-) + create mode 100644 env.c + +diff --git a/Makefile b/Makefile +index 9308656..c66d376 100644 +--- a/Makefile ++++ b/Makefile +@@ -22,6 +22,7 @@ dwl: dwl.o util.o + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.o: env.c + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +diff --git a/config.def.h b/config.def.h +index a784eb4..e0f10de 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,11 +6,11 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +-static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static unsigned int borderpx = 1; /* border pixel of windows */ ++static float rootcolor[] = COLOR(0x222222ff); ++static float bordercolor[] = COLOR(0x444444ff); ++static float focuscolor[] = COLOR(0x005577ff); ++static float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + +diff --git a/dwl.c b/dwl.c +index d48bf40..de33dfe 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -429,6 +429,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "env.c" ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1082,6 +1084,8 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } ++ ++ inputconfig(device); + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +@@ -3141,6 +3145,7 @@ main(int argc, char *argv[]) + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); ++ loadtheme(); + setup(); + run(startup_cmd); + cleanup(); +diff --git a/env.c b/env.c +new file mode 100644 +index 0000000..618f81e +--- /dev/null ++++ b/env.c +@@ -0,0 +1,217 @@ ++static int ++isenabled(const char *val, int def) ++{ ++ return ((def && (!val || !val[0] || (val[0] != '0'))) || (!def && (val && val[0] && (val[0] != '0')))); ++} ++ ++static void ++setclickmethod(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ long l; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_CLICK_METHOD"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ l = strtol(val, &end, 10); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_click_set_method(libinput_device, ++ (enum libinput_config_click_method)l); ++} ++ ++static void ++settap(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_TAP"); ++ if (val) { ++ if (!val[0]) ++ return; ++ ++ libinput_device_config_tap_set_enabled(libinput_device, ++ isenabled(val, 1) ? LIBINPUT_CONFIG_TAP_ENABLED : ++ LIBINPUT_CONFIG_TAP_DISABLED); ++ } else if (tap_to_click && libinput_device_config_tap_get_finger_count(libinput_device)) ++ libinput_device_config_tap_set_enabled(libinput_device, ++ LIBINPUT_CONFIG_TAP_ENABLED); ++} ++ ++static void ++settapanddrag(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_DRAG"); ++ if (val && val[0]) ++ libinput_device_config_tap_set_drag_enabled(libinput_device, ++ isenabled(val, 1) ? LIBINPUT_CONFIG_DRAG_ENABLED : ++ LIBINPUT_CONFIG_DRAG_DISABLED); ++} ++ ++static void ++setnaturalscroll(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_NATURAL_SCROLL"); ++ if (val && val[0]) ++ libinput_device_config_scroll_set_natural_scroll_enabled( ++ libinput_device, isenabled(val, 0)); ++ else if (!val && libinput_device_config_scroll_has_natural_scroll(libinput_device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled( ++ libinput_device, natural_scrolling); ++} ++ ++static void ++setaccelprofile(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ double profile; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_ACCELERATION_PROFILE"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ profile = strtod(val, &end); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_accel_set_profile(libinput_device, ++ (enum libinput_config_accel_profile)profile); ++} ++ ++static void ++setaccelspeed(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ double accel = 0; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_ACCELERATION"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ accel = strtod(val, &end); ++ if (errno || (end && *end) || (accel < -1) || (accel > 1)) ++ return; ++ ++ libinput_device_config_accel_set_speed(libinput_device, accel); ++} ++ ++static void ++setscrollmethod(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ long l; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_SCROLL_METHOD"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ l = strtol(val, &end, 10); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_scroll_set_method(libinput_device, ++ (enum libinput_config_scroll_method)l); ++} ++ ++static void ++setdwt(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_DISABLE_WHILE_TYPING"); ++ if (val && val[0]) ++ libinput_device_config_dwt_set_enabled(libinput_device, ++ isenabled(val, false) ? LIBINPUT_CONFIG_DWT_ENABLED : ++ LIBINPUT_CONFIG_DWT_DISABLED); ++} ++ ++static void ++setmiddleemul(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_MIDDLE_EMULATION"); ++ if (val && val[0]) ++ libinput_device_config_middle_emulation_set_enabled(libinput_device, ++ isenabled(val, false) ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : ++ LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); ++} ++ ++static void ++setlefthanded(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_LEFT_HANDED"); ++ if (val && val[0]) ++ libinput_device_config_left_handed_set(libinput_device, ++ isenabled(val, 0)); ++} ++ ++static void ++inputconfig(struct libinput_device *libinput_device) ++{ ++ setclickmethod(libinput_device); ++ settap(libinput_device); ++ settapanddrag(libinput_device); ++ setnaturalscroll(libinput_device); ++ setaccelprofile(libinput_device); ++ setaccelspeed(libinput_device); ++ setscrollmethod(libinput_device); ++ setdwt(libinput_device); ++ setmiddleemul(libinput_device); ++ setlefthanded(libinput_device); ++} ++ ++static void ++parsecolor(const char *val, float color[4]) ++{ ++ uint8_t r, g, b; ++ if (sscanf(val, "#%02hhx%02hhx%02hhx", &r, &g, &b) == 3) { ++ color[0] = (float)r / 0xFF; ++ color[1] = (float)g / 0xFF; ++ color[2] = (float)b / 0xFF; ++ color[3] = 1.0; ++ } ++} ++ ++static void ++loadtheme(void) ++{ ++ const char *val; ++ unsigned int tmp; ++ ++ val = getenv("DWL_ROOT_COLOR"); ++ if (val) ++ parsecolor(val, rootcolor); ++ ++ val = getenv("DWL_BORDER_COLOR"); ++ if (val) ++ parsecolor(val, bordercolor); ++ ++ val = getenv("DWL_FOCUS_COLOR"); ++ if (val) ++ parsecolor(val, focuscolor); ++ ++ val = getenv("DWL_URGENT_COLOR"); ++ if (val) ++ parsecolor(val, urgentcolor); ++ ++ val = getenv("DWL_BORDER"); ++ if (val && sscanf(val, "%u", &tmp) == 1) ++ borderpx = tmp; ++} +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/fakefullscreenclient/README.md b/dwl-bak/dwl-patches/patches/fakefullscreenclient/README.md new file mode 100644 index 0000000..6a98f0d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fakefullscreenclient/README.md @@ -0,0 +1,8 @@ +### Description +Allow setting fake fullscreen per client + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/fakefullscreenclient) +- [2024-03-29](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fakefullscreenclient/fakefullscreenclient.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch b/dwl-bak/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch new file mode 100644 index 0000000..848f350 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch @@ -0,0 +1,87 @@ +From 2ec6d0c668b4daee601337f8da45ccfa3a7d5fc6 Mon Sep 17 00:00:00 2001 +From: choc +Date: Fri, 29 Mar 2024 22:50:00 +0800 +Subject: [PATCH] implement fakefullscreenclient + +--- + config.def.h | 1 + + dwl.c | 23 ++++++++++++++++++++++- + 2 files changed, 23 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..8c220eb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -137,6 +137,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, togglefakefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 5867b0c..1e78491 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -137,7 +137,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, isfakefullscreen; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -318,6 +318,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 setfakefullscreen(Client *c, int fullscreen); + static void setgamma(struct wl_listener *listener, void *data); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); +@@ -332,6 +333,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefakefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2181,6 +2183,17 @@ setfullscreen(Client *c, int fullscreen) + printstatus(); + } + ++void ++setfakefullscreen(Client *c, int fullscreen) ++{ ++ c->isfakefullscreen = fullscreen; ++ if (!c->mon) ++ return; ++ if (c->isfullscreen) ++ setfullscreen(c, 0); ++ client_set_fullscreen(c, fullscreen); ++} ++ + void + setgamma(struct wl_listener *listener, void *data) + { +@@ -2620,6 +2633,14 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefakefullscreen(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ setfakefullscreen(sel, !sel->isfakefullscreen); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/fallback/README.md b/dwl-bak/dwl-patches/patches/fallback/README.md new file mode 100644 index 0000000..e9de8f4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fallback/README.md @@ -0,0 +1,8 @@ +### Description +Tries a different display mode if the preferred mode doesn't work. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fallback/fallback.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl-bak/dwl-patches/patches/fallback/fallback.patch b/dwl-bak/dwl-patches/patches/fallback/fallback.patch new file mode 100644 index 0000000..b734625 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fallback/fallback.patch @@ -0,0 +1,44 @@ +From a94c52af879027e654f67c36621a8c9b2f338f56 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sat, 2 Dec 2023 10:36:35 +0200 +Subject: [PATCH] fall back to a lower output mode if needed + (swaywm/sway@4cdc4ac) + +--- + dwl.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index d48bf40..7719f7e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -920,6 +920,7 @@ createmon(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; ++ struct wlr_output_mode *preferred_mode, *mode; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +@@ -956,7 +957,17 @@ createmon(struct wl_listener *listener, void *data) + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ +- wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); ++ preferred_mode = wlr_output_preferred_mode(wlr_output); ++ wlr_output_state_set_mode(&state, preferred_mode); ++ if (!wlr_output_test_state(wlr_output, &state) && !wl_list_empty(&wlr_output->modes)) { ++ wl_list_for_each(mode, &wlr_output->modes, link) { ++ if (mode != preferred_mode) { ++ wlr_output_state_set_mode(&state, mode); ++ if (wlr_output_test_state(wlr_output, &state)) ++ break; ++ } ++ } ++ } + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/focusdir/README.md b/dwl-bak/dwl-patches/patches/focusdir/README.md new file mode 100644 index 0000000..a2ed478 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/focusdir/README.md @@ -0,0 +1,9 @@ +### Description +Focus the window to the left, right, above or below the current focused window + +### Download +- [git branch](https://codeberg.org/ldev105/dwl/src/branch/focusdir) +- [2023-01-22](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/focusdir/focusdir.patch) + +### Authors +- [ldev105](https://codeberg.org/ldev105) diff --git a/dwl-bak/dwl-patches/patches/focusdir/focusdir.patch b/dwl-bak/dwl-patches/patches/focusdir/focusdir.patch new file mode 100644 index 0000000..72b8c5a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/focusdir/focusdir.patch @@ -0,0 +1,98 @@ +From a0e71a687b7fcaebdaf1da80c09bf5563bff46b1 Mon Sep 17 00:00:00 2001 +From: ldev +Date: Mon, 12 Feb 2024 21:50:24 +0100 +Subject: [PATCH] focusdir + +--- + config.def.h | 4 ++++ + dwl.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..2a1a82e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -124,6 +124,10 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_h, focusdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_l, focusdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_k, focusdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_j, focusdir, {.ui = 3} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index bf02a6d..64d5de7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -268,6 +269,7 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); ++static void focusdir(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -1271,6 +1273,49 @@ focusstack(const Arg *arg) + focusclient(c, 1); + } + ++void focusdir(const Arg *arg) ++{ ++ /* Focus the left, right, up, down client relative to the current focused client on selmon */ ++ Client *c, *sel = focustop(selmon); ++ if (!sel || sel->isfullscreen) ++ return; ++ ++ int dist=INT_MAX; ++ Client *newsel = NULL; ++ int newdist=INT_MAX; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon)) ++ continue; /* skip non visible windows */ ++ ++ if (arg->ui == 0 && sel->geom.x <= c->geom.x) { ++ /* Client isn't on our left */ ++ continue; ++ } ++ if (arg->ui == 1 && sel->geom.x >= c->geom.x) { ++ /* Client isn't on our right */ ++ continue; ++ } ++ if (arg->ui == 2 && sel->geom.y <= c->geom.y) { ++ /* Client isn't above us */ ++ continue; ++ } ++ if (arg->ui == 3 && sel->geom.y >= c->geom.y) { ++ /* Client isn't below us */ ++ continue; ++ } ++ ++ dist=abs(sel->geom.x-c->geom.x)+abs(sel->geom.y-c->geom.y); ++ if (dist < newdist){ ++ newdist = dist; ++ newsel=c; ++ } ++ } ++ if (newsel != NULL){ ++ focusclient(newsel, 1); ++ } ++} ++ ++ + /* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/follow/README.md b/dwl-bak/dwl-patches/patches/follow/README.md new file mode 100644 index 0000000..d2597cd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/follow/README.md @@ -0,0 +1,10 @@ +### Description +An extremely simple patch that adds the option to change DWL's window sending behavior; when active, sent windows will be followed, i.e. when a window is sent to another tag, the view changes to that tag.
No dependencies. + +![](follow.webp) + +### Download +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/follow/follow.patch)
Targets tag v0.7, but also works on git master and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) diff --git a/dwl-bak/dwl-patches/patches/follow/follow.patch b/dwl-bak/dwl-patches/patches/follow/follow.patch new file mode 100644 index 0000000..d260a75 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/follow/follow.patch @@ -0,0 +1,38 @@ +From c4de366aa5e2a465f5e2963f4ffed7adde77929a Mon Sep 17 00:00:00 2001 +From: moonsabre +Date: Thu, 13 Mar 2025 14:36:49 -0700 +Subject: [PATCH] Add parameter for following sent windows. + +--- + config.def.h | 1 + + dwl.c | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..cdbdc72 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int follow = 1; /* 1 means follow windows when sent to another tag */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index a2711f6..d2cf8ba 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2677,6 +2677,8 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ if (follow == 1) ++ view(arg); + printstatus(); + } + +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/follow/follow.webp b/dwl-bak/dwl-patches/patches/follow/follow.webp new file mode 100644 index 0000000..b629315 Binary files /dev/null and b/dwl-bak/dwl-patches/patches/follow/follow.webp differ diff --git a/dwl-bak/dwl-patches/patches/foreign-toplevel-management/README.md b/dwl-bak/dwl-patches/patches/foreign-toplevel-management/README.md new file mode 100644 index 0000000..6e30328 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/foreign-toplevel-management/README.md @@ -0,0 +1,9 @@ +### Description +Implement `foreign-toplevel-management`, it add handlers for activate, close, fullscreen and destroy request events, it's missing minimize and maximize request handlers. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6-a/foreign-toplevel-management) +- [2024-05-02](https://codeberg.org/dwl/dwl-patches/raw/commit/e58c3ec41a39df934d2998161d7187ac965ea77a/foreign-toplevel-management/foreign-toplevel-management.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch b/dwl-bak/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch new file mode 100644 index 0000000..3dfa14f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch @@ -0,0 +1,507 @@ +From dc63d64cd69f515013464b3feb8acb411f5a348f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 2 May 2024 17:25:45 -0500 +Subject: [PATCH] implement foreign toplevel management + +--- + Makefile | 5 +- + dwl.c | 93 +++++- + ...oreign-toplevel-management-unstable-v1.xml | 270 ++++++++++++++++++ + 3 files changed, 366 insertions(+), 2 deletions(-) + create mode 100644 protocols/wlr-foreign-toplevel-management-unstable-v1.xml + +diff --git a/Makefile b/Makefile +index a67fdd3..e0e1260 100644 +--- a/Makefile ++++ b/Makefile +@@ -16,7 +16,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ +-dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h wlr-foreign-toplevel-management-unstable-v1-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -37,6 +37,9 @@ wlr-layer-shell-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++wlr-foreign-toplevel-management-unstable-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ protocols/wlr-foreign-toplevel-management-unstable-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/dwl.c b/dwl.c +index bf763df..648616d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -126,6 +127,11 @@ typedef struct { + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; ++ struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; ++ struct wl_listener factivate; ++ struct wl_listener fclose; ++ struct wl_listener ffullscreen; ++ struct wl_listener fdestroy; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; + #ifdef XWAYLAND +@@ -347,6 +353,11 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void createforeigntoplevel(Client *c); ++static void factivatenotify(struct wl_listener *listener, void *data); ++static void fclosenotify(struct wl_listener *listener, void *data); ++static void fdestroynotify(struct wl_listener *listener, void *data); ++static void ffullscreennotify(struct wl_listener *listener, void *data); + + /* variables */ + static const char broken[] = "broken"; +@@ -392,6 +403,8 @@ static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + ++static struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_mgr; ++ + static struct wlr_seat *seat; + static KeyboardGroup kb_group = {0}; + static KeyboardGroup vkb_group = {0}; +@@ -458,6 +471,11 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ if (c->foreign_toplevel) { ++ wlr_foreign_toplevel_handle_v1_set_app_id(c->foreign_toplevel, appid); ++ wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); ++ } ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { +@@ -1288,6 +1306,8 @@ focusclient(Client *c, int lift) + client_set_border_color(old_c, bordercolor); + + client_activate_surface(old, 0); ++ if (old_c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_set_activated(old_c->foreign_toplevel, 0); + } + } + printstatus(); +@@ -1306,6 +1326,8 @@ focusclient(Client *c, int lift) + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, 1); + } + + void +@@ -1599,6 +1621,8 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ createforeigntoplevel(c); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -2232,12 +2256,17 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ +- if (oldmon) ++ if (oldmon) { ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_output_leave(c->foreign_toplevel, oldmon->wlr_output); + arrange(oldmon); ++ } + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, m->wlr_output); + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +@@ -2351,6 +2380,9 @@ setup(void) + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + ++ /* Initializes foreign toplevel management */ ++ foreign_toplevel_mgr = wlr_foreign_toplevel_manager_v1_create(dpy); ++ + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(); +@@ -2691,6 +2723,11 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (c->foreign_toplevel) { ++ wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); ++ c->foreign_toplevel = NULL; ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2802,6 +2839,12 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); ++ if (c->foreign_toplevel) { ++ const char *title; ++ if (!(title = client_get_title(c))) ++ title = broken; ++ wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); ++ } + if (c == focustop(c->mon)) + printstatus(); + } +@@ -2929,6 +2972,54 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++createforeigntoplevel(Client *c) ++{ ++ c->foreign_toplevel = wlr_foreign_toplevel_handle_v1_create(foreign_toplevel_mgr); ++ ++ LISTEN(&c->foreign_toplevel->events.request_activate, &c->factivate, factivatenotify); ++ LISTEN(&c->foreign_toplevel->events.request_close, &c->fclose, fclosenotify); ++ LISTEN(&c->foreign_toplevel->events.request_fullscreen, &c->ffullscreen, ffullscreennotify); ++ LISTEN(&c->foreign_toplevel->events.destroy, &c->fdestroy, fdestroynotify); ++} ++ ++void ++factivatenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, factivate); ++ if (c->mon == selmon) { ++ c->tags = c->mon->tagset[c->mon->seltags]; ++ } else { ++ setmon(c, selmon, 0); ++ } ++ focusclient(c, 1); ++ arrange(c->mon); ++} ++ ++void ++fclosenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, fclose); ++ client_send_close(c); ++} ++ ++void ++ffullscreennotify(struct wl_listener *listener, void *data) { ++ Client *c = wl_container_of(listener, c, ffullscreen); ++ struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; ++ setfullscreen(c, event->fullscreen); ++} ++ ++void ++fdestroynotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, fdestroy); ++ wl_list_remove(&c->factivate.link); ++ wl_list_remove(&c->fclose.link); ++ wl_list_remove(&c->ffullscreen.link); ++ wl_list_remove(&c->fdestroy.link); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml +new file mode 100644 +index 0000000..44505bb +--- /dev/null ++++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml +@@ -0,0 +1,270 @@ ++ ++ ++ ++ Copyright © 2018 Ilia Bozhinov ++ ++ Permission to use, copy, modify, distribute, and sell this ++ software and its documentation for any purpose is hereby granted ++ without fee, provided that the above copyright notice appear in ++ all copies and that both that copyright notice and this permission ++ notice appear in supporting documentation, and that the name of ++ the copyright holders not be used in advertising or publicity ++ pertaining to distribution of the software without specific, ++ written prior permission. The copyright holders make no ++ representations about the suitability of this software for any ++ purpose. It is provided "as is" without express or implied ++ warranty. ++ ++ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS ++ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND ++ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY ++ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ++ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ++ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF ++ THIS SOFTWARE. ++ ++ ++ ++ ++ The purpose of this protocol is to enable the creation of taskbars ++ and docks by providing them with a list of opened applications and ++ letting them request certain actions on them, like maximizing, etc. ++ ++ After a client binds the zwlr_foreign_toplevel_manager_v1, each opened ++ toplevel window will be sent via the toplevel event ++ ++ ++ ++ ++ This event is emitted whenever a new toplevel window is created. It ++ is emitted for all toplevels, regardless of the app that has created ++ them. ++ ++ All initial details of the toplevel(title, app_id, states, etc.) will ++ be sent immediately after this event via the corresponding events in ++ zwlr_foreign_toplevel_handle_v1. ++ ++ ++ ++ ++ ++ ++ Indicates the client no longer wishes to receive events for new toplevels. ++ However the compositor may emit further toplevel_created events, until ++ the finished event is emitted. ++ ++ The client must not send any more requests after this one. ++ ++ ++ ++ ++ ++ This event indicates that the compositor is done sending events to the ++ zwlr_foreign_toplevel_manager_v1. The server will destroy the object ++ immediately after sending this request, so it will become invalid and ++ the client should free any resources associated with it. ++ ++ ++ ++ ++ ++ ++ A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel ++ window. Each app may have multiple opened toplevels. ++ ++ Each toplevel has a list of outputs it is visible on, conveyed to the ++ client with the output_enter and output_leave events. ++ ++ ++ ++ ++ This event is emitted whenever the title of the toplevel changes. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the app-id of the toplevel changes. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the toplevel becomes visible on ++ the given output. A toplevel may be visible on multiple outputs. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the toplevel stops being visible on ++ the given output. It is guaranteed that an entered-output event ++ with the same output has been emitted before this event. ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be maximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be unmaximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be minimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be unminimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Request that this toplevel be activated on the given seat. ++ There is no guarantee the toplevel will be actually activated. ++ ++ ++ ++ ++ ++ ++ The different states that a toplevel can have. These have the same meaning ++ as the states with the same names defined in xdg-toplevel ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 ++ is created and each time the toplevel state changes, either because of a ++ compositor action or because of a request in this protocol. ++ ++ ++ ++ ++ ++ ++ ++ This event is sent after all changes in the toplevel state have been ++ sent. ++ ++ This allows changes to the zwlr_foreign_toplevel_handle_v1 properties ++ to be seen as atomic, even if they happen via multiple events. ++ ++ ++ ++ ++ ++ Send a request to the toplevel to close itself. The compositor would ++ typically use a shell-specific method to carry out this request, for ++ example by sending the xdg_toplevel.close event. However, this gives ++ no guarantees the toplevel will actually be destroyed. If and when ++ this happens, the zwlr_foreign_toplevel_handle_v1.closed event will ++ be emitted. ++ ++ ++ ++ ++ ++ The rectangle of the surface specified in this request corresponds to ++ the place where the app using this protocol represents the given toplevel. ++ It can be used by the compositor as a hint for some operations, e.g ++ minimizing. The client is however not required to set this, in which ++ case the compositor is free to decide some default value. ++ ++ If the client specifies more than one rectangle, only the last one is ++ considered. ++ ++ The dimensions are given in surface-local coordinates. ++ Setting width=height=0 removes the already-set rectangle. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This event means the toplevel has been destroyed. It is guaranteed there ++ won't be any more events for this zwlr_foreign_toplevel_handle_v1. The ++ toplevel itself becomes inert so any requests will be ignored except the ++ destroy request. ++ ++ ++ ++ ++ ++ Destroys the zwlr_foreign_toplevel_handle_v1 object. ++ ++ This request should be called either when the client does not want to ++ use the toplevel anymore or after the closed event to finalize the ++ destruction of the object. ++ ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be fullscreened on the given output. If the ++ fullscreen state and/or the outputs the toplevel is visible on actually ++ change, this will be indicated by the state and output_enter/leave ++ events. ++ ++ The output parameter is only a hint to the compositor. Also, if output ++ is NULL, the compositor should decide which output the toplevel will be ++ fullscreened on, if at all. ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be unfullscreened. If the fullscreen state ++ actually changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the parent of the toplevel changes. ++ ++ No event is emitted when the parent handle is destroyed by the client. ++ ++ ++ ++ ++ +-- +2.43.2 + diff --git a/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/README.md b/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/README.md new file mode 100644 index 0000000..6f7118c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/README.md @@ -0,0 +1,29 @@ +### Description + +# fullscreenadaptivesync — Enables adaptive sync/VRR when a client is fullscreen. + +Adds a function that automatically enables adaptive sync/VRR when a fullscreen client is detected, and disables it once the client exits fullscreen. + +--- + +1. **Why** + - Some VRR implementations can introduce distracting flickering when the display’s refresh rate is synced with the application’s framerate. While VRR is useful for some applications (especially fullscreen games), this patch automates the toggling of VRR whenever a client enters or exits fullscreen. + +2. **Requirements** + - A FreeSync/G-Sync capable monitor + - GPU/driver support for adaptive sync + +3. **How it works** + - When a client enters fullscreen, adaptive sync is automatically enabled. + - When the client exits fullscreen, adaptive sync is disabled again. + +4. **togglefullscreenadaptivesync** + - Adds a switch to enable or disable the fullscreenadaptivesync behavior. + - Enabled by default. + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/fullscreenadaptivesync) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch b/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch new file mode 100644 index 0000000..ccc012b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch @@ -0,0 +1,122 @@ +From c003f450c197a0c960bbb355511f8dca7a35e3c3 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sat, 4 Jan 2025 14:24:59 +0200 +Subject: [PATCH] add fullscreenadaptivesync + +--- + config.def.h | 1 + + dwl.c | 40 ++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..886f1ab 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_F5, togglefullscreenadaptivesync, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..7be05ef 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -322,6 +322,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void set_adaptive_sync(Monitor *m, int enabled); + 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); +@@ -340,6 +341,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefullscreenadaptivesync(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int fullscreen_adaptive_sync_enabled = 1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -2269,6 +2273,31 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++set_adaptive_sync(Monitor *m, int enable) ++{ ++ struct wlr_output_state state; ++ struct wlr_output_configuration_v1 *config; ++ struct wlr_output_configuration_head_v1 *config_head; ++ ++ if (!m || !m->wlr_output || !m->wlr_output->enabled ++ || !fullscreen_adaptive_sync_enabled) ++ return; ++ ++ config = wlr_output_configuration_v1_create(); ++ config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); ++ ++ /* Set and commit the adaptive sync state change */ ++ wlr_output_state_init(&state); ++ wlr_output_state_set_adaptive_sync_enabled(&state, enable); ++ wlr_output_commit_state(m->wlr_output, &state); ++ wlr_output_state_finish(&state); ++ ++ /* Broadcast the adaptive sync state change to output_mgr */ ++ config_head->state.adaptive_sync_enabled = enable; ++ wlr_output_manager_v1_set_configuration(output_mgr, config); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2332,10 +2361,12 @@ setfullscreen(Client *c, int fullscreen) + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); ++ set_adaptive_sync(c->mon, 1); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); ++ set_adaptive_sync(c->mon, 0); + } + arrange(c->mon); + printstatus(); +@@ -2739,6 +2770,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefullscreenadaptivesync(const Arg *arg) ++{ ++ fullscreen_adaptive_sync_enabled = !fullscreen_adaptive_sync_enabled; ++} ++ + void + toggletag(const Arg *arg) + { +@@ -2809,6 +2846,9 @@ unmapnotify(struct wl_listener *listener, void *data) + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } ++ /* Toggle adaptive sync off when fullscreen client is unmapped */ ++ if (c->isfullscreen) ++ set_adaptive_sync(selmon, 0); + + wlr_scene_node_destroy(&c->scene->node); + printstatus(); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/gaplessgrid/README.md b/dwl-bak/dwl-patches/patches/gaplessgrid/README.md new file mode 100644 index 0000000..6921d54 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaplessgrid/README.md @@ -0,0 +1,19 @@ +### Description +Arranges windows in a grid. Except it adjusts the number of windows in the first few columns to avoid empty cells. + +On widescreens (w > 2*h), it splits to three columns before splitting rows. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20240714.patch) +- [2023-08-01](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20230801.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/gaplessgrid) + +## Pre-codeberg +- [2023-11-14](https://github.com/djpohly/dwl/compare/main...Sneethe:gaplessgrid.patch) +- [2021-07-27](https://github.com/djpohly/dwl/compare/main...vnepogodin:gaplessgrid.patch) + +### Authors +- [Sneethe](https://github.com/Sneethe/) +- [Vladislav Nepogodin](https://github.com/vnepogodin) +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani/) (Revived to codeberg) diff --git a/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch new file mode 100644 index 0000000..2ade7da --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch @@ -0,0 +1,97 @@ +From 958cf2c2415927e2f7b471da9ada7c6e7d169a63 Mon Sep 17 00:00:00 2001 +From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> +Date: Mon, 8 Jan 2024 16:11:30 +0100 +Subject: [PATCH] revive gaplessgrid + +--- + config.def.h | 2 ++ + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 48 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a8ed61d..7400b7f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -134,6 +135,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 632dabf..4d810f7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -268,6 +268,7 @@ static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static void gaplessgrid(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -1311,6 +1312,51 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch new file mode 100644 index 0000000..cf430b9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch @@ -0,0 +1,89 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..014a909 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..dae1d1a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -287,6 +287,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static void gaplessgrid(Monitor *m); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -1467,6 +1468,56 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ ++ /* widescreen is better if 3 columns */ ++ if (n >= 3 && n <= 6 && (m->w.width / m->w.height) > 1) ++ cols = 3; ++ ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid.patch b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid.patch new file mode 100644 index 0000000..2af1072 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaplessgrid/gaplessgrid.patch @@ -0,0 +1,89 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..014a909 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index dc0c861..875d8cd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -292,6 +292,7 @@ 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 gaplessgrid(Monitor *m); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -1510,6 +1511,56 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ ++ /* widescreen is better if 3 columns */ ++ if (n >= 3 && n <= 6 && (m->w.width / m->w.height) > 1) ++ cols = 3; ++ ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { diff --git a/dwl-bak/dwl-patches/patches/gaps/README.md b/dwl-bak/dwl-patches/patches/gaps/README.md new file mode 100644 index 0000000..4925a47 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaps/README.md @@ -0,0 +1,14 @@ +### Description +Adds gaps between clients, providing the ability to disable them at run-time. + +`smartgaps` can also be changed to remove gaps when there is only one client present. + +### Download +- [git branch](https://codeberg.org/bigman/dwl/src/branch/gaps) +- [2024-07-12](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaps/gaps.patch) + +### Authors +- [peesock](https://codeberg.org/bigman) +- [sewn](https://codeberg.org/sewn) +- [Serene Void](https://github.com/serenevoid) +- [Rayan Nakib](https://nakibrayan2.pages.dev) diff --git a/dwl-bak/dwl-patches/patches/gaps/gaps.patch b/dwl-bak/dwl-patches/patches/gaps/gaps.patch new file mode 100644 index 0000000..c025baf --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gaps/gaps.patch @@ -0,0 +1,127 @@ +From 50e3dd4746b6cb719efb9f8213b94ac52a5320d9 Mon Sep 17 00:00:00 2001 +From: peesock +Date: Mon, 24 Jun 2024 20:06:42 -0700 +Subject: [PATCH] gaps! + +Co-authored-by: sewn +Co-authored-by: serenevoid +--- + config.def.h | 4 ++++ + dwl.c | 34 ++++++++++++++++++++++++++-------- + 2 files changed, 30 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..b388b4e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,9 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +@@ -135,6 +138,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, ++ { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, 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 dc0437e..dc851df 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -199,6 +199,7 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -336,6 +337,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -949,6 +951,8 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ ++ m->gaps = gaps; ++ + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { +@@ -2638,7 +2642,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + +@@ -2647,23 +2651,30 @@ tile(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; + + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; +- i = my = ty = 0; ++ i = 0; ++ my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, ++ .width = mw - 2*gappx*e, .height = h}, 0); ++ my += c->geom.height + gappx*e; + } else { ++ r = n - i; ++ h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ ty += c->geom.height + gappx*e; + } + i++; + } +@@ -2686,6 +2697,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ selmon->gaps = !selmon->gaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/genericgaps/README.md b/dwl-bak/dwl-patches/patches/genericgaps/README.md new file mode 100644 index 0000000..f27897d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/genericgaps/README.md @@ -0,0 +1,25 @@ +### Description + +This patch adds gaps around windows and works with any layout (the layout code +does not need to know about the gaps). This patch is a modified version of +[vanitygaps][vanitygaps] patch. + +This works by allowing layout code to place clients normally without gaps, and +then correcting positions and sizes of clients afterwards to add gaps around +them. This approach is very flexible but there is one small downside: you will +always have "outer" gaps (between edges of a monitor and a window) if "inner" +gaps are non-zero. But for me it's not a problem because I always want "outer" +gaps to be as big or bigger than "inner" gaps anyway. + +[vanitygaps]: /dwl/dwl-patches/src/branch/main/patches/vanitygaps + +### Download + +- [0.7 2025-02-11](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps-0.7.patch): + added support for `smartgaps` and `monoclegaps` settings and removed the suck + from `arrange()` function +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/genericgaps/genericgaps-0.7.patch b/dwl-bak/dwl-patches/patches/genericgaps/genericgaps-0.7.patch new file mode 100644 index 0000000..ff27889 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/genericgaps/genericgaps-0.7.patch @@ -0,0 +1,375 @@ +From a11488df97c0592486c3ce86d9c5dbddb0f88524 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Mon, 10 Feb 2025 23:32:59 +0100 +Subject: [PATCH] Add gaps to windows regardless of layout + +--- + config.def.h | 23 ++++++ + dwl.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 221 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..7b50d9d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,14 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int enablegaps = 1; /* 1 means gaps are enabled */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +140,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..9bf4651 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -200,6 +200,11 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int enablegaps; /* enable gaps, used by togglegaps */ ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -246,6 +251,7 @@ typedef struct { + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); + static void arrange(Monitor *m); ++void arrangegaps(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); +@@ -273,6 +279,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +300,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -313,6 +327,7 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); ++static void preparegaps(Monitor *m); + static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); +@@ -327,6 +342,7 @@ 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +356,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +430,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int resizelock = 0; /* do not actually resize during arrange */ ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -515,12 +534,52 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) ++ if (m->lt[m->sellt]->arrange) { ++ preparegaps(m); + m->lt[m->sellt]->arrange(m); ++ arrangegaps(m); ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } + ++void ++arrangegaps(Monitor *m) ++{ ++ Client *c; ++ int n, gaps; ++ ++ if (!m->enablegaps) ++ return; ++ ++ resizelock = 0; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ n++; ++ } ++ ++ gaps = !(smartgaps && n == 1) && ++ (monoclegaps || m->lt[m->sellt]->arrange != monocle); ++ if (gaps) { ++ m->w.width += m->gappih + 2 * m->gappoh; ++ m->w.height += m->gappiv + 2 * m->gappov; ++ } ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (gaps) { ++ c->geom.x += c->mon->gappih + c->mon->gappoh; ++ c->geom.y += c->mon->gappiv + c->mon->gappov; ++ c->geom.width -= c->mon->gappih; ++ c->geom.height -= c->mon->gappiv; ++ } ++ resize(c, c->geom, 0); ++ } ++} ++ + void + arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) + { +@@ -991,6 +1050,12 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->enablegaps = enablegaps; ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1173,6 +1238,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1526,6 +1597,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2034,6 +2182,31 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + ++void ++preparegaps(Monitor *m) ++{ ++ Client *c; ++ int n; ++ ++ if (!m->enablegaps) ++ return; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ n++; ++ } ++ ++ resizelock = 1; ++ ++ if ((smartgaps && n == 1) || (!monoclegaps && m->lt[m->sellt]->arrange == monocle)) ++ return; ++ ++ m->w.width -= m->gappih + 2 * m->gappoh; ++ m->w.height -= m->gappiv + 2 * m->gappov; ++} ++ + void + printstatus(void) + { +@@ -2185,6 +2358,11 @@ resize(Client *c, struct wlr_box geo, int interact) + struct wlr_box *bbox; + struct wlr_box clip; + ++ if (resizelock) { ++ c->geom = geo; ++ return; ++ } ++ + if (!c->mon || !client_surface(c)->mapped) + return; + +@@ -2354,6 +2532,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2741,6 +2929,15 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ if (!selmon) ++ return; ++ selmon->enablegaps = !selmon->enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/genericgaps/genericgaps.patch b/dwl-bak/dwl-patches/patches/genericgaps/genericgaps.patch new file mode 100644 index 0000000..aa62b92 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/genericgaps/genericgaps.patch @@ -0,0 +1,350 @@ +From f2a1b84369266d252fbea57c6f1eb64253617452 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Thu, 12 Dec 2024 22:44:53 +0100 +Subject: [PATCH] Add genericgaps + +--- + config.def.h | 22 +++++++ + dwl.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 190 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..930af12 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,12 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +139,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..c14ae43 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -107,6 +107,7 @@ typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ ++ int interact; + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; +@@ -200,6 +201,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -273,6 +278,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +299,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -320,13 +333,15 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resizeapply(Client *c, struct wlr_box geo, int interact); ++static void resizenoapply(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); + 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +355,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +429,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ ++static void (*resize)(Client *c, struct wlr_box geo, int interact) = resizeapply; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -481,9 +500,25 @@ applyrules(Client *c) + setmon(c, mon, newtags); + } + ++void ++applygaps(Client *c) ++{ ++ struct wlr_box geom = c->geom; ++ ++ if (!c->mon) ++ return; ++ ++ geom.x += c->mon->gappih + c->mon->gappoh; ++ geom.y += c->mon->gappiv + c->mon->gappov; ++ geom.width -= c->mon->gappih; ++ geom.height -= c->mon->gappiv; ++ resize(c, geom, 0); ++} ++ + void + arrange(Monitor *m) + { ++ int save_width, save_height; + Client *c; + + if (!m->wlr_output->enabled) +@@ -515,8 +550,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) ++ if (m->lt[m->sellt]->arrange) { ++ save_width = m->w.width; ++ save_height = m->w.height; ++ if (enablegaps) { ++ m->w.width -= m->gappih + 2 * m->gappoh; ++ m->w.height -= m->gappiv + 2 * m->gappov; ++ } ++ resize = resizenoapply; + m->lt[m->sellt]->arrange(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (enablegaps) ++ applygaps(c); ++ resizeapply(c, c->geom, c->interact); ++ } ++ m->w.width = save_width; ++ m->w.height = save_height; ++ resize = resizeapply; ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -993,6 +1046,11 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ ++ ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { +@@ -1173,6 +1231,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1526,6 +1590,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2180,7 +2321,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resizeapply(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2212,6 +2353,13 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++void ++resizenoapply(Client *c, struct wlr_box geo, int interact) ++{ ++ c->geom = geo; ++ c->interact = interact; ++} ++ + void + run(char *startup_cmd) + { +@@ -2354,6 +2502,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2741,6 +2899,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.47.1 + diff --git a/dwl-bak/dwl-patches/patches/gestures/README.md b/dwl-bak/dwl-patches/patches/gestures/README.md new file mode 100644 index 0000000..0fbe418 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gestures/README.md @@ -0,0 +1,23 @@ +### Description +Add swipe gestures to trigger functions, similar to [libinput-gestures](https://github.com/bulletmark/libinput-gestures/tree/master). It supports the following gestures: `SWIPE_UP`, `SWIPE_DOWN`, `SWIPE_LEFT` and `SWIPE_RIGHT` + +> NOTE: It requires that you have previously applied [pointer-gestures-unstable-v1](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pointer-gestures-unstable-v1) + +```c +static const Gesture gestures[] = { + /* modifier gesture fingers_count function argument */ + { MODKEY, SWIPE_LEFT, 4, shiftview, { .i = 1 } }, + { 0, SWIPE_RIGHT, 4, shiftview, { .i = -1 } }, +}; +``` + +**NOTE:** the example above requires the following patch [shiftview](https://codeberg.org/dwl/dwl-patches/wiki/shiftview) + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/gestures) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/gestures/gestures.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/be3735bc6a5c64ff76c200a8679453bd179be456/gestures/gestures.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/655fd2916c1bcaa022ce6dcdfb370051cf64df66/gestures/gestures.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/gestures/gestures.patch b/dwl-bak/dwl-patches/patches/gestures/gestures.patch new file mode 100644 index 0000000..4f74325 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/gestures/gestures.patch @@ -0,0 +1,169 @@ +From 42f97e88bd901d81b81da61c44a790b583706308 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:18:49 -0500 +Subject: [PATCH] implement gestures + +--- + config.def.h | 9 +++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 77 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8b75564 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,6 +14,8 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static const unsigned int swipe_min_threshold = 0; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -174,3 +176,10 @@ static const Button buttons[] = { + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; ++ ++static const Gesture gestures[] = { ++ // { MODKEY, SWIPE_LEFT, 4, shiftview, { .i = 1 } }, ++ // { MODKEY, SWIPE_RIGHT, 4, shiftview, { .i = -1 } }, ++ { MODKEY, SWIPE_UP, 3, focusstack, {.i = 1} }, ++ { MODKEY, SWIPE_DOWN, 3, focusstack, {.i = -1} }, ++}; +diff --git a/dwl.c b/dwl.c +index ded83e2..5d861e8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -88,6 +88,7 @@ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif ++enum { SWIPE_LEFT, SWIPE_RIGHT, SWIPE_DOWN, SWIPE_UP }; + + typedef union { + int i; +@@ -103,6 +104,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ unsigned int mod; ++ unsigned int motion; ++ unsigned int fingers_count; ++ void (*func)(const Arg *); ++ const Arg arg; ++} Gesture; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -251,6 +260,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static int ongesture(struct wlr_pointer_swipe_end_event *event); + static void swipe_begin(struct wl_listener *listener, void *data); + static void swipe_update(struct wl_listener *listener, void *data); + static void swipe_end(struct wl_listener *listener, void *data); +@@ -416,6 +426,10 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static uint32_t swipe_fingers = 0; ++static double swipe_dx = 0; ++static double swipe_dy = 0; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -435,6 +449,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static const unsigned int abzsquare = swipe_min_threshold * swipe_min_threshold; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -657,6 +673,11 @@ swipe_begin(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_begin_event *event = data; + ++ swipe_fingers = event->fingers; ++ // Reset swipe distance at the beginning of a swipe ++ swipe_dx = 0; ++ swipe_dy = 0; ++ + // Forward swipe begin event to client + wlr_pointer_gestures_v1_send_swipe_begin( + pointer_gestures, +@@ -671,6 +692,11 @@ swipe_update(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_update_event *event = data; + ++ swipe_fingers = event->fingers; ++ // Accumulate swipe distance ++ swipe_dx += event->dx; ++ swipe_dy += event->dy; ++ + // Forward swipe update event to client + wlr_pointer_gestures_v1_send_swipe_update( + pointer_gestures, +@@ -681,11 +707,53 @@ swipe_update(struct wl_listener *listener, void *data) + ); + } + ++int ++ongesture(struct wlr_pointer_swipe_end_event *event) ++{ ++ struct wlr_keyboard *keyboard; ++ uint32_t mods; ++ const Gesture *g; ++ unsigned int motion; ++ unsigned int adx = (int)round(fabs(swipe_dx)); ++ unsigned int ady = (int)round(fabs(swipe_dy)); ++ int handled = 0; ++ ++ if (event->cancelled) { ++ return handled; ++ } ++ ++ // Require absolute distance movement beyond a small thresh-hold ++ if (adx * adx + ady * ady < abzsquare) { ++ return handled; ++ } ++ ++ if (adx > ady) { ++ motion = swipe_dx < 0 ? SWIPE_LEFT : SWIPE_RIGHT; ++ } else { ++ motion = swipe_dy < 0 ? SWIPE_UP : SWIPE_DOWN; ++ } ++ ++ keyboard = wlr_seat_get_keyboard(seat); ++ mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; ++ for (g = gestures; g < END(gestures); g++) { ++ if (CLEANMASK(mods) == CLEANMASK(g->mod) && ++ swipe_fingers == g->fingers_count && ++ motion == g->motion && g->func) { ++ g->func(&g->arg); ++ handled = 1; ++ } ++ } ++ return handled; ++} ++ + void + swipe_end(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_end_event *event = data; + ++ // TODO: should we stop here if the event has been handled? ++ ongesture(event); ++ + // Forward swipe end event to client + wlr_pointer_gestures_v1_send_swipe_end( + pointer_gestures, +-- +2.45.1 diff --git a/dwl-bak/dwl-patches/patches/globalkey/README.md b/dwl-bak/dwl-patches/patches/globalkey/README.md new file mode 100644 index 0000000..d250f92 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/globalkey/README.md @@ -0,0 +1,33 @@ +### Description + +This patch adds the ability to pass keys that are specified in the config header globally, similar to Hyprland's approach. +This might deal with Wayland's lack of global shortcuts. + +Example: +``` +static const PassKeypressRule pass_rules[] = { + ADDPASSRULE("com.obsproject.Studio", XKB_KEY_Home), + ADDPASSRULE("WebCord", XKB_KEY_n), + /* xkb key is case-insensitive */ +}; +``` + +This will pass the `Home` key (alongside with mods) to OBS regardless of what client is currently focused, if any. +The string "com.obsproject.Studio" should match the exact appid of the client. + +To get the appid use [dwlmsg](https://codeberg.org/notchoc/dwlmsg) or run stock dwl from a terminal then launch the needed application inside, dwl will print all the info to the stdout. + +Note that if a popup (like [fuzzel](https://codeberg.org/dnkl/fuzzel)) is focused, no keys will be globally passed. +This is done so these menus don't get closed after hitting some of the global keys. + +## Warning +This patch is a stupid hack, it doesn't work all the time. +Examples: obs needs to be clicked on once before applying global hotkeys. +Electron (discord/webcord/chromium) with wayland backend ignores the very first press. +Other programs might not work at all. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/globalkey) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/globalkey/globalkey.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl-bak/dwl-patches/patches/globalkey/globalkey.patch b/dwl-bak/dwl-patches/patches/globalkey/globalkey.patch new file mode 100644 index 0000000..07d3856 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/globalkey/globalkey.patch @@ -0,0 +1,131 @@ +From f36e3f134c9f14a9821783d9908471ed0bdca0ed Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Fri, 14 Mar 2025 20:05:45 +0200 +Subject: [PATCH] implement globalkey patch + +--- + config.def.h | 8 +++++++ + dwl.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 69 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..25486c8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -122,6 +122,14 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#define ADDPASSRULE(S, K) {.appid = S, .len = LENGTH(S), .key = K} ++static const PassKeypressRule pass_rules[] = { ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_Home), ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_End), ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_F12), ++ ADDPASSRULE("WebCord", XKB_KEY_n), ++}; ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ +diff --git a/dwl.c b/dwl.c +index 4816159..9ad64dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -217,6 +217,12 @@ typedef struct { + int x, y; + } MonitorRule; + ++typedef struct { ++ const char* appid; ++ size_t len; ++ uint32_t key; ++} PassKeypressRule; ++ + typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +@@ -293,6 +299,7 @@ static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); ++static void keypressglobal(struct wlr_surface *last_surface, struct wlr_keyboard *keyboard, struct wlr_keyboard_key_event *event, uint32_t mods, xkb_keysym_t keysym); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); + static void killclient(const Arg *arg); +@@ -1628,6 +1635,12 @@ keypress(struct wl_listener *listener, void *data) + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; ++ struct wlr_surface *last_surface = seat->keyboard_state.focused_surface; ++ struct wlr_xdg_surface *xdg_surface = last_surface ? wlr_xdg_surface_try_from_wlr_surface(last_surface) : NULL; ++ int pass = 0; ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = last_surface ? wlr_xwayland_surface_try_from_wlr_surface(last_surface) : NULL; ++#endif + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +@@ -1662,12 +1675,60 @@ keypress(struct wl_listener *listener, void *data) + if (handled) + return; + ++ /* don't pass when popup is focused ++ * this is better than having popups (like fuzzel or wmenu) closing while typing in a passed keybind */ ++ pass = (xdg_surface && xdg_surface->role != WLR_XDG_SURFACE_ROLE_POPUP) || !last_surface ++#ifdef XWAYLAND ++ || xsurface ++#endif ++ ; ++ /* passed keys don't get repeated */ ++ if (!locked && pass) { ++ for (i = 0; i < nsyms; ++i) ++ keypressglobal(last_surface, &group->wlr_group->keyboard, event, mods, syms[i]); ++ } ++ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); + } + ++void ++keypressglobal(struct wlr_surface *last_surface, struct wlr_keyboard *keyboard, struct wlr_keyboard_key_event *event, uint32_t mods, xkb_keysym_t keysym) ++{ ++ Client *c = NULL, *lastc = focustop(selmon); ++ int reset = false; ++ const char *appid = NULL; ++ ++ for (size_t r = 0; r < LENGTH(pass_rules); r++) { ++ uint32_t rcode = xkb_keysym_to_upper(pass_rules[r].key); ++ uint32_t pcode = xkb_keysym_to_upper(keysym); ++ ++ /* match key only (case insensitive) ignoring mods */ ++ if (rcode == pcode) { ++ wl_list_for_each(c, &clients, link) { ++ if (c && c != lastc) { ++ appid = client_get_appid(c); ++ if (appid && strncmp(appid, pass_rules[r].appid, pass_rules[r].len) == 0) { ++ reset = true; ++ ++ client_notify_enter(client_surface(c), keyboard); ++ client_activate_surface(client_surface(c), 1); ++ wlr_seat_keyboard_send_key(seat, event->time_msec, event->keycode, event->state); ++ ++ goto done; ++ } ++ } ++ } ++ } ++ } ++ ++done: ++ if (reset && last_surface) ++ client_notify_enter(last_surface, keyboard); ++} ++ + void + keypressmod(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/headless/README.md b/dwl-bak/dwl-patches/patches/headless/README.md new file mode 100644 index 0000000..990a5d9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/headless/README.md @@ -0,0 +1,11 @@ +### Description +Implements `swaymsg create_output` command, it allows you to create virtual/headless outputs. But in combination with a VNC server (for example wayvnc), this allows you to essentially have additional monitors, by connecting to the VNC server with an appropiate client (for example on an tablet or laptop). + +If you plan to use wayvnc, you'll need [virtual-pointer](https://codeberg.org/dwl/dwl-patches/wiki/virtual-pointer.-) patch as well + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/v0.5/headless) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0096e49402bc59b4050e12cdb9befb79d0011006/headless/headless.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/headless/headless.patch b/dwl-bak/dwl-patches/patches/headless/headless.patch new file mode 100644 index 0000000..fb01fda --- /dev/null +++ b/dwl-bak/dwl-patches/patches/headless/headless.patch @@ -0,0 +1,130 @@ +From 2e1123af5c7ae4354ec997d59cb36143fb2fdd27 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Mon, 8 Apr 2024 10:23:40 -0500 +Subject: [PATCH] feat: implement headless backend + +--- + config.def.h | 1 + + dwl.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 59 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..f0a2080 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -141,6 +141,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, XKB_KEY_M, create_output, {0} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index ef27a1d..79a63b0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -12,7 +12,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -58,6 +61,9 @@ + #include + #include + #endif ++#if WLR_HAS_X11_BACKEND ++#include ++#endif + + #include "util.h" + +@@ -327,6 +333,8 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void _create_output(struct wlr_backend *backend, void *data); ++static void create_output(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -335,6 +343,7 @@ static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wlr_backend *backend; ++static struct wlr_backend *headless_backend; + static struct wlr_scene *scene; + static struct wlr_scene_tree *layers[NUM_LAYERS]; + static struct wlr_scene_tree *drag_icon; +@@ -2321,6 +2330,16 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + ++ /** ++ * Initialize headless backend ++ */ ++ headless_backend = wlr_headless_backend_create(dpy); ++ if (!headless_backend) { ++ die("Failed to create secondary headless backend"); ++ } else { ++ wlr_multi_backend_add(backend, headless_backend); ++ } ++ + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, +@@ -2746,6 +2765,45 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++_create_output(struct wlr_backend *_backend, void *data) ++{ ++ bool *done = data; ++ if (*done) { ++ return; ++ } ++ ++ if (wlr_backend_is_wl(_backend)) { ++ wlr_wl_output_create(_backend); ++ *done = true; ++ } else if (wlr_backend_is_headless(_backend)) { ++ wlr_headless_add_output(_backend, 1920, 1080); ++ *done = true; ++ } ++#if WLR_HAS_X11_BACKEND ++ else if (wlr_backend_is_x11(backend)) { ++ wlr_x11_output_create(backend); ++ *done = true; ++ } ++#endif ++} ++ ++void ++create_output(const Arg *arg) ++{ ++ bool done = false; ++ ++ if (!wlr_backend_is_multi(backend)) { ++ die("Expected a multi backend"); ++ } ++ ++ wlr_multi_for_each_backend(backend, _create_output, &done); ++ ++ if (!done) { ++ die("Can only create outputs for Wayland, X11 or headless backends"); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.2 + diff --git a/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/README.md b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/README.md new file mode 100644 index 0000000..0b4bebe --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/README.md @@ -0,0 +1,12 @@ +### Description +Hide all clients (and layer surfaces) behind the current client if it is +fullscreen, only the background (layer surfaces at the background layer) will +be shown + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/hide-behind-fullscreen) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch) +- [hide-behind-fullscreen-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch new file mode 100644 index 0000000..de2dfc6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch @@ -0,0 +1,73 @@ +From 78e5c4b42cf125be70814fd65a09cbcf0a18aa7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 10 Apr 2022 22:38:53 -0500 +Subject: [PATCH] hide-behind-fullscreen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 2 +- + dwl.c | 24 +++++++++++++++++++++++- + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..1d5a4c84 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,7 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index a2711f67..56356f29 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -484,7 +484,9 @@ applyrules(Client *c) + void + arrange(Monitor *m) + { +- Client *c; ++ LayerSurface *l; ++ Client *c, *sel = focustop(m); ++ int i; + + if (!m->wlr_output->enabled) + return; +@@ -515,6 +517,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + ++ if (sel && sel->isfullscreen && VISIBLEON(sel, m)) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &sel->mon->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, (sel->isfullscreen && c == sel) ++ || !sel->isfullscreen); ++ } ++ } ++ if (!sel || (!sel->isfullscreen && VISIBLEON(sel, m))) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &m->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 1); ++ } ++ } ++ + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch new file mode 100644 index 0000000..f82d0e6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch @@ -0,0 +1,73 @@ +From ac1537f068ea626f1984803ed8db08faf7943b18 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 10 Apr 2022 22:38:53 -0500 +Subject: [PATCH] hide-behind-fullscreen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 2 +- + dwl.c | 24 +++++++++++++++++++++++- + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..1d5a4c84 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,7 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index ad21e1ba..f5395fe6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -505,7 +505,9 @@ applyrules(Client *c) + void + arrange(Monitor *m) + { +- Client *c; ++ LayerSurface *l; ++ Client *c, *sel = focustop(m); ++ int i; + + if (!m->wlr_output->enabled) + return; +@@ -536,6 +538,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + ++ if (sel && sel->isfullscreen && VISIBLEON(sel, m)) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &sel->mon->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, (sel->isfullscreen && c == sel) ++ || !sel->isfullscreen); ++ } ++ } ++ if (!sel || (!sel->isfullscreen && VISIBLEON(sel, m))) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &m->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 1); ++ } ++ } ++ + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/hide-behind-monocle/README.md b/dwl-bak/dwl-patches/patches/hide-behind-monocle/README.md new file mode 100644 index 0000000..507d2e1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-monocle/README.md @@ -0,0 +1,10 @@ +### Description +Hide all clients behind the focused one in the monocle layout + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/hide-behind-monocle) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-monocle/hide-behind-monocle.patch) +- [hide-behind-monocle-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch b/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch new file mode 100644 index 0000000..76b6e96 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch @@ -0,0 +1,332 @@ +From 9bb408e6921b582dbcf5adfc36a7618086219998 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:25:16 -0600 +Subject: [PATCH] hide behind monocle +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 55 insertions(+), 32 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a2711f67..70687178 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -288,10 +288,11 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +-static Client *focustop(Monitor *m); ++static Client *focustop(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void hidebehindmonocle(Monitor *m); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -497,7 +498,7 @@ arrange(Monitor *m) + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, +- (c = focustop(m)) && c->isfullscreen); ++ (c = focustop(m, 0)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + +@@ -750,7 +751,7 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + printstatus(); + } + +@@ -1185,7 +1186,7 @@ void + destroydragicon(struct wl_listener *listener, void *data) + { + /* Focus enter isn't sent during drag, so refocus the focused node. */ +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -1220,7 +1221,7 @@ destroylock(SessionLock *lock, int unlock) + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + +- focusclient(focustop(selmon), 0); ++ focusclient(focustop(selmon, 0), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +@@ -1249,7 +1250,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +@@ -1366,6 +1367,7 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ hidebehindmonocle(c->mon); + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are +@@ -1420,14 +1422,14 @@ focusmon(const Arg *arg) + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void + focusstack(const Arg *arg) + { + /* Focus the next or previous client (in tiling order) on selmon */ +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { +@@ -1453,12 +1455,15 @@ focusstack(const Arg *arg) + * will focus the topmost client of this mon, when actually will + * only return that client */ + Client * +-focustop(Monitor *m) ++focustop(Monitor *m, int onlytiled) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (VISIBLEON(c, m)) { ++ if (onlytiled && c->isfloating) ++ continue; + return c; ++ } + } + return NULL; + } +@@ -1515,6 +1520,25 @@ handlesig(int signo) + } + } + ++void ++hidebehindmonocle(Monitor *m) ++{ ++ Client *c; ++ if (m && m->lt[m->sellt]->arrange == monocle) { ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m) && c->isfloating); ++ } ++ ++ c = NULL; ++ ++ /* Enable top tiled client, fullscreen is considered tiled */ ++ if ((c = focustop(m, 1))) ++ wlr_scene_node_set_enabled(&c->scene->node, 1); ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1653,7 +1677,7 @@ keyrepeat(void *data) + void + killclient(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + client_send_close(sel); + } +@@ -1780,8 +1804,7 @@ monocle(Monitor *m) + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); ++ hidebehindmonocle(m); + } + + void +@@ -2049,7 +2072,7 @@ printstatus(void) + if (c->isurgent) + urg |= c->tags; + } +- if ((c = focustop(m))) { ++ if ((c = focustop(m, 0))) { + title = client_get_title(c); + appid = client_get_appid(c); + printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +@@ -2401,7 +2424,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void +@@ -2670,12 +2693,12 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2683,7 +2706,7 @@ tag(const Arg *arg) + void + tagmon(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setmon(sel, dirtomon(arg->i), 0); + } +@@ -2725,7 +2748,7 @@ tile(Monitor *m) + void + togglefloating(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +@@ -2734,7 +2757,7 @@ togglefloating(const Arg *arg) + void + togglefullscreen(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setfullscreen(sel, !sel->isfullscreen); + } +@@ -2743,12 +2766,12 @@ void + toggletag(const Arg *arg) + { + uint32_t newtags; +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2761,7 +2784,7 @@ toggleview(const Arg *arg) + return; + + selmon->tagset[selmon->seltags] = newtagset; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2785,7 +2808,7 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data) + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2802,7 +2825,7 @@ unmapnotify(struct wl_listener *listener, void *data) + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + } else { + wl_list_remove(&c->link); +@@ -2883,7 +2906,7 @@ updatemons(struct wl_listener *listener, void *data) + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ +- if ((c = focustop(m)) && c->isfullscreen) ++ if ((c = focustop(m, 0)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, +@@ -2903,7 +2926,7 @@ updatemons(struct wl_listener *listener, void *data) + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); +@@ -2925,7 +2948,7 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); +- if (c == focustop(c->mon)) ++ if (c == focustop(c->mon, 0)) + printstatus(); + } + +@@ -2935,7 +2958,7 @@ urgent(struct wl_listener *listener, void *data) + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); +- if (!c || c == focustop(selmon)) ++ if (!c || c == focustop(selmon, 0)) + return; + + c->isurgent = 1; +@@ -2953,7 +2976,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(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -3024,7 +3047,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3141,7 +3164,7 @@ sethints(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); +- if (c == focustop(selmon)) ++ if (c == focustop(selmon, 0)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch b/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch new file mode 100644 index 0000000..dfe23dd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch @@ -0,0 +1,332 @@ +From 8022376ee59d616831271dbc9f289a8bfd4fedda Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:25:16 -0600 +Subject: [PATCH] hide behind monocle +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 55 insertions(+), 32 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..cad3b0b2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -285,10 +285,11 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +-static Client *focustop(Monitor *m); ++static Client *focustop(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void hidebehindmonocle(Monitor *m); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -518,7 +519,7 @@ arrange(Monitor *m) + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, +- (c = focustop(m)) && c->isfullscreen); ++ (c = focustop(m, 0)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + +@@ -807,7 +808,7 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + printstatus(); + } + +@@ -1243,7 +1244,7 @@ void + destroydragicon(struct wl_listener *listener, void *data) + { + /* Focus enter isn't sent during drag, so refocus the focused node. */ +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + wl_list_remove(&listener->link); + } +@@ -1280,7 +1281,7 @@ destroylock(SessionLock *lock, int unlock) + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + +- focusclient(focustop(selmon), 0); ++ focusclient(focustop(selmon, 0), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +@@ -1309,7 +1310,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +@@ -1420,6 +1421,7 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ hidebehindmonocle(c->mon); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +@@ -1473,14 +1475,14 @@ focusmon(const Arg *arg) + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void + focusstack(const Arg *arg) + { + /* Focus the next or previous client (in tiling order) on selmon */ +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { +@@ -1506,12 +1508,15 @@ focusstack(const Arg *arg) + * will focus the topmost client of this mon, when actually will + * only return that client */ + Client * +-focustop(Monitor *m) ++focustop(Monitor *m, int onlytiled) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (VISIBLEON(c, m)) { ++ if (onlytiled && c->isfloating) ++ continue; + return c; ++ } + } + return NULL; + } +@@ -1557,6 +1562,25 @@ handlesig(int signo) + quit(NULL); + } + ++void ++hidebehindmonocle(Monitor *m) ++{ ++ Client *c; ++ if (m && m->lt[m->sellt]->arrange == monocle) { ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m) && c->isfloating); ++ } ++ ++ c = NULL; ++ ++ /* Enable top tiled client, fullscreen is considered tiled */ ++ if ((c = focustop(m, 1))) ++ wlr_scene_node_set_enabled(&c->scene->node, 1); ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1695,7 +1719,7 @@ keyrepeat(void *data) + void + killclient(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + client_send_close(sel); + } +@@ -1824,8 +1848,7 @@ monocle(Monitor *m) + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); ++ hidebehindmonocle(m); + } + + void +@@ -2092,7 +2115,7 @@ printstatus(void) + if (c->isurgent) + urg |= c->tags; + } +- if ((c = focustop(m))) { ++ if ((c = focustop(m, 0))) { + printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); + printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); + printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +@@ -2406,7 +2429,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void +@@ -2680,12 +2703,12 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2693,7 +2716,7 @@ tag(const Arg *arg) + void + tagmon(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setmon(sel, dirtomon(arg->i), 0); + } +@@ -2735,7 +2758,7 @@ tile(Monitor *m) + void + togglefloating(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +@@ -2744,7 +2767,7 @@ togglefloating(const Arg *arg) + void + togglefullscreen(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setfullscreen(sel, !sel->isfullscreen); + } +@@ -2753,12 +2776,12 @@ void + toggletag(const Arg *arg) + { + uint32_t newtags; +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2771,7 +2794,7 @@ toggleview(const Arg *arg) + return; + + selmon->tagset[selmon->seltags] = newtagset; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2795,7 +2818,7 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data) + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2812,7 +2835,7 @@ unmapnotify(struct wl_listener *listener, void *data) + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + } else { + wl_list_remove(&c->link); +@@ -2893,7 +2916,7 @@ updatemons(struct wl_listener *listener, void *data) + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ +- if ((c = focustop(m)) && c->isfullscreen) ++ if ((c = focustop(m, 0)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, +@@ -2913,7 +2936,7 @@ updatemons(struct wl_listener *listener, void *data) + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); +@@ -2935,7 +2958,7 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); +- if (c == focustop(c->mon)) ++ if (c == focustop(c->mon, 0)) + printstatus(); + } + +@@ -2945,7 +2968,7 @@ urgent(struct wl_listener *listener, void *data) + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); +- if (!c || c == focustop(selmon)) ++ if (!c || c == focustop(selmon, 0)) + return; + + c->isurgent = 1; +@@ -2963,7 +2986,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(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -3034,7 +3057,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3145,7 +3168,7 @@ sethints(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); +- if (c == focustop(selmon)) ++ if (c == focustop(selmon, 0)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/hide_vacant_tags/README.md b/dwl-bak/dwl-patches/patches/hide_vacant_tags/README.md new file mode 100644 index 0000000..fae2a4d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide_vacant_tags/README.md @@ -0,0 +1,11 @@ +### Description + +Prevent [bar](/dwl/dwl-patches/wiki/bar) from drawing tags with no clients (i.e. vacant). +It also stops drawing empty rectangles on the bar for non-vacant tags as there is no need anymore to distinguish vacant tags and it offers a more visible contrast than if there were filled/empty rectangles. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hide_vacant_tags/hide_vacant_tags.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl-bak/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch b/dwl-bak/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch new file mode 100644 index 0000000..ae3966e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch @@ -0,0 +1,69 @@ +From 54b75630d9f93bd1a8ab5949df64f086043e96eb Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 13 Dec 2024 17:31:33 +0300 +Subject: [PATCH] hide vacant tags + +https://dwm.suckless.org/patches/hide_vacant_tags/ +--- + dwl.c | 21 +++++++++++++-------- + 1 file changed, 13 insertions(+), 8 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 1e199f3..24d4b20 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -721,7 +721,7 @@ bufrelease(struct wl_listener *listener, void *data) + void + buttonpress(struct wl_listener *listener, void *data) + { +- unsigned int i = 0, x = 0; ++ unsigned int i = 0, x = 0, occ = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; +@@ -751,9 +751,16 @@ buttonpress(struct wl_listener *listener, void *data) + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; +- do ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != selmon) ++ continue; ++ occ |= c->tags == TAGMASK ? 0 : c->tags; ++ } ++ do { ++ if (!(occ & 1 << i || selmon->tagset[selmon->seltags] & 1 << i)) ++ continue; + x += TEXTW(selmon, tags[i]); +- while (cx >= x && ++i < LENGTH(tags)); ++ } while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; +@@ -1530,20 +1537,18 @@ drawbar(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; +- occ |= c->tags; ++ occ |= c->tags == TAGMASK ? 0 : c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { ++ if(!(occ & 1 << i || m->tagset[m->seltags] & 1 << i)) ++ continue; + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); +- if (occ & 1 << i) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, +- m == selmon && c && c->tags & 1 << i, +- urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); +-- +2.47.1 + diff --git a/dwl-bak/dwl-patches/patches/hiderule/README.md b/dwl-bak/dwl-patches/patches/hiderule/README.md new file mode 100644 index 0000000..bed2475 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hiderule/README.md @@ -0,0 +1,9 @@ +### Description +Adds a `ishidden` option to client rules, that allows hiding any matching clients entirely. + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/hiderule) +- [yyyy-mm-dd](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/hiderule/hiderule.patch) + +### Authors +- [minego](https://codeberg.org/minego) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/hiderule/hiderule.patch b/dwl-bak/dwl-patches/patches/hiderule/hiderule.patch new file mode 100644 index 0000000..8ed1a84 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hiderule/hiderule.patch @@ -0,0 +1,57 @@ +From fb48ec754d63b3d8e40fff2d047050675887d7f4 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 12:53:18 -0600 +Subject: [PATCH] hiderule + +--- + config.def.h | 9 ++++++--- + dwl.c | 4 ++++ + 2 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..c476057 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,11 +21,14 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor ishidden */ + /* examples: +- { "Gimp", NULL, 0, 1, -1 }, ++ { "Gimp", NULL, 0, 1, -1, 0 }, + */ +- { "firefox", NULL, 1 << 8, 0, -1 }, ++ { "firefox", NULL, 1 << 8, 0, -1, 0 }, ++ ++ { "firefox", "Sharing Indicator", ++ 0, 1, -1, 1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 5867b0c..799fd89 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -228,6 +228,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int ishidden; + } Rule; + + typedef struct { +@@ -464,6 +465,9 @@ applyrules(Client *c) + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; ++ if (r->ishidden) { ++ mon = NULL; ++ } + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/hot-reload/README.md b/dwl-bak/dwl-patches/patches/hot-reload/README.md new file mode 100644 index 0000000..e387ca4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hot-reload/README.md @@ -0,0 +1,110 @@ +### Description +Enables hot-reloading of dwl; meaning almost all logic can be changed at runtime. +This obviously requires some black magic so for now there's a glibc 2.0 or later +dependency to this. +In particular this allows for every option in config.h to be changed at runtime. + +#### Reloading +To reload rebuild dwl.so, perhaps reinstall it and then run trigger reload function (bound to Mod+Shift+R by default). +This currently calls `notify-send` in order to inform you of a reloading taking place. +So in short: +1. make changes to `config.h` or `dwl.c` +2. run `make` to rebuild dwl.so +3. run `sudo make install` to reinstall dwl + + +#### Limitations +Reloading the compositor will replace all functionality except for `main`, `setup`, `run` and the reload logic. +Note that you're responsible yourself for reloading ressources like fonts, which may only get acquired once. +A lot of components of dwl will also only get run on a trigger (the tiling for example). +So not every change will be immediate. +Furthermore, any patch adding more global state to dwl cannot currently be reloaded properly since +we keep state in the cold part. These patches will still work and their functionality will (hopefully) be +reloadable but you will need to restart the compositor once. + +#### Notes +##### reduce compile errors +This patch triggers `-Wpedantic` a bunch (I don't think there's a way around this, `dlsym` yields `void*` pointers to functions). +This will show a lot of warnings but cause no errors. +So you may want to disable this compile option in order to get readable compiler output. +##### runtime dependencies +This does depend on you having a notification daemon like `dunst` or `mako` running as well as +having `notify-send` installed in order for the compositor to inform you of the reload. + + +#### How? +Most of all dwl functionality is moved into a shared object file `dwl.so`, which can be reloaded at runtime. + +#### How do I make this work with other patches? +Most patches should already put everything in more or less the correct place but if they don't, then here is +where you learn how to fix it. + +The concept itself is quite simple. We compile dwl.c twice once normally and once with the `HOT` macro defined. +The first run will yield the executable and the second will yield a shared object file to be reloaded at runtime. +From the cold part there are some newly available macros: +> symbol names are written as-is, never as string literals +* `TSYM(T, s)` dynamically loads the symbol `s` with type `T` from the shared object file use this if you need to call functions in the cold part (i.e. the `setup` function). +* `CSYM(T, v)` dynamically accesses the value of the symbol `v` of type `T` from the shared object. Use this to query values from config.h for example. +* `LISTEN_GLOBAL(E, L)` is similar to the `LISTEN` macro. `E` is an event and `L` the name of a global +listener. Current implementation is a bit messy and I may fix it if someone bothers me about it. +* `UNLISTEN(L)` takes a listener and unregisteres it. This is important for reloading. + +When adding new code there are some considerations to be made. Since dwl decorates all symbols with `static` by default, we cannot access them as-is. +C's macro system is a bit too powerful though and we use this to our advantage. We will repeatedly define and +undefine a macro called `static` in order to replace the `static` keyword inside some sections. +This allows us to do less refactoring and preserve a lot of the original patch compatability since we're only +strategically adding lines. We're tring to be as minimally invasive as we can. +As a general guide: +* global state should be global for the cold part and `extern` in the cold part meaning it should be inside a block like this: + ```C + #ifdef HOT + #define static extern + #else + #define static + #endif + ... // your global variables go here + #undef static + ``` +* function declarations should be visible in the hot part but not included in the cold part meaning they should be enclosed like this: + ```C + #ifdef HOT + #define static + ... // your function declarations go here + #undef static + #endif + ``` +* static data like the event handler structs in the current `main` branch are a bit more difficult but we will let them reside inside the hot part. +Thus, we enclose them the same way we do functions: + ```C + #ifdef HOT + #define static + ... // your struct wl_listener event handlers go here + #undef static + #endif + ``` +* function definitions should go in the hot part, so they need to be inside a big block like this: + ```C + #ifdef HOT + ... // function definitions here + #endif +* enfore use of the `LISTEN_GLOBAL` and `UNLISTEN` macros (I know this sucks but what can I do, I need to get +access to the callbacks somehow). So you want + * `wl_list_remove(listener.link)` to become `UNLISTEN(listener)` and + * `wl_signal_add(event, global_listener)` to become `LISTEN_GLOBAL(event, global_listener)`. +* Make sure that any patch you're using also uses static everywhere. +* If a patch adds any config variables that are accessed in the cold part (i.e. probably `setup`), +then you'll have to manually remove the `static` keyword from them. + +Note that usually you do not have to create the big `#ifdef` blocks yourself. +There is for example already a huge `#ifdef HOT`-delimited codeblock at the bottom +of dwl.c where all the function definitions go. + +If you have any troubles, feel free to reach out. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload-0.7.patch) +- [main 2025-05-30](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload.patch) +- find the repo for the patch [here](/Sivecano/dwl/src/branch/hot-reload) +### Authors +- [Sivecano](https://codeberg.org/Sivecano) +- Sérécano at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl-bak/dwl-patches/patches/hot-reload/hot-reload-0.7.patch b/dwl-bak/dwl-patches/patches/hot-reload/hot-reload-0.7.patch new file mode 100644 index 0000000..1611684 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hot-reload/hot-reload-0.7.patch @@ -0,0 +1,770 @@ +From 559c635056f23d55df3f83c12d1201a7328f648f Mon Sep 17 00:00:00 2001 +From: Sivecano +Date: Sun, 26 Jan 2025 18:30:02 +0100 +Subject: [PATCH] redo hot-reloading in one file + +--- + Makefile | 19 ++- + config.def.h | 5 +- + dwl.c | 337 ++++++++++++++++++++++++++++++++++++++++++++------- + util.c | 34 ++++++ + util.h | 6 + + 5 files changed, 351 insertions(+), 50 deletions(-) + +diff --git a/Makefile b/Makefile +index 3358bae..e7ee9ff 100644 +--- a/Makefile ++++ b/Makefile +@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + + # CFLAGS / LDFLAGS + PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -fPIC -rdynamic + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +-all: dwl ++all: dwl dwl.so + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c config.mk cursor-shape-v1-protocol.h \ ++ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +@@ -49,7 +52,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h *.so + + dist: clean + mkdir -p dwl-$(VERSION) +@@ -63,6 +66,8 @@ install: dwl + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl ++ mkdir -p $(DESTDIR)$(PREFIX)/lib ++ install -m 744 dwl.so $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 +@@ -70,9 +75,13 @@ install: dwl + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + uninstall: +- rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ ++ rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(PREFIX)/lib/dwl.so $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + + .SUFFIXES: .c .o + .c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< ++ ++.SUFFIXES: .c .so ++.c.so: ++ $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -shared -DHOT $< +diff --git a/config.def.h b/config.def.h +index 22d2171..6e3dda1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,7 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); ++const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); +@@ -18,7 +18,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + #define TAGCOUNT (9) + + /* logging */ +-static int log_level = WLR_ERROR; ++int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, reload, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index def2562..1c9ab67 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,15 @@ + /* + * See LICENSE file for copyright and license details. + */ ++ ++/* stuff for hot-reload */ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++ + #include + #include + #include +@@ -67,6 +76,7 @@ + #include + #endif + ++ + #include "util.h" + + /* macros */ +@@ -77,8 +87,34 @@ + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +-#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++ ++#define SYM(a) dlsym(dwl_module, #a) ++#define TSYM(T, a) ((T)SYM(a)) ++#define CSYM(T, a) *(TSYM(T*, a)) ++ ++#define LISTEN(E, L, H) do { \ ++ (L)->notify = SYM(H); \ ++ listeners = append_listener((L), listeners); \ ++ wl_signal_add((E), (L)); \ ++ } while(0) ++ ++#define LISTEN_GLOBAL(E, L) do { \ ++ struct wl_listener* l = SYM(L); \ ++ listeners = append_listener(l, listeners); \ ++ wl_signal_add((E), l); \ ++ } while (0) ++ ++#define LISTEN_STATIC(E, H) do { \ ++ struct wl_listener* _l = malloc(sizeof(struct wl_listener)); \ ++ _l->notify = SYM(H); \ ++ listeners = append_listener(_l, listeners); \ ++ wl_signal_add((E), _l); \ ++ } while (0) ++ ++#define UNLISTEN(L) do { \ ++ wl_list_remove(&(L)->link); \ ++ listeners = remove_listener((L), listeners);\ ++ } while (0) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -242,6 +278,9 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++#define static ++ ++#ifdef HOT + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -253,7 +292,18 @@ static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void cleanup(void); ++ ++#undef static ++#define static ++#ifdef HOT ++ + static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +@@ -321,7 +371,18 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void run(char *startup_cmd); ++ ++#ifdef HOT ++#undef static ++#define static ++ + 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); +@@ -332,7 +393,18 @@ static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void setup(void); ++ ++#ifdef HOT ++#undef static ++#define static ++ + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -356,6 +428,16 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); + ++#endif ++ ++#ifdef HOT ++ #undef static ++ #define static extern ++#else ++ #undef static ++ #define static ++#endif ++ + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; +@@ -400,7 +482,9 @@ static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; ++#ifdef HOT + static struct wl_listener lock_listener = {.notify = locksession}; ++#endif + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; +@@ -426,6 +510,33 @@ static struct wlr_xwayland *xwayland; + static xcb_atom_t netatom[NetLast]; + #endif + ++/* undoes the shadowing of static from above */ ++#undef static ++ ++/* this is where we put global hot-reload state */ ++#ifdef HOT ++#define COLD extern ++#else ++#define COLD ++ ++static void* load(void); ++static const char* get_module_path(void); ++ ++#endif ++ ++COLD void * dwl_module = NULL; ++COLD void * last_module = NULL; ++COLD struct listens* listeners = NULL; ++COLD void reload(const Arg* arg); ++ ++#ifndef HOT ++static char* runpath; ++ ++#endif ++ ++ ++#ifdef HOT ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + +@@ -673,6 +784,8 @@ checkidleinhibitor(struct wlr_surface *exclude) + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); + } + ++#endif ++ + void + cleanup(void) + { +@@ -687,7 +800,7 @@ cleanup(void) + } + wlr_xcursor_manager_destroy(cursor_mgr); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ TSYM(void (*)(struct wl_listener*, void*), destroykeyboardgroup)(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ +@@ -699,6 +812,8 @@ cleanup(void) + wlr_scene_node_destroy(&scene->tree.node); + } + ++#ifdef HOT ++ + void + cleanupmon(struct wl_listener *listener, void *data) + { +@@ -712,10 +827,10 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + +- wl_list_remove(&m->destroy.link); +- wl_list_remove(&m->frame.link); ++ UNLISTEN(&m->destroy); ++ UNLISTEN(&m->frame); + wl_list_remove(&m->link); +- wl_list_remove(&m->request_state.link); ++ UNLISTEN(&m->request_state); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +@@ -848,7 +963,7 @@ commitpopup(struct wl_listener *listener, void *data) + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + } + + void +@@ -1179,8 +1294,8 @@ destroydecoration(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + +- wl_list_remove(&c->destroy_decoration.link); +- wl_list_remove(&c->set_decoration_mode.link); ++ UNLISTEN(&c->destroy_decoration); ++ UNLISTEN(&c->set_decoration_mode); + } + + void +@@ -1205,9 +1320,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); +- wl_list_remove(&l->destroy.link); +- wl_list_remove(&l->unmap.link); +- wl_list_remove(&l->surface_commit.link); ++ UNLISTEN(&l->destroy); ++ UNLISTEN(&l->unmap); ++ UNLISTEN(&l->surface_commit); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +@@ -1226,9 +1341,9 @@ destroylock(SessionLock *lock, int unlock) + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +- wl_list_remove(&lock->new_surface.link); +- wl_list_remove(&lock->unlock.link); +- wl_list_remove(&lock->destroy.link); ++ UNLISTEN(&lock->new_surface); ++ UNLISTEN(&lock->unlock); ++ UNLISTEN(&lock->destroy); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; +@@ -1242,7 +1357,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; +- wl_list_remove(&m->destroy_lock_surface.link); ++ UNLISTEN(&m->destroy_lock_surface); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; +@@ -1262,22 +1377,22 @@ destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); +- wl_list_remove(&c->destroy.link); +- wl_list_remove(&c->set_title.link); +- wl_list_remove(&c->fullscreen.link); ++ UNLISTEN(&c->destroy); ++ UNLISTEN(&c->set_title); ++ UNLISTEN(&c->fullscreen); + #ifdef XWAYLAND + if (c->type != XDGShell) { +- wl_list_remove(&c->activate.link); +- wl_list_remove(&c->associate.link); +- wl_list_remove(&c->configure.link); +- wl_list_remove(&c->dissociate.link); +- wl_list_remove(&c->set_hints.link); ++ UNLISTEN(&c->activate); ++ UNLISTEN(&c->associate); ++ UNLISTEN(&c->configure); ++ UNLISTEN(&c->dissociate); ++ UNLISTEN(&c->set_hints); + } else + #endif + { +- wl_list_remove(&c->commit.link); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->commit); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + free(c); + } +@@ -1292,7 +1407,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data) + active_constraint = NULL; + } + +- wl_list_remove(&pointer_constraint->destroy.link); ++ UNLISTEN(&pointer_constraint->destroy); + free(pointer_constraint); + } + +@@ -1306,8 +1421,8 @@ destroysessionlock(struct wl_listener *listener, void *data) + void + destroysessionmgr(struct wl_listener *listener, void *data) + { +- wl_list_remove(&lock_listener.link); +- wl_list_remove(&listener->link); ++ UNLISTEN(&lock_listener); ++ UNLISTEN(listener); + } + + void +@@ -1315,10 +1430,10 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + { + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); ++ UNLISTEN(&group->key); ++ UNLISTEN(&group->modifiers); ++ UNLISTEN(&group->destroy); + wlr_keyboard_group_destroy(group->wlr_group); +- wl_list_remove(&group->key.link); +- wl_list_remove(&group->modifiers.link); +- wl_list_remove(&group->destroy.link); + free(group); + } + +@@ -2212,6 +2327,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++#else /*HOT*/ ++ + void + run(char *startup_cmd) + { +@@ -2251,11 +2368,11 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ TSYM(void (*)(void), printstatus)(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +- selmon = xytomon(cursor->x, cursor->y); ++ selmon = TSYM(Monitor* (*)(double x, double y), xytomon)(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully +@@ -2271,6 +2388,9 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++#endif ++#ifdef HOT ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2428,17 +2548,19 @@ setsel(struct wl_listener *listener, void *data) + wlr_seat_set_selection(seat, event->source, event->serial); + } + ++#else /*HOT*/ ++ + void + setup(void) + { + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; +- struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = SYM(handlesig)}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +- wlr_log_init(log_level, NULL); ++ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ +@@ -2454,7 +2576,7 @@ setup(void) + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); +- root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); ++ root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, TSYM(float*, rootcolor)); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); +@@ -2550,7 +2672,7 @@ setup(void) + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); +- wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); ++ LISTEN_GLOBAL(&session_lock_mgr->events.new_lock, lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); +@@ -2620,7 +2742,7 @@ setup(void) + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +- kb_group = createkeyboardgroup(); ++ kb_group = TSYM(KeyboardGroup *(*)(void), createkeyboardgroup)(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); +@@ -2647,6 +2769,9 @@ setup(void) + #endif + } + ++#endif ++#ifdef HOT ++ + void + spawn(const Arg *arg) + { +@@ -3121,8 +3246,8 @@ void + dissociatex11(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, dissociate); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + + xcb_atom_t +@@ -3185,17 +3310,141 @@ xwaylandready(struct wl_listener *listener, void *data) + } + #endif + ++#else /* HOT */ ++void* ++load(void) ++{ ++ const char* path = get_module_path(); ++ char load[PATH_MAX] = "/tmp/dwl.soXXXXXX"; ++ void* new; ++ ++ if (!path) { ++ fprintf(stderr, "cannot find dwl.so\n"); ++ } ++ ++ do { ++ mktemp(load); ++ errno = 0; ++ symlink(path, load); ++ } while(errno == EEXIST); ++ ++ new = dlopen(load, RTLD_NOW|RTLD_LOCAL); ++ ++ unlink(load); ++ if (new == NULL) ++ fprintf(stderr, "error while loading %s: %s\n", path, dlerror()); ++ else ++ printf("loaded: %s\n", path); ++ ++ return new; ++} ++ ++const char * ++get_module_path(void) { ++ char home[PATH_MAX]; ++ strcpy(home, getenv("HOME")); ++ strcat(home, "/.local/lib"); ++ const char* abspaths[] = {".", home, "/usr/share/lib", "/usr/local/lib", "/usr/local/share/lib"}; ++ const char* relpaths[] = {"", "/../lib"}; ++ char paths[LENGTH(abspaths) + LENGTH(relpaths)][PATH_MAX]; ++ static char out[PATH_MAX] = "./"; ++ ++ for (size_t i = 0; i < LENGTH(abspaths); i++) ++ realpath(abspaths[i], paths[i]); ++ ++ for (size_t i = 0; i < LENGTH(relpaths); i++) ++ { ++ char tmp[PATH_MAX]; ++ strcpy(tmp, runpath); ++ strcat(tmp, relpaths[i]); ++ realpath(tmp, paths[LENGTH(abspaths) + i]); ++ } ++ ++ ++ ++ for (size_t i = 0; i < LENGTH(paths); i++) ++ { ++ char tmp[PATH_MAX]; ++ printf("checking path: %s\n", paths[i]); ++ strcpy(tmp, paths[i]); ++ strcat(tmp, "/dwl.so"); ++ if (access(tmp, F_OK|R_OK) == 0) ++ { ++ strcpy(out, tmp); ++ return out; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++reload(const Arg* arg) ++{ ++ char* error; ++ void* new; ++ size_t i = 0; ++ ++ // deinitialize previous module ++ if (last_module) { ++ // dlclose(last_module); ++ last_module = NULL; ++ } ++ ++ wlr_log(WLR_INFO, "reloading"); ++ ++ new = load(); ++ ++ if (new == NULL) ++ { ++ wlr_log(WLR_ERROR, "couldn't load new dwl module from %s", get_module_path()); ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "failed to reload dwl", NULL); ++ return; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- listens ---------"); ++ for(listens* a = listeners; a != NULL; a = a->next) ++ { ++ Dl_info info; ++ void* old = a->listen->notify; ++ dladdr(a->listen->notify, &info); ++ a->listen->notify = dlsym(new, info.dli_sname); ++ if ((error = dlerror()) != NULL){ ++ fprintf(stderr, "reload failure: %s", error); ++ a->listen->notify = old; ++ return; ++ } ++ wlr_log(WLR_DEBUG, "replaced listener: %s", info.dli_sname); ++ i++; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- done! ---------"); ++ wlr_log(WLR_DEBUG, "replaced %zu listeners", i); ++ ++ last_module = dwl_module; ++ dwl_module = new; ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "reloaded dwl", NULL); ++ ++} ++ + int + main(int argc, char *argv[]) + { + char *startup_cmd = NULL; + int c; + ++ runpath = dirname(argv[0]); ++ dwl_module = load(); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') +- log_level = WLR_DEBUG; ++ CSYM(enum wlr_log_importance, log_level) = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else +@@ -3215,3 +3464,5 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++#endif +diff --git a/util.c b/util.c +index 51130af..2000731 100644 +--- a/util.c ++++ b/util.c +@@ -49,3 +49,37 @@ fd_set_nonblock(int fd) { + + return 0; + } ++ ++struct listens* ++append_listener(struct wl_listener* new, struct listens* list) ++{ ++ struct listens* l = malloc(sizeof(struct listens)); ++ l->listen = new; ++ l->next = list; ++ return l; ++} ++ ++struct listens* ++remove_listener(struct wl_listener* l, struct listens* ls) ++{ ++ struct listens* out = ls; ++ struct listens* f = NULL; ++ for(struct listens* last = NULL; ls != NULL; ls = ls->next) ++ { ++ if (ls->listen == l) ++ { ++ if (last != NULL) ++ last->next = ls->next; ++ else ++ out = ls->next; ++ ++ f = ls; ++ } ++ else ++ last = ls; ++ } ++ ++ free(f); ++ ++ return out; ++} +diff --git a/util.h b/util.h +index 226980d..11aab34 100644 +--- a/util.h ++++ b/util.h +@@ -1,5 +1,11 @@ + /* See LICENSE.dwm file for copyright and license details. */ ++typedef struct listens { ++ struct wl_listener* listen; ++ struct listens* next; ++} listens; + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); + int fd_set_nonblock(int fd); ++struct listens* append_listener(struct wl_listener* l, struct listens* ls); ++struct listens* remove_listener(struct wl_listener* l, struct listens* ls); +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/hot-reload/hot-reload.patch b/dwl-bak/dwl-patches/patches/hot-reload/hot-reload.patch new file mode 100644 index 0000000..627fa88 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/hot-reload/hot-reload.patch @@ -0,0 +1,1029 @@ +From 44ce31430dd7d2c0a6c1dc29534f22fb33b25bca Mon Sep 17 00:00:00 2001 +From: Sivecano +Date: Sun, 26 Jan 2025 18:30:02 +0100 +Subject: [PATCH] redo hot-reloading in one file + +--- + Makefile | 19 +- + config.def.h | 5 +- + dwl.c | 484 +++++++++++++++++++++++++++++++++++++++------------ + util.c | 34 ++++ + util.h | 6 + + 5 files changed, 433 insertions(+), 115 deletions(-) + +diff --git a/Makefile b/Makefile +index 578194f..69c1b54 100644 +--- a/Makefile ++++ b/Makefile +@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + + # CFLAGS / LDFLAGS + PKGS = wayland-server xkbcommon libinput $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -fPIC -rdynamic + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +-all: dwl ++all: dwl dwl.so + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c config.mk cursor-shape-v1-protocol.h \ ++ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +@@ -49,7 +52,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h *.so + + dist: clean + mkdir -p dwl-$(VERSION) +@@ -64,6 +67,8 @@ install: dwl + rm -f $(DESTDIR)$(PREFIX)/bin/dwl + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl ++ mkdir -p $(DESTDIR)$(PREFIX)/lib ++ install -m 744 dwl.so $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 +@@ -71,9 +76,13 @@ install: dwl + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + uninstall: +- rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ ++ rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(PREFIX)/lib/dwl.so $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + + .SUFFIXES: .c .o + .c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< ++ ++.SUFFIXES: .c .so ++.c.so: ++ $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -shared -DHOT $< +diff --git a/config.def.h b/config.def.h +index 22d2171..6e3dda1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,7 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); ++const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); +@@ -18,7 +18,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + #define TAGCOUNT (9) + + /* logging */ +-static int log_level = WLR_ERROR; ++int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, reload, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index 4816159..70e99be 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,15 @@ + /* + * See LICENSE file for copyright and license details. + */ ++ ++/* stuff for hot-reload */ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++ + #include + #include + #include +@@ -68,6 +77,7 @@ + #include + #endif + ++ + #include "util.h" + + /* macros */ +@@ -78,8 +88,33 @@ + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +-#define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) ++#define SYM(a) dlsym(dwl_module, #a) ++#define TSYM(T, a) ((T)SYM(a)) ++#define CSYM(T, a) *(TSYM(T*, a)) ++ ++#define LISTEN(E, L, H) do { \ ++ (L)->notify = SYM(H); \ ++ listeners = append_listener((L), listeners); \ ++ wl_signal_add((E), (L)); \ ++ } while(0) ++ ++#define LISTEN_GLOBAL(E, L) do { \ ++ struct wl_listener* l = SYM(L); \ ++ listeners = append_listener(l, listeners); \ ++ wl_signal_add((E), l); \ ++ } while (0) ++ ++#define LISTEN_STATIC(E, H) do { \ ++ struct wl_listener* _l = ecalloc(1, sizeof(struct wl_listener)); \ ++ _l->notify = SYM(H); \ ++ listeners = append_listener(_l, listeners); \ ++ wl_signal_add((E), _l); \ ++ } while (0) ++ ++#define UNLISTEN(L) do { \ ++ wl_list_remove(&(L)->link); \ ++ listeners = remove_listener((L), listeners);\ ++ } while (0) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -239,6 +274,9 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++#define static ++ ++#ifdef HOT + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -250,7 +288,18 @@ static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void cleanup(void); ++ ++#undef static ++#define static ++#ifdef HOT ++ + static void cleanupmon(struct wl_listener *listener, void *data); + static void cleanuplisteners(void); + static void closemon(Monitor *m); +@@ -318,7 +367,18 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void run(char *startup_cmd); ++ ++#ifdef HOT ++#undef static ++#define static ++ + 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); +@@ -328,7 +388,18 @@ static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void setup(void); ++ ++#ifdef HOT ++#undef static ++#define static ++ + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -352,6 +423,16 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); + ++#endif ++ ++#ifdef HOT ++ #undef static ++ #define static extern ++#else ++ #undef static ++ #define static ++#endif ++ + /* variables */ + static pid_t child_pid = -1; + static int locked; +@@ -406,6 +487,10 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++#ifdef HOT ++#undef static ++#define static ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -429,6 +514,7 @@ static struct wl_listener output_mgr_test = {.notify = outputmgrtest}; + static struct wl_listener output_power_mgr_set_mode = {.notify = powermgrsetmode}; + static struct wl_listener request_activate = {.notify = urgent}; + static struct wl_listener request_cursor = {.notify = setcursor}; ++ + static struct wl_listener request_set_psel = {.notify = setpsel}; + static struct wl_listener request_set_sel = {.notify = setsel}; + static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; +@@ -436,7 +522,15 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; + ++/* undoes the shadowing of static from above */ ++#endif ++#undef static ++ ++ + #ifdef XWAYLAND ++#ifdef HOT ++#define static ++ + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); + static void configurex11(struct wl_listener *listener, void *data); +@@ -446,11 +540,45 @@ static void sethints(struct wl_listener *listener, void *data); + static void xwaylandready(struct wl_listener *listener, void *data); + static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; + static struct wl_listener xwayland_ready = {.notify = xwaylandready}; ++ ++#define static extern ++#else ++#define static ++#endif ++ + static struct wlr_xwayland *xwayland; ++ ++#undef static ++#endif ++ ++/* this is where we put global hot-reload state */ ++#ifdef HOT ++#define COLD extern ++#else ++#define COLD ++ ++static void* load(void); ++static const char* get_module_path(void); ++ ++#endif ++ ++COLD void * dwl_module = NULL; ++COLD void * last_module = NULL; ++COLD struct listens* listeners = NULL; ++COLD void reload(const Arg* arg); ++ ++#ifndef HOT ++static char* runpath; ++ + #endif + ++ ++#ifdef HOT ++ ++#define static + /* configuration, allows nested code to access above variables */ + #include "config.h" ++#undef static + + /* attempt to encapsulate suck into one file */ + #include "client.h" +@@ -695,10 +823,12 @@ checkidleinhibitor(struct wlr_surface *exclude) + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); + } + ++#endif ++ + void + cleanup(void) + { +- cleanuplisteners(); ++ TSYM(void (*)(void), cleanuplisteners)(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -710,7 +840,7 @@ cleanup(void) + } + wlr_xcursor_manager_destroy(cursor_mgr); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ TSYM(void (*)(struct wl_listener*, void*), destroykeyboardgroup)(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ +@@ -722,6 +852,8 @@ cleanup(void) + wlr_scene_node_destroy(&scene->tree.node); + } + ++#ifdef HOT ++ + void + cleanupmon(struct wl_listener *listener, void *data) + { +@@ -735,10 +867,10 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + +- wl_list_remove(&m->destroy.link); +- wl_list_remove(&m->frame.link); ++ UNLISTEN(&m->destroy); ++ UNLISTEN(&m->frame); + wl_list_remove(&m->link); +- wl_list_remove(&m->request_state.link); ++ UNLISTEN(&m->request_state); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +@@ -751,37 +883,37 @@ cleanupmon(struct wl_listener *listener, void *data) + void + cleanuplisteners(void) + { +- wl_list_remove(&cursor_axis.link); +- wl_list_remove(&cursor_button.link); +- wl_list_remove(&cursor_frame.link); +- wl_list_remove(&cursor_motion.link); +- wl_list_remove(&cursor_motion_absolute.link); +- wl_list_remove(&gpu_reset.link); +- wl_list_remove(&new_idle_inhibitor.link); +- wl_list_remove(&layout_change.link); +- wl_list_remove(&new_input_device.link); +- wl_list_remove(&new_virtual_keyboard.link); +- wl_list_remove(&new_virtual_pointer.link); +- wl_list_remove(&new_pointer_constraint.link); +- wl_list_remove(&new_output.link); +- wl_list_remove(&new_xdg_toplevel.link); +- wl_list_remove(&new_xdg_decoration.link); +- wl_list_remove(&new_xdg_popup.link); +- wl_list_remove(&new_layer_surface.link); +- wl_list_remove(&output_mgr_apply.link); +- wl_list_remove(&output_mgr_test.link); +- wl_list_remove(&output_power_mgr_set_mode.link); +- wl_list_remove(&request_activate.link); +- wl_list_remove(&request_cursor.link); +- wl_list_remove(&request_set_psel.link); +- wl_list_remove(&request_set_sel.link); +- wl_list_remove(&request_set_cursor_shape.link); +- wl_list_remove(&request_start_drag.link); +- wl_list_remove(&start_drag.link); +- wl_list_remove(&new_session_lock.link); ++ UNLISTEN(&cursor_axis); ++ UNLISTEN(&cursor_button); ++ UNLISTEN(&cursor_frame); ++ UNLISTEN(&cursor_motion); ++ UNLISTEN(&cursor_motion_absolute); ++ UNLISTEN(&gpu_reset); ++ UNLISTEN(&new_idle_inhibitor); ++ UNLISTEN(&layout_change); ++ UNLISTEN(&new_input_device); ++ UNLISTEN(&new_virtual_keyboard); ++ UNLISTEN(&new_virtual_pointer); ++ UNLISTEN(&new_pointer_constraint); ++ UNLISTEN(&new_output); ++ UNLISTEN(&new_xdg_toplevel); ++ UNLISTEN(&new_xdg_decoration); ++ UNLISTEN(&new_xdg_popup); ++ UNLISTEN(&new_layer_surface); ++ UNLISTEN(&output_mgr_apply); ++ UNLISTEN(&output_mgr_test); ++ UNLISTEN(&output_power_mgr_set_mode); ++ UNLISTEN(&request_activate); ++ UNLISTEN(&request_cursor); ++ UNLISTEN(&request_set_psel); ++ UNLISTEN(&request_set_sel); ++ UNLISTEN(&request_set_cursor_shape); ++ UNLISTEN(&request_start_drag); ++ UNLISTEN(&start_drag); ++ UNLISTEN(&new_session_lock); + #ifdef XWAYLAND +- wl_list_remove(&new_xwayland_surface.link); +- wl_list_remove(&xwayland_ready.link); ++ UNLISTEN(&new_xwayland_surface); ++ UNLISTEN(&xwayland_ready); + #endif + } + +@@ -908,7 +1040,7 @@ commitpopup(struct wl_listener *listener, void *data) + box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); + box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1239,8 +1371,8 @@ destroydecoration(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, destroy_decoration); + +- wl_list_remove(&c->destroy_decoration.link); +- wl_list_remove(&c->set_decoration_mode.link); ++ UNLISTEN(&c->destroy_decoration); ++ UNLISTEN(&c->set_decoration_mode); + } + + void +@@ -1249,7 +1381,7 @@ destroydragicon(struct wl_listener *listener, void *data) + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1259,7 +1391,7 @@ destroyidleinhibitor(struct wl_listener *listener, void *data) + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1269,9 +1401,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); +- wl_list_remove(&l->destroy.link); +- wl_list_remove(&l->unmap.link); +- wl_list_remove(&l->surface_commit.link); ++ UNLISTEN(&l->destroy); ++ UNLISTEN(&l->unmap); ++ UNLISTEN(&l->surface_commit); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +@@ -1290,9 +1422,9 @@ destroylock(SessionLock *lock, int unlock) + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +- wl_list_remove(&lock->new_surface.link); +- wl_list_remove(&lock->unlock.link); +- wl_list_remove(&lock->destroy.link); ++ UNLISTEN(&lock->new_surface); ++ UNLISTEN(&lock->unlock); ++ UNLISTEN(&lock->destroy); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; +@@ -1306,7 +1438,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; +- wl_list_remove(&m->destroy_lock_surface.link); ++ UNLISTEN(&m->destroy_lock_surface); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; +@@ -1326,23 +1458,23 @@ destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); +- wl_list_remove(&c->destroy.link); +- wl_list_remove(&c->set_title.link); +- wl_list_remove(&c->fullscreen.link); ++ UNLISTEN(&c->destroy); ++ UNLISTEN(&c->set_title); ++ UNLISTEN(&c->fullscreen); + #ifdef XWAYLAND + if (c->type != XDGShell) { +- wl_list_remove(&c->activate.link); +- wl_list_remove(&c->associate.link); +- wl_list_remove(&c->configure.link); +- wl_list_remove(&c->dissociate.link); +- wl_list_remove(&c->set_hints.link); ++ UNLISTEN(&c->activate); ++ UNLISTEN(&c->associate); ++ UNLISTEN(&c->configure); ++ UNLISTEN(&c->dissociate); ++ UNLISTEN(&c->set_hints); + } else + #endif + { +- wl_list_remove(&c->commit.link); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); +- wl_list_remove(&c->maximize.link); ++ UNLISTEN(&c->commit); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); ++ UNLISTEN(&c->maximize); + } + free(c); + } +@@ -1357,7 +1489,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data) + active_constraint = NULL; + } + +- wl_list_remove(&pointer_constraint->destroy.link); ++ UNLISTEN(&pointer_constraint->destroy); + free(pointer_constraint); + } + +@@ -1373,9 +1505,9 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + { + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); +- wl_list_remove(&group->key.link); +- wl_list_remove(&group->modifiers.link); +- wl_list_remove(&group->destroy.link); ++ UNLISTEN(&group->key); ++ UNLISTEN(&group->modifiers); ++ UNLISTEN(&group->destroy); + wlr_keyboard_group_destroy(group->wlr_group); + free(group); + } +@@ -1541,8 +1673,8 @@ gpureset(struct wl_listener *listener, void *data) + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + +- wl_list_remove(&gpu_reset.link); +- wl_signal_add(&drw->events.lost, &gpu_reset); ++ UNLISTEN(&gpu_reset); ++ LISTEN_GLOBAL(&drw->events.lost, gpu_reset); + + wlr_compositor_set_renderer(compositor, drw); + +@@ -2232,6 +2364,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++#else /*HOT*/ ++ + void + run(char *startup_cmd) + { +@@ -2271,11 +2405,11 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ TSYM(void (*)(void), printstatus)(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +- selmon = xytomon(cursor->x, cursor->y); ++ selmon = TSYM(Monitor* (*)(double x, double y), xytomon)(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully +@@ -2291,6 +2425,9 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++#endif ++#ifdef HOT ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2437,17 +2574,19 @@ setsel(struct wl_listener *listener, void *data) + wlr_seat_set_selection(seat, event->source, event->serial); + } + ++#else /*HOT*/ ++ + void + setup(void) + { + int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; +- struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = SYM(handlesig)}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +- wlr_log_init(log_level, NULL); ++ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ +@@ -2463,7 +2602,7 @@ setup(void) + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); +- root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); ++ root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, TSYM(float*, rootcolor)); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); +@@ -2475,7 +2614,7 @@ setup(void) + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); +- wl_signal_add(&drw->events.lost, &gpu_reset); ++ LISTEN_GLOBAL(&drw->events.lost, gpu_reset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: +@@ -2522,24 +2661,24 @@ setup(void) + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); +- wl_signal_add(&activation->events.request_activate, &request_activate); ++ LISTEN_GLOBAL(&activation->events.request_activate, request_activate); + + wlr_scene_set_gamma_control_manager_v1(scene, wlr_gamma_control_manager_v1_create(dpy)); + + power_mgr = wlr_output_power_manager_v1_create(dpy); +- wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); ++ LISTEN_GLOBAL(&power_mgr->events.set_mode, output_power_mgr_set_mode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); +- wl_signal_add(&output_layout->events.change, &layout_change); ++ LISTEN_GLOBAL(&output_layout->events.change, layout_change); + + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); +- wl_signal_add(&backend->events.new_output, &new_output); ++ LISTEN_GLOBAL(&backend->events.new_output, new_output); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more +@@ -2551,19 +2690,19 @@ setup(void) + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); +- wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); +- wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); ++ LISTEN_GLOBAL(&xdg_shell->events.new_toplevel, new_xdg_toplevel); ++ LISTEN_GLOBAL(&xdg_shell->events.new_popup, new_xdg_popup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); +- wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); ++ LISTEN_GLOBAL(&layer_shell->events.new_surface, new_layer_surface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); +- wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); ++ LISTEN_GLOBAL(&idle_inhibit_mgr->events.new_inhibitor, new_idle_inhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); +- wl_signal_add(&session_lock_mgr->events.new_lock, &new_session_lock); ++ LISTEN_GLOBAL(&session_lock_mgr->events.new_lock, new_session_lock); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); +@@ -2573,10 +2712,10 @@ setup(void) + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); +- wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration); ++ LISTEN_GLOBAL(&xdg_decoration_mgr->events.new_toplevel_decoration, new_xdg_decoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); +- wl_signal_add(&pointer_constraints->events.new_constraint, &new_pointer_constraint); ++ LISTEN_GLOBAL(&pointer_constraints->events.new_constraint, new_pointer_constraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + +@@ -2604,14 +2743,14 @@ setup(void) + * + * And more comments are sprinkled throughout the notify functions above. + */ +- wl_signal_add(&cursor->events.motion, &cursor_motion); +- wl_signal_add(&cursor->events.motion_absolute, &cursor_motion_absolute); +- wl_signal_add(&cursor->events.button, &cursor_button); +- wl_signal_add(&cursor->events.axis, &cursor_axis); +- wl_signal_add(&cursor->events.frame, &cursor_frame); ++ LISTEN_GLOBAL(&cursor->events.motion, cursor_motion); ++ LISTEN_GLOBAL(&cursor->events.motion_absolute, cursor_motion_absolute); ++ LISTEN_GLOBAL(&cursor->events.button, cursor_button); ++ LISTEN_GLOBAL(&cursor->events.axis, cursor_axis); ++ LISTEN_GLOBAL(&cursor->events.frame, cursor_frame); + + 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); ++ LISTEN_GLOBAL(&cursor_shape_mgr->events.request_set_shape, request_set_cursor_shape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and +@@ -2619,27 +2758,27 @@ setup(void) + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ +- wl_signal_add(&backend->events.new_input, &new_input_device); ++ LISTEN_GLOBAL(&backend->events.new_input, new_input_device); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); +- wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, +- &new_virtual_keyboard); ++ LISTEN_GLOBAL(&virtual_keyboard_mgr->events.new_virtual_keyboard, ++ new_virtual_keyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); +- wl_signal_add(&virtual_pointer_mgr->events.new_virtual_pointer, +- &new_virtual_pointer); ++ LISTEN_GLOBAL(&virtual_pointer_mgr->events.new_virtual_pointer, ++ new_virtual_pointer); + + seat = wlr_seat_create(dpy, "seat0"); +- wl_signal_add(&seat->events.request_set_cursor, &request_cursor); +- wl_signal_add(&seat->events.request_set_selection, &request_set_sel); +- wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); +- wl_signal_add(&seat->events.request_start_drag, &request_start_drag); +- wl_signal_add(&seat->events.start_drag, &start_drag); ++ LISTEN_GLOBAL(&seat->events.request_set_cursor, request_cursor); ++ LISTEN_GLOBAL(&seat->events.request_set_selection, request_set_sel); ++ LISTEN_GLOBAL(&seat->events.request_set_primary_selection, request_set_psel); ++ LISTEN_GLOBAL(&seat->events.request_start_drag, request_start_drag); ++ LISTEN_GLOBAL(&seat->events.start_drag, start_drag); + +- kb_group = createkeyboardgroup(); ++ kb_group = TSYM(KeyboardGroup *(*)(void), createkeyboardgroup)(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); +- wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); +- wl_signal_add(&output_mgr->events.test, &output_mgr_test); ++ LISTEN_GLOBAL(&output_mgr->events.apply, output_mgr_apply); ++ LISTEN_GLOBAL(&output_mgr->events.test, output_mgr_test); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the +@@ -2651,8 +2790,8 @@ setup(void) + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { +- wl_signal_add(&xwayland->events.ready, &xwayland_ready); +- wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); ++ LISTEN_GLOBAL(&xwayland->events.ready, xwayland_ready); ++ LISTEN_GLOBAL(&xwayland->events.new_surface, new_xwayland_surface); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { +@@ -2661,6 +2800,9 @@ setup(void) + #endif + } + ++#endif ++#ifdef HOT ++ + void + spawn(const Arg *arg) + { +@@ -3142,8 +3284,8 @@ void + dissociatex11(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, dissociate); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + + void +@@ -3178,17 +3320,141 @@ xwaylandready(struct wl_listener *listener, void *data) + } + #endif + ++#else /* HOT */ ++void* ++load(void) ++{ ++ const char* path = get_module_path(); ++ char load[PATH_MAX] = "/tmp/dwl.soXXXXXX"; ++ void* new; ++ ++ if (!path) { ++ fprintf(stderr, "cannot find dwl.so\n"); ++ } ++ ++ do { ++ mktemp(load); ++ errno = 0; ++ symlink(path, load); ++ } while(errno == EEXIST); ++ ++ new = dlopen(load, RTLD_NOW|RTLD_LOCAL); ++ ++ unlink(load); ++ if (new == NULL) ++ fprintf(stderr, "error while loading %s: %s\n", path, dlerror()); ++ else ++ printf("loaded: %s\n", path); ++ ++ return new; ++} ++ ++const char * ++get_module_path(void) { ++ char home[PATH_MAX]; ++ strcpy(home, getenv("HOME")); ++ strcat(home, "/.local/lib"); ++ const char* abspaths[] = {".", home, "/usr/share/lib", "/usr/local/lib", "/usr/local/share/lib"}; ++ const char* relpaths[] = {"", "/../lib"}; ++ char paths[LENGTH(abspaths) + LENGTH(relpaths)][PATH_MAX]; ++ static char out[PATH_MAX] = "./"; ++ ++ for (size_t i = 0; i < LENGTH(abspaths); i++) ++ realpath(abspaths[i], paths[i]); ++ ++ for (size_t i = 0; i < LENGTH(relpaths); i++) ++ { ++ char tmp[PATH_MAX]; ++ strcpy(tmp, runpath); ++ strcat(tmp, relpaths[i]); ++ realpath(tmp, paths[LENGTH(abspaths) + i]); ++ } ++ ++ ++ ++ for (size_t i = 0; i < LENGTH(paths); i++) ++ { ++ char tmp[PATH_MAX]; ++ printf("checking path: %s\n", paths[i]); ++ strcpy(tmp, paths[i]); ++ strcat(tmp, "/dwl.so"); ++ if (access(tmp, F_OK|R_OK) == 0) ++ { ++ strcpy(out, tmp); ++ return out; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++reload(const Arg* arg) ++{ ++ char* error; ++ void* new; ++ size_t i = 0; ++ ++ // deinitialize previous module ++ if (last_module) { ++ // dlclose(last_module); ++ last_module = NULL; ++ } ++ ++ wlr_log(WLR_INFO, "reloading"); ++ ++ new = load(); ++ ++ if (new == NULL) ++ { ++ wlr_log(WLR_ERROR, "couldn't load new dwl module from %s", get_module_path()); ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "failed to reload dwl", NULL); ++ return; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- listens ---------"); ++ for(listens* a = listeners; a != NULL; a = a->next) ++ { ++ Dl_info info; ++ void* old = a->listen->notify; ++ dladdr(a->listen->notify, &info); ++ a->listen->notify = dlsym(new, info.dli_sname); ++ if ((error = dlerror()) != NULL){ ++ fprintf(stderr, "reload failure: %s", error); ++ a->listen->notify = old; ++ return; ++ } ++ wlr_log(WLR_DEBUG, "replaced listener: %s", info.dli_sname); ++ i++; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- done! ---------"); ++ wlr_log(WLR_DEBUG, "replaced %zu listeners", i); ++ ++ last_module = dwl_module; ++ dwl_module = new; ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "reloaded dwl", NULL); ++ ++} ++ + int + main(int argc, char *argv[]) + { + char *startup_cmd = NULL; + int c; + ++ runpath = dirname(argv[0]); ++ dwl_module = load(); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') +- log_level = WLR_DEBUG; ++ CSYM(enum wlr_log_importance, log_level) = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else +@@ -3208,3 +3474,5 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++#endif +diff --git a/util.c b/util.c +index b925987..8fb9b77 100644 +--- a/util.c ++++ b/util.c +@@ -49,3 +49,37 @@ fd_set_nonblock(int fd) { + + return 0; + } ++ ++struct listens* ++append_listener(struct wl_listener* new, struct listens* list) ++{ ++ struct listens* l = malloc(sizeof(struct listens)); ++ l->listen = new; ++ l->next = list; ++ return l; ++} ++ ++struct listens* ++remove_listener(struct wl_listener* l, struct listens* ls) ++{ ++ struct listens* out = ls; ++ struct listens* f = NULL; ++ for(struct listens* last = NULL; ls != NULL; ls = ls->next) ++ { ++ if (ls->listen == l) ++ { ++ if (last != NULL) ++ last->next = ls->next; ++ else ++ out = ls->next; ++ ++ f = ls; ++ } ++ else ++ last = ls; ++ } ++ ++ free(f); ++ ++ return out; ++} +diff --git a/util.h b/util.h +index 226980d..11aab34 100644 +--- a/util.h ++++ b/util.h +@@ -1,5 +1,11 @@ + /* See LICENSE.dwm file for copyright and license details. */ ++typedef struct listens { ++ struct wl_listener* listen; ++ struct listens* next; ++} listens; + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); + int fd_set_nonblock(int fd); ++struct listens* append_listener(struct wl_listener* l, struct listens* ls); ++struct listens* remove_listener(struct wl_listener* l, struct listens* ls); +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/inputdevicerules/README.md b/dwl-bak/dwl-patches/patches/inputdevicerules/README.md new file mode 100644 index 0000000..3f87ef9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/inputdevicerules/README.md @@ -0,0 +1,18 @@ +### Description + +Input device rules implemented using custom device create functions for +keyboards and pointing devices. + +Examples provided: + +- ignore unwanted input devices +- configure a toggle input device +- exclude certain keyboards (eg ydotool) from keyboard group + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/inputdevicerules) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/inputdevicerules/inputdevicerules-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/inputdevicerules/inputdevicerules-v0.6.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch b/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch new file mode 100644 index 0000000..b9e9fd2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch @@ -0,0 +1,173 @@ +From 620a68ffbcecf996916a8fd637f0bcff7a72e004 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] input device rules + +* customise input device handling +* ignore unwanted input devices +* configure a toggle for an input device +--- + config.def.h | 13 ++++++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..88006d79 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,18 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* input devices */ ++static const InputRule inputrules[] = { ++ /* name kbcreate ptrcreate */ ++ /* ignore bad device - like a touchpad ;) */ ++ { "BAD DEVICE", NULL, NULL }, ++ /* ungroup ydotool device - fixes a bug */ ++ { "ydotoold virtual device", createungroupedkeyboard, createpointer }, ++ /* put your touchpad name here to enable toggle touchpad */ ++ { "Elan Touchpad", createkeyboard, createtogglepointer }, ++ { NULL, createkeyboard, createpointer }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +@@ -139,6 +151,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_u, togglepointer, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 3dba2bf5..a11c7587 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++ void (*ptrcreate)(struct wlr_pointer *); ++} InputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -266,6 +272,8 @@ static void createmon(struct wl_listener *listener, void *data); + static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); ++static void createtogglepointer(struct wlr_pointer *pointer); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -335,6 +343,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointer(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -406,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct libinput_device *togglepointerdevice = NULL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1092,6 +1103,33 @@ createpointerconstraint(struct wl_listener *listener, void *data) + &pointer_constraint->destroy, destroypointerconstraint); + } + ++void ++createtogglepointer(struct wlr_pointer *pointer) ++{ ++ struct libinput_device *device; ++ ++ createpointer(pointer); ++ ++ if (wlr_input_device_is_libinput(&pointer->base) ++ && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ togglepointerdevice = device; ++ } ++} ++ ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1467,13 +1505,27 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const InputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->kbcreate) ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: +- createpointer(wlr_pointer_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->ptrcreate) ++ r->ptrcreate(wlr_pointer_from_input_device(device)); ++ break; ++ } ++ } + break; + default: + /* TODO handle other input device types */ +@@ -2647,6 +2699,18 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointer(const Arg *arg) ++{ ++ if (!togglepointerdevice) ++ return; ++ ++ libinput_device_config_send_events_set_mode( ++ togglepointerdevice, ++ libinput_device_config_send_events_get_mode(togglepointerdevice) ^ LIBINPUT_CONFIG_SEND_EVENTS_DISABLED ++ ); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch b/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch new file mode 100644 index 0000000..18f6f9e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch @@ -0,0 +1,173 @@ +From 89f870a04f903681b0a7a0ac4eb1ae70c4984b46 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] input device rules + +* customise input device handling +* ignore unwanted input devices +* configure a toggle for an input device +--- + config.def.h | 13 ++++++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0b287ab5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,18 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* input devices */ ++static const InputRule inputrules[] = { ++ /* name kbcreate ptrcreate */ ++ /* ignore bad device - like a touchpad ;) */ ++ { "BAD DEVICE", NULL, NULL }, ++ /* ungroup ydotool device - fixes a bug */ ++ { "ydotoold virtual device", createungroupedkeyboard, createpointer }, ++ /* put your touchpad name here to enable toggle touchpad */ ++ { "Elan Touchpad", createkeyboard, createtogglepointer }, ++ { NULL, createkeyboard, createpointer }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +@@ -142,6 +154,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_u, togglepointer, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index a2711f67..f6f91938 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,6 +143,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++ void (*ptrcreate)(struct wlr_pointer *); ++} InputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -270,6 +276,8 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtogglepointer(struct wlr_pointer *pointer); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -340,6 +348,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointer(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +422,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct libinput_device *togglepointerdevice = NULL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1133,6 +1144,33 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtogglepointer(struct wlr_pointer *pointer) ++{ ++ struct libinput_device *device; ++ ++ createpointer(pointer); ++ ++ if (wlr_input_device_is_libinput(&pointer->base) ++ && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ togglepointerdevice = device; ++ } ++} ++ ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1531,13 +1569,27 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const InputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->kbcreate) ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: +- createpointer(wlr_pointer_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->ptrcreate) ++ r->ptrcreate(wlr_pointer_from_input_device(device)); ++ break; ++ } ++ } + break; + default: + /* TODO handle other input device types */ +@@ -2739,6 +2791,18 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointer(const Arg *arg) ++{ ++ if (!togglepointerdevice) ++ return; ++ ++ libinput_device_config_send_events_set_mode( ++ togglepointerdevice, ++ libinput_device_config_send_events_get_mode(togglepointerdevice) ^ LIBINPUT_CONFIG_SEND_EVENTS_DISABLED ++ ); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/ipc/README.md b/dwl-bak/dwl-patches/patches/ipc/README.md new file mode 100644 index 0000000..bf648a4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ipc/README.md @@ -0,0 +1,28 @@ +### Description +Largely based on [raphi](https://sr.ht/~raphi/)'s [somebar](https://sr.ht/~raphi/somebar/), this patch provides an ipc for wayland clients to get and set dwl state. The ipc is intended for status bars, but can also be scripted with tools like [dwlmsg](https://codeberg.org/notchoc/dwlmsg). + +Status information to stdout is currently disabled as dwl tends to freeze. For now, `dwlmsg -w` should act as a drop-in replacement. + +Note to [pertag](../pertag/) users: apply [this](./ipcpertag.patch) for ipc tagsetting to work as expected + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/ipc) +- [2024-08-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/ipc/ipc.patch) +- [2024-07-29](https://codeberg.org/dwl/dwl-patches/raw/commit/d235f0f88ed069eca234da5a544fb1c6e19f1d33/patches/ipc/ipc.patch) don't focus other outputs (apply [this minipatch](./focus-tagset-output.patch) if you'd prefer that) +- [2024-07-16](https://codeberg.org/dwl/dwl-patches/raw/commit/642b2559d522034785c1c1203c6d426855ec19ca/patches/ipc/ipc.patch) +- [2024-06-30](https://codeberg.org/dwl/dwl-patches/raw/commit/9a751e5020133d3ab9219e68a43109c6f3c931a7/patches/ipc/ipc.patch) +- [2024-06-21](https://codeberg.org/dwl/dwl-patches/raw/commit/f96ee44cbaef06bd38b8fa29ac7ecba8b1b5abd5/patches/ipc/ipc.patch) +- [2024-06-19](https://codeberg.org/dwl/dwl-patches/raw/commit/e69afc7263b8d982a7923e5d4910f2e1f7140bb8/patches/ipc/ipc.patch) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/raw/commit/f8598a91b44acc3bd7e9041be97265bbce8fa219/patches/ipc/ipc.patch) +- [2024-03-13](https://codeberg.org/dwl/dwl-patches/raw/commit/0150cfebbcd85f2d6e6728afad345a11a0c45947/ipc/ipc.patch) +- [2024-02-20](https://codeberg.org/dwl/dwl-patches/raw/commit/0c5ae06e4bc1d7f641376e8fcb86b43bd48ce2ee/ipc/ipc.patch) +- [2023-10-28](https://gist.githubusercontent.com/fbushstone/b116c44340eb7a7878de1119dd931ca5/raw/ee66ac9e2a5dddd9b528df553e21080c2811e974/ipc-v2-fixed.patch) Updated version of 2023-04-29, prevents ipc from freezing the compositor in printstatus. +- [2023-04-29](https://github.com/djpohly/dwl/compare/main...madcowog:ipc-v2.patch) Use this for dwl-ipc-unstable-v2. If you are using commit [9d68554](https://github.com/djpohly/dwl/commit/9d68554c59a886b641d27a364884fb461af2d4f1) or later, use this. For status bars this protocol is supported by dwlb, Waybar and dwl-bar. +- [2023-04-29](https://github.com/djpohly/dwl/compare/main...madcowog:ipc-bbdf2.patch) Use this for dwl-ipc-unstable-v1. If you are using commit [bbdaf2a9](https://github.com/djpohly/dwl/commit/bbdf2a913b72e7a308ee0dfde6518a4285d4a775), [release 0.4](https://github.com/djpohly/dwl/releases/tag/v0.4) or earlier, use this. For status bars, this protocol is supported by dwl-bar. +- [2023-02-20](https://lists.sr.ht/~raphi/public-inbox/patches/39166) Use this for net-tapesoftware-dwl-wm-unstable-v1. If you are using commit [c69a2bec](https://github.com/djpohly/dwl/commit/c69a2bec3ff417fbc4ea8fec0a49096773e01e7d) or later, use this. For status bars this protocol is supported by somebar. + +### Authors +- [MadcowOG](https://github.com/MadcowOG) +- [fbushstone](https://github.com/fbushstone) +- [notchoc](https://codeberg.org/notchoc) +- [snuk](https://codeberg.org/snuk) diff --git a/dwl-bak/dwl-patches/patches/ipc/focus-tagset-output.patch b/dwl-bak/dwl-patches/patches/ipc/focus-tagset-output.patch new file mode 100644 index 0000000..2ea3015 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ipc/focus-tagset-output.patch @@ -0,0 +1,24 @@ +From b65477621f0438b0ed6c405f74c9d15e8fb57e96 Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 29 Jul 2024 21:26:06 +0800 +Subject: [PATCH] ipc: focus set_tag'd output + +--- + dwl.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dwl.c b/dwl.c +index 4a025f0..dcbb063 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1533,6 +1533,7 @@ dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, + if (!ipc_output) + return; + monitor = ipc_output->mon; ++ selmon = monitor; + + if (!newtags || newtags == monitor->tagset[monitor->seltags]) + return; +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/ipc/ipc.patch b/dwl-bak/dwl-patches/patches/ipc/ipc.patch new file mode 100644 index 0000000..9f989e7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ipc/ipc.patch @@ -0,0 +1,597 @@ +From 6c6d655b68770ce82a24fde9b58c4d97b672553a Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 23 Oct 2023 10:35:17 +0800 +Subject: [PATCH] implement dwl-ipc-unstable-v2 + https://codeberg.org/dwl/dwl-patches/wiki/ipc + +--- + Makefile | 14 +- + config.def.h | 1 + + dwl.c | 257 ++++++++++++++++++++++++++---- + protocols/dwl-ipc-unstable-v2.xml | 181 +++++++++++++++++++++ + 4 files changed, 419 insertions(+), 34 deletions(-) + create mode 100644 protocols/dwl-ipc-unstable-v2.xml + +diff --git a/Makefile b/Makefile +index 8db7409..a79a080 100644 +--- a/Makefile ++++ b/Makefile +@@ -17,12 +17,14 @@ DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEV + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + + all: dwl +-dwl: dwl.o util.o +- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ ++dwl: dwl.o util.o dwl-ipc-unstable-v2-protocol.o ++ $(CC) dwl.o util.o dwl-ipc-unstable-v2-protocol.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ dwl-ipc-unstable-v2-protocol.h + util.o: util.c util.h ++dwl-ipc-unstable-v2-protocol.o: dwl-ipc-unstable-v2-protocol.c dwl-ipc-unstable-v2-protocol.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland + # protocols, which are specified in XML. wlroots requires you to rig these up +@@ -45,6 +47,12 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++dwl-ipc-unstable-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ protocols/dwl-ipc-unstable-v2.xml $@ ++dwl-ipc-unstable-v2-protocol.c: ++ $(WAYLAND_SCANNER) private-code \ ++ protocols/dwl-ipc-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..1593033 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index 8a587d1..7a4949b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -68,6 +68,7 @@ + #include + #endif + ++#include "dwl-ipc-unstable-v2-protocol.h" + #include "util.h" + + /* macros */ +@@ -144,6 +145,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ struct wl_list link; ++ struct wl_resource *resource; ++ Monitor *mon; ++} DwlIpcOutput; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -189,6 +196,7 @@ typedef struct { + + struct Monitor { + struct wl_list link; ++ struct wl_list dwl_ipc_outputs; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ +@@ -286,6 +294,17 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id); ++static void dwl_ipc_manager_destroy(struct wl_resource *resource); ++static void dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output); ++static void dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource); ++static void dwl_ipc_output_destroy(struct wl_resource *resource); ++static void dwl_ipc_output_printstatus(Monitor *monitor); ++static void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output); ++static void dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags); ++static void dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index); ++static void dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset); ++static void dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -338,6 +357,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -411,6 +431,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct zdwl_ipc_manager_v2_interface dwl_manager_implementation = {.release = dwl_ipc_manager_release, .get_output = dwl_ipc_manager_get_output}; ++static struct zdwl_ipc_output_v2_interface dwl_output_implementation = {.release = dwl_ipc_output_release, .set_tags = dwl_ipc_output_set_tags, .set_layout = dwl_ipc_output_set_layout, .set_client_tags = dwl_ipc_output_set_client_tags}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -703,6 +726,10 @@ cleanupmon(struct wl_listener *listener, void *data) + LayerSurface *l, *tmp; + size_t i; + ++ DwlIpcOutput *ipc_output, *ipc_output_tmp; ++ wl_list_for_each_safe(ipc_output, ipc_output_tmp, &m->dwl_ipc_outputs, link) ++ wl_resource_destroy(ipc_output->resource); ++ + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) +@@ -983,6 +1010,8 @@ createmon(struct wl_listener *listener, void *data) + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + ++ wl_list_init(&m->dwl_ipc_outputs); ++ + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + +@@ -1334,6 +1363,192 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) ++{ ++ struct wl_resource *manager_resource = wl_resource_create(client, &zdwl_ipc_manager_v2_interface, version, id); ++ if (!manager_resource) { ++ wl_client_post_no_memory(client); ++ return; ++ } ++ wl_resource_set_implementation(manager_resource, &dwl_manager_implementation, NULL, dwl_ipc_manager_destroy); ++ ++ zdwl_ipc_manager_v2_send_tags(manager_resource, TAGCOUNT); ++ ++ for (unsigned int i = 0; i < LENGTH(layouts); i++) ++ zdwl_ipc_manager_v2_send_layout(manager_resource, layouts[i].symbol); ++} ++ ++void ++dwl_ipc_manager_destroy(struct wl_resource *resource) ++{ ++ /* No state to destroy */ ++} ++ ++void ++dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor = wlr_output_from_resource(output)->data; ++ struct wl_resource *output_resource = wl_resource_create(client, &zdwl_ipc_output_v2_interface, wl_resource_get_version(resource), id); ++ if (!output_resource) ++ return; ++ ++ ipc_output = ecalloc(1, sizeof(*ipc_output)); ++ ipc_output->resource = output_resource; ++ ipc_output->mon = monitor; ++ wl_resource_set_implementation(output_resource, &dwl_output_implementation, ipc_output, dwl_ipc_output_destroy); ++ wl_list_insert(&monitor->dwl_ipc_outputs, &ipc_output->link); ++ dwl_ipc_output_printstatus_to(ipc_output); ++} ++ ++void ++dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource) ++{ ++ wl_resource_destroy(resource); ++} ++ ++static void ++dwl_ipc_output_destroy(struct wl_resource *resource) ++{ ++ DwlIpcOutput *ipc_output = wl_resource_get_user_data(resource); ++ wl_list_remove(&ipc_output->link); ++ free(ipc_output); ++} ++ ++void ++dwl_ipc_output_printstatus(Monitor *monitor) ++{ ++ DwlIpcOutput *ipc_output; ++ wl_list_for_each(ipc_output, &monitor->dwl_ipc_outputs, link) ++ dwl_ipc_output_printstatus_to(ipc_output); ++} ++ ++void ++dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) ++{ ++ Monitor *monitor = ipc_output->mon; ++ Client *c, *focused; ++ int tagmask, state, numclients, focused_client, tag; ++ const char *title, *appid; ++ focused = focustop(monitor); ++ zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); ++ ++ for (tag = 0 ; tag < TAGCOUNT; tag++) { ++ numclients = state = focused_client = 0; ++ tagmask = 1 << tag; ++ if ((tagmask & monitor->tagset[monitor->seltags]) != 0) ++ state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != monitor) ++ continue; ++ if (!(c->tags & tagmask)) ++ continue; ++ if (c == focused) ++ focused_client = 1; ++ if (c->isurgent) ++ state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT; ++ ++ numclients++; ++ } ++ zdwl_ipc_output_v2_send_tag(ipc_output->resource, tag, state, numclients, focused_client); ++ } ++ title = focused ? client_get_title(focused) : ""; ++ appid = focused ? client_get_appid(focused) : ""; ++ ++ zdwl_ipc_output_v2_send_layout(ipc_output->resource, monitor->lt[monitor->sellt] - layouts); ++ zdwl_ipc_output_v2_send_title(ipc_output->resource, title); ++ zdwl_ipc_output_v2_send_appid(ipc_output->resource, appid); ++ zdwl_ipc_output_v2_send_layout_symbol(ipc_output->resource, monitor->ltsymbol); ++ if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FULLSCREEN_SINCE_VERSION) { ++ zdwl_ipc_output_v2_send_fullscreen(ipc_output->resource, focused ? focused->isfullscreen : 0); ++ } ++ if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FLOATING_SINCE_VERSION) { ++ zdwl_ipc_output_v2_send_floating(ipc_output->resource, focused ? focused->isfloating : 0); ++ } ++ zdwl_ipc_output_v2_send_frame(ipc_output->resource); ++} ++ ++void ++dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ Client *selected_client; ++ unsigned int newtags = 0; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ ++ monitor = ipc_output->mon; ++ selected_client = focustop(monitor); ++ if (!selected_client) ++ return; ++ ++ newtags = (selected_client->tags & and_tags) ^ xor_tags; ++ if (!newtags) ++ return; ++ ++ selected_client->tags = newtags; ++ if (selmon == monitor) ++ focusclient(focustop(monitor), 1); ++ arrange(selmon); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ ++ monitor = ipc_output->mon; ++ if (index >= LENGTH(layouts)) ++ return; ++ if (index != monitor->lt[monitor->sellt] - layouts) ++ monitor->sellt ^= 1; ++ ++ monitor->lt[monitor->sellt] = &layouts[index]; ++ arrange(monitor); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ unsigned int newtags = tagmask & TAGMASK; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ monitor = ipc_output->mon; ++ ++ if (!newtags || newtags == monitor->tagset[monitor->seltags]) ++ return; ++ if (toggle_tagset) ++ monitor->seltags ^= 1; ++ ++ monitor->tagset[monitor->seltags] = newtags; ++ if (selmon == monitor) ++ focusclient(focustop(monitor), 1); ++ arrange(monitor); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource) ++{ ++ wl_resource_destroy(resource); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -2033,38 +2248,9 @@ void + printstatus(void) + { + Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; + +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); ++ wl_list_for_each(m, &mons, link) ++ dwl_ipc_output_printstatus(m); + } + + void +@@ -2584,6 +2770,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, 2, NULL, dwl_ipc_manager_bind); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2681,6 +2869,13 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) { ++ DwlIpcOutput *ipc_output; ++ wl_list_for_each(ipc_output, &selmon->dwl_ipc_outputs, link) ++ zdwl_ipc_output_v2_send_toggle_visibility(ipc_output->resource); ++} ++ + void + togglefloating(const Arg *arg) + { +diff --git a/protocols/dwl-ipc-unstable-v2.xml b/protocols/dwl-ipc-unstable-v2.xml +new file mode 100644 +index 0000000..0a6e7e5 +--- /dev/null ++++ b/protocols/dwl-ipc-unstable-v2.xml +@@ -0,0 +1,181 @@ ++ ++ ++ ++ ++ This protocol allows clients to update and get updates from dwl. ++ ++ Warning! The protocol described in this file is experimental and ++ backward incompatible changes may be made. Backward compatible ++ changes may be added together with the corresponding interface ++ version bump. ++ Backward incompatible changes are done by bumping the version ++ number in the protocol and interface names and resetting the ++ interface version. Once the protocol is to be declared stable, ++ the 'z' prefix and the version number in the protocol and ++ interface names are removed and the interface version number is ++ reset. ++ ++ ++ ++ ++ This interface is exposed as a global in wl_registry. ++ ++ Clients can use this interface to get a dwl_ipc_output. ++ After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. ++ The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. ++ ++ ++ ++ ++ Indicates that the client will not the dwl_ipc_manager object anymore. ++ Objects created through this instance are not affected. ++ ++ ++ ++ ++ ++ Get a dwl_ipc_outout for the specified wl_output. ++ ++ ++ ++ ++ ++ ++ ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all tags. ++ ++ ++ ++ ++ ++ ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all layouts. ++ ++ ++ ++ ++ ++ ++ ++ Observe and control a dwl output. ++ ++ Events are double-buffered: ++ Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. ++ ++ Request are not double-buffered: ++ The compositor will update immediately upon request. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates to that the client no longer needs this dwl_ipc_output. ++ ++ ++ ++ ++ ++ Indicates the client should hide or show themselves. ++ If the client is visible then hide, if hidden then show. ++ ++ ++ ++ ++ ++ Indicates if the output is active. Zero is invalid, nonzero is valid. ++ ++ ++ ++ ++ ++ ++ Indicates that a tag has been updated. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates a new layout is selected. ++ ++ ++ ++ ++ ++ ++ Indicates the title has changed. ++ ++ ++ ++ ++ ++ ++ Indicates the appid has changed. ++ ++ ++ ++ ++ ++ ++ Indicates the layout has changed. Since layout symbols are dynamic. ++ As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. ++ You can ignore the zdwl_ipc_output.layout event. ++ ++ ++ ++ ++ ++ ++ Indicates that a sequence of status updates have finished and the client should redraw. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ The tags are updated as follows: ++ new_tags = (current_tags AND and_tags) XOR xor_tags ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates if the selected client on this output is fullscreen. ++ ++ ++ ++ ++ ++ ++ Indicates if the selected client on this output is floating. ++ ++ ++ ++ ++ +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/ipc/ipcpertag.patch b/dwl-bak/dwl-patches/patches/ipc/ipcpertag.patch new file mode 100644 index 0000000..97e3c33 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ipc/ipcpertag.patch @@ -0,0 +1,97 @@ +From c7d77ff4dec1da5a68b4da8aa42d4ed78dc41a00 Mon Sep 17 00:00:00 2001 +From: choc +Date: Thu, 14 Mar 2024 11:18:37 +0800 +Subject: [PATCH] fix ipc to work with pertag + +--- + dwl.c | 50 +++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 35 insertions(+), 15 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a1a7809..d86e6e2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1526,28 +1526,37 @@ void + dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index) + { + DwlIpcOutput *ipc_output; +- Monitor *monitor; ++ Client *c = NULL; ++ Monitor *monitor = NULL; + + ipc_output = wl_resource_get_user_data(resource); + if (!ipc_output) + return; +- + monitor = ipc_output->mon; ++ ++ if (monitor != selmon) ++ c = focustop(selmon); ++ + if (index >= LENGTH(layouts)) + return; +- if (index != monitor->lt[monitor->sellt] - layouts) +- monitor->sellt ^= 1; + +- monitor->lt[monitor->sellt] = &layouts[index]; +- arrange(monitor); +- printstatus(); ++ if (c) { ++ monitor = selmon; ++ selmon = ipc_output->mon; ++ } ++ setlayout(&(Arg){.v = &layouts[index]}); ++ if (c) { ++ selmon = monitor; ++ focusclient(c, 0); ++ } + } + + void + dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset) + { + DwlIpcOutput *ipc_output; +- Monitor *monitor; ++ Client *c = NULL; ++ Monitor *monitor = NULL; + unsigned int newtags = tagmask & TAGMASK; + + ipc_output = wl_resource_get_user_data(resource); +@@ -1555,16 +1564,27 @@ dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, + return; + monitor = ipc_output->mon; + +- if (!newtags || newtags == monitor->tagset[monitor->seltags]) ++ if (monitor != selmon) ++ c = focustop(selmon); ++ ++ if (!newtags) + return; +- if (toggle_tagset) ++ ++ /* view toggles seltags for us so we un-toggle it */ ++ if (!toggle_tagset) { + monitor->seltags ^= 1; ++ monitor->tagset[monitor->seltags] = 0; ++ } + +- monitor->tagset[monitor->seltags] = newtags; +- if (selmon == monitor) +- focusclient(focustop(monitor), 1); +- arrange(monitor); +- printstatus(); ++ if (c) { ++ monitor = selmon; ++ selmon = ipc_output->mon; ++ } ++ view(&(Arg){.ui = newtags}); ++ if (c) { ++ selmon = monitor; ++ focusclient(c, 0); ++ } + } + + void +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/kblayout/README.md b/dwl-bak/dwl-patches/patches/kblayout/README.md new file mode 100644 index 0000000..259694c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/kblayout/README.md @@ -0,0 +1,32 @@ +### Description + +This patch adds per-client keyboard layout and ability to send current +keyboard layout information to a status bar. + +Only per-client feature is enabled by default. You can edit +`kblayout_file` and `kblayout_cmd` variables to notify a status bar +about keyboard layout. + +[Someblocks](https://sr.ht/~raphi/someblocks) config that works +with the example settings in `config.h`: + +```c +static const Block blocks[] = { + /*Icon*/ /*Command*/ /*Update Interval*/ /*Update Signal*/ + {"", "cat /tmp/dwl-kblayout", 0, 3}, +}; +``` + +Both of these features are included in one patch because their +implementation happens to share some code. If you don't need +any of these features, just disable it in `config.h`. + +### Download + +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/kblayout) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/kblayout/kblayout.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [ForzCross](https://codeberg.org/ForzCross) diff --git a/dwl-bak/dwl-patches/patches/kblayout/kblayout.patch b/dwl-bak/dwl-patches/patches/kblayout/kblayout.patch new file mode 100644 index 0000000..707a4fc --- /dev/null +++ b/dwl-bak/dwl-patches/patches/kblayout/kblayout.patch @@ -0,0 +1,187 @@ +From ad18a8b8e9de138c3d89246ac0e25c0467ff5971 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 11 Oct 2024 10:50:14 +0200 +Subject: [PATCH] Add per client keyboard layout and status bar info + +--- + config.def.h | 3 +++ + dwl.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 72 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..862c104 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,9 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++/* keyboard layout change notification for status bar */ ++static const char kblayout_file[] = "/tmp/dwl-keymap"; ++static const char *kblayout_cmd[] = {"pkill", "-RTMIN+3", "someblocks", NULL}; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index a2711f6..95ca3d3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -141,6 +142,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ unsigned int kblayout_idx; + } Client; + + typedef struct { +@@ -294,6 +296,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); ++static void kblayout(KeyboardGroup *kb); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); +@@ -413,6 +416,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static unsigned int kblayout_idx = -1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -879,6 +884,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); ++ ++ kblayout(kb_group); + } + + KeyboardGroup * +@@ -1056,11 +1063,13 @@ createnotify(struct wl_listener *listener, void *data) + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; ++ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->kblayout_idx = kb ? kb->modifiers.group : 0; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1339,10 +1348,24 @@ dirtomon(enum wlr_direction dir) + void + focusclient(Client *c, int lift) + { ++ /* Copied from wlroots/types/wlr_keyboard_group.c */ ++ struct keyboard_group_device { ++ struct wlr_keyboard *keyboard; ++ struct wl_listener key; ++ struct wl_listener modifiers; ++ struct wl_listener keymap; ++ struct wl_listener repeat_info; ++ struct wl_listener destroy; ++ struct wl_list link; // wlr_keyboard_group.devices ++ }; ++ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; ++ struct keyboard_group_device *device; ++ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); ++ struct wlr_keyboard_group *group = kb ? wlr_keyboard_group_from_wlr_keyboard(kb) : NULL; + + if (locked) + return; +@@ -1395,6 +1418,19 @@ focusclient(Client *c, int lift) + } + printstatus(); + ++ /* Update keyboard layout */ ++ if (group) { ++ // Update the first real device, because kb or group->kb is not a real ++ // keyboard and its effective layout gets overwritten ++ device = wl_container_of(group->devices.next, device, link); ++ wlr_keyboard_notify_modifiers(device->keyboard, ++ device->keyboard->modifiers.depressed, ++ device->keyboard->modifiers.latched, ++ device->keyboard->modifiers.locked, ++ c ? c->kblayout_idx : 0 ++ ); ++ } ++ + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); +@@ -1405,7 +1441,7 @@ focusclient(Client *c, int lift) + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ +- client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); ++ client_notify_enter(client_surface(c), kb); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +@@ -1554,6 +1590,36 @@ inputdevice(struct wl_listener *listener, void *data) + wlr_seat_set_capabilities(seat, caps); + } + ++void ++kblayout(KeyboardGroup *kb) ++{ ++ FILE *f; ++ Client *c; ++ unsigned int idx = kb->wlr_group->keyboard.modifiers.group; ++ ++ // If layout did not change, do nothing ++ if (kblayout_idx == idx) ++ return; ++ kblayout_idx = idx; ++ ++ // Update client layout ++ if ((c = focustop(selmon))) ++ c->kblayout_idx = kblayout_idx; ++ ++ // Save current layout to kblayout_file ++ if (*kblayout_file && (f = fopen(kblayout_file, "w"))) { ++ fputs(xkb_keymap_layout_get_name(kb->wlr_group->keyboard.keymap, ++ idx), f); ++ fclose(f); ++ } ++ ++ // Run kblayout_cmd ++ if (kblayout_cmd[0] && fork() == 0) { ++ execvp(kblayout_cmd[0], (char *const *)kblayout_cmd); ++ die("dwl: execvp %s failed:", kblayout_cmd[0]); ++ } ++} ++ + int + keybinding(uint32_t mods, xkb_keysym_t sym) + { +@@ -1631,6 +1697,8 @@ keypressmod(struct wl_listener *listener, void *data) + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); ++ ++ kblayout(group); + } + + int +-- +2.46.2 + diff --git a/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/README.md b/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/README.md new file mode 100644 index 0000000..c52de56 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/README.md @@ -0,0 +1,11 @@ +### Description +Allows clients to use the keyboard-shortcuts-inhibit protocol to block the compositor from using keybinds. This is useful for virtualization software like looking-glass which requires this protocol to run. + +### Download +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/patch/keyboard-shortcuts-inhibit) +- [2024-05-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch) +- [2023-05-01](https://github.com/djpohly/dwl/compare/main...madcowog:keyboard-shortcuts-inhibit.patch) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [MadcowOG](https://github.com/MadcowOG) diff --git a/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch b/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch new file mode 100644 index 0000000..bea327e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch @@ -0,0 +1,101 @@ +From ac1fa09172a401427cabbda045688903bdd3cbe7 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Wed, 12 Jun 2024 20:05:40 +0200 +Subject: [PATCH] feat: apply keyboard shortcuts inhibit patch + +--- + dwl.c | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 6f041a0..8cab9e0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -267,6 +268,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); ++static void createshortcutsinhibitor(struct wl_listener *listener, void *data); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); + static void destroydecoration(struct wl_listener *listener, void *data); +@@ -280,6 +282,7 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroyshortcutsinhibitmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -375,6 +378,7 @@ static struct wl_list clients; /* tiling order */ + static struct wl_list fstack; /* focus order */ + static struct wlr_idle_notifier_v1 *idle_notifier; + static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; ++static struct wlr_keyboard_shortcuts_inhibit_manager_v1 *shortcuts_inhibit_mgr; + static struct wlr_layer_shell_v1 *layer_shell; + static struct wlr_output_manager_v1 *output_mgr; + static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +@@ -394,6 +398,7 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; ++static struct wl_listener new_shortcuts_inhibitor = {.notify = createshortcutsinhibitor}; + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; +@@ -1083,6 +1088,10 @@ createpointer(struct wlr_pointer *pointer) + wlr_cursor_attach_input_device(cursor, &pointer->base); + } + ++void createshortcutsinhibitor(struct wl_listener *listener, void *data) { ++ wlr_keyboard_shortcuts_inhibitor_v1_activate(data); ++} ++ + void + createpointerconstraint(struct wl_listener *listener, void *data) + { +@@ -1280,6 +1289,11 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void destroyshortcutsinhibitmgr(struct wl_listener *listener, void *data) { ++ wl_list_remove(&new_shortcuts_inhibitor.link); ++ wl_list_remove(&listener->link); ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1531,7 +1545,9 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { ++ if (!locked ++ && event->state == WL_KEYBOARD_KEY_STATE_PRESSED ++ && wl_list_empty(&shortcuts_inhibit_mgr->inhibitors)) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } +@@ -2450,6 +2466,10 @@ setup(void) + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + ++ shortcuts_inhibit_mgr = wlr_keyboard_shortcuts_inhibit_v1_create(dpy); ++ wl_signal_add(&shortcuts_inhibit_mgr->events.new_inhibitor, &new_shortcuts_inhibitor); ++ LISTEN_STATIC(&shortcuts_inhibit_mgr->events.destroy, destroyshortcutsinhibitmgr); ++ + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), +-- +2.44.1 + diff --git a/dwl-bak/dwl-patches/patches/keycodes/README.md b/dwl-bak/dwl-patches/patches/keycodes/README.md new file mode 100644 index 0000000..b56adc3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/keycodes/README.md @@ -0,0 +1,20 @@ +### Description +Use keycodes instead of keysyms. This way, input is independent from keyboard +layout (you can use the keys.h file to customize, or get the keycodes with +`wev` or `xkbcli interactive-wayland` (x11-libs/libxkbcommon[tools] in gentoo)). + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/keycodes) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/keycodes/keycodes.patch) +- [keycodes-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/keycodes/keycodes-0.7.patch) + +### Config after patching +(run in DWL source directory) +``` +export XKB_DEFAULT_VARIANT=yourbestkeyboardlayout +cc -lxkbcommon -o generate-keys generate-keys.c +./generate-keys +``` + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/keycodes/keycodes-0.7.patch b/dwl-bak/dwl-patches/patches/keycodes/keycodes-0.7.patch new file mode 100644 index 0000000..7852307 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/keycodes/keycodes-0.7.patch @@ -0,0 +1,883 @@ +From 4985178cfed599c9a32d154f42e02cb66db7a82d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Fri, 4 Jun 2021 16:51:01 -0500 +Subject: [PATCH 1/2] allow use keycodes instead keysyms +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 85 ++++----- + dwl.c | 35 ++-- + keys.h | 514 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 569 insertions(+), 65 deletions(-) + create mode 100644 keys.h + +diff --git a/config.def.h b/config.def.h +index 22d2171d..87a6e60f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -109,11 +109,11 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +-#define TAGKEYS(KEY,SKEY,TAG) \ ++#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_SHIFT, KEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,KEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -122,51 +122,52 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#include "keys.h" + static const Key keys[] = { +- /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, +- TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), +- TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), +- TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +- TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), +- TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), +- TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), +- TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), +- TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), +- TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ /* modifier key function argument */ ++ { MODKEY, Key_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_Return, spawn, {.v = termcmd} }, ++ { MODKEY, Key_j, focusstack, {.i = +1} }, ++ { MODKEY, Key_k, focusstack, {.i = -1} }, ++ { MODKEY, Key_i, incnmaster, {.i = +1} }, ++ { MODKEY, Key_d, incnmaster, {.i = -1} }, ++ { MODKEY, Key_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, Key_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, Key_Return, zoom, {0} }, ++ { MODKEY, Key_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_c, killclient, {0} }, ++ { MODKEY, Key_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, Key_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, Key_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, Key_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_space, togglefloating, {0} }, ++ { MODKEY, Key_e, togglefullscreen, {0} }, ++ { MODKEY, Key_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_0, tag, {.ui = ~0} }, ++ { MODKEY, Key_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, Key_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_period, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ TAGKEYS( Key_1, 0), ++ TAGKEYS( Key_2, 1), ++ TAGKEYS( Key_3, 2), ++ TAGKEYS( Key_4, 3), ++ TAGKEYS( Key_5, 4), ++ TAGKEYS( Key_6, 5), ++ TAGKEYS( Key_7, 6), ++ TAGKEYS( Key_8, 7), ++ TAGKEYS( Key_9, 8), ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,Key_BackSpace, quit, {0} }, ++#define CHVT(KEY,n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT, KEY, chvt, {.ui = (n)} } + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } +- CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), +- CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++ CHVT(Key_F1, 1), CHVT(Key_F2, 2), CHVT(Key_F3, 3), CHVT(Key_F4, 4), ++ CHVT(Key_F5, 5), CHVT(Key_F6, 6), CHVT(Key_F7, 7), CHVT(Key_F8, 8), ++ CHVT(Key_F9, 9), CHVT(Key_F10, 10), CHVT(Key_F11, 11), CHVT(Key_F12, 12), + }; + + static const Button buttons[] = { +diff --git a/dwl.c b/dwl.c +index a2711f67..68860d4e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -145,7 +145,7 @@ typedef struct { + + typedef struct { + uint32_t mod; +- xkb_keysym_t keysym; ++ xkb_keycode_t keycode; + void (*func)(const Arg *); + const Arg arg; + } Key; +@@ -154,9 +154,8 @@ typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + +- int nsyms; +- const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ +- uint32_t mods; /* invalid if nsyms == 0 */ ++ xkb_keycode_t keycode; ++ uint32_t mods; /* invalid if keycode == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; +@@ -294,7 +293,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keycode_t keycode); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1555,7 +1554,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keycode_t keycode) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1565,7 +1564,7 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && keycode == k->keycode && k->func) { + k->func(&k->arg); + return 1; + } +@@ -1576,17 +1575,12 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + void + keypress(struct wl_listener *listener, void *data) + { +- int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +- /* Get a list of keysyms based on the keymap for this keyboard */ +- const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); +@@ -1595,19 +1589,16 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { +- for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; +- } ++ if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ handled = keybinding(mods, keycode); + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; +- group->keysyms = syms; +- group->nsyms = nsyms; ++ group->keycode = keycode; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { +- group->nsyms = 0; ++ group->keycode = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + +@@ -1637,15 +1628,13 @@ int + keyrepeat(void *data) + { + KeyboardGroup *group = data; +- int i; +- if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) ++ if (!group->keycode || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + +- for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keycode); + + return 0; + } +diff --git a/keys.h b/keys.h +new file mode 100644 +index 00000000..047b76b0 +--- /dev/null ++++ b/keys.h +@@ -0,0 +1,514 @@ ++/* You can use the macros within this file ++ * instead of search the keycodes yourself ++ * with wev or something like that ++ * You probably are also searching these: ++ * Key_XF86AudioMute ++ * Key_XF86AudioLowerVolume ++ * Key_XF86AudioRaiseVolume ++ * Key_XF86MonBrightnessDown ++ * Key_XF86MonBrightnessUp ++*/ ++ ++#define Key_Escape 0x009 ++#define Key_1 0x00a ++#define Key_exclam 0x00a ++#define Key_2 0x00b ++#define Key_at 0x00b ++#define Key_3 0x00c ++#define Key_numbersign 0x00c ++#define Key_4 0x00d ++#define Key_dollar 0x00d ++#define Key_5 0x00e ++#define Key_percent 0x00e ++#define Key_6 0x00f ++#define Key_asciicircum 0x00f ++#define Key_7 0x010 ++#define Key_ampersand 0x010 ++#define Key_8 0x011 ++#define Key_asterisk 0x011 ++#define Key_9 0x012 ++#define Key_parenleft 0x012 ++#define Key_0 0x013 ++#define Key_parenright 0x013 ++#define Key_minus 0x014 ++#define Key_underscore 0x014 ++#define Key_equal 0x015 ++#define Key_plus 0x015 ++#define Key_BackSpace 0x016 ++#define Key_Tab 0x017 ++#define Key_ISO_Left_Tab 0x017 ++#define Key_q 0x018 ++#define Key_Q 0x018 ++#define Key_w 0x019 ++#define Key_W 0x019 ++#define Key_e 0x01a ++#define Key_E 0x01a ++#define Key_r 0x01b ++#define Key_R 0x01b ++#define Key_t 0x01c ++#define Key_T 0x01c ++#define Key_y 0x01d ++#define Key_Y 0x01d ++#define Key_u 0x01e ++#define Key_U 0x01e ++#define Key_i 0x01f ++#define Key_I 0x01f ++#define Key_o 0x020 ++#define Key_O 0x020 ++#define Key_p 0x021 ++#define Key_P 0x021 ++#define Key_bracketleft 0x022 ++#define Key_braceleft 0x022 ++#define Key_bracketright 0x023 ++#define Key_braceright 0x023 ++#define Key_Return 0x024 ++#define Key_Control_L 0x025 ++#define Key_a 0x026 ++#define Key_A 0x026 ++#define Key_s 0x027 ++#define Key_S 0x027 ++#define Key_d 0x028 ++#define Key_D 0x028 ++#define Key_f 0x029 ++#define Key_F 0x029 ++#define Key_g 0x02a ++#define Key_G 0x02a ++#define Key_h 0x02b ++#define Key_H 0x02b ++#define Key_j 0x02c ++#define Key_J 0x02c ++#define Key_k 0x02d ++#define Key_K 0x02d ++#define Key_l 0x02e ++#define Key_L 0x02e ++#define Key_semicolon 0x02f ++#define Key_colon 0x02f ++#define Key_apostrophe 0x030 ++#define Key_quotedbl 0x030 ++#define Key_grave 0x031 ++#define Key_asciitilde 0x031 ++#define Key_Shift_L 0x032 ++#define Key_backslash 0x033 ++#define Key_bar 0x033 ++#define Key_z 0x034 ++#define Key_Z 0x034 ++#define Key_x 0x035 ++#define Key_X 0x035 ++#define Key_c 0x036 ++#define Key_C 0x036 ++#define Key_v 0x037 ++#define Key_V 0x037 ++#define Key_b 0x038 ++#define Key_B 0x038 ++#define Key_n 0x039 ++#define Key_N 0x039 ++#define Key_m 0x03a ++#define Key_M 0x03a ++#define Key_comma 0x03b ++#define Key_less 0x03b ++#define Key_period 0x03c ++#define Key_greater 0x03c ++#define Key_slash 0x03d ++#define Key_question 0x03d ++#define Key_Shift_R 0x03e ++#define Key_KP_Multiply 0x03f ++#define Key_XF86ClearGrab 0x03f ++#define Key_Alt_L 0x040 ++#define Key_Meta_L 0x040 ++#define Key_space 0x041 ++#define Key_Caps_Lock 0x042 ++#define Key_F1 0x043 ++#define Key_XF86Switch_VT_1 0x043 ++#define Key_F2 0x044 ++#define Key_XF86Switch_VT_2 0x044 ++#define Key_F3 0x045 ++#define Key_XF86Switch_VT_3 0x045 ++#define Key_F4 0x046 ++#define Key_XF86Switch_VT_4 0x046 ++#define Key_F5 0x047 ++#define Key_XF86Switch_VT_5 0x047 ++#define Key_F6 0x048 ++#define Key_XF86Switch_VT_6 0x048 ++#define Key_F7 0x049 ++#define Key_XF86Switch_VT_7 0x049 ++#define Key_F8 0x04a ++#define Key_XF86Switch_VT_8 0x04a ++#define Key_F9 0x04b ++#define Key_XF86Switch_VT_9 0x04b ++#define Key_F10 0x04c ++#define Key_XF86Switch_VT_10 0x04c ++#define Key_Num_Lock 0x04d ++#define Key_Scroll_Lock 0x04e ++#define Key_KP_Home 0x04f ++#define Key_KP_7 0x04f ++#define Key_KP_Up 0x050 ++#define Key_KP_8 0x050 ++#define Key_KP_Prior 0x051 ++#define Key_KP_9 0x051 ++#define Key_KP_Subtract 0x052 ++#define Key_XF86Prev_VMode 0x052 ++#define Key_KP_Left 0x053 ++#define Key_KP_4 0x053 ++#define Key_KP_Begin 0x054 ++#define Key_KP_5 0x054 ++#define Key_KP_Right 0x055 ++#define Key_KP_6 0x055 ++#define Key_KP_Add 0x056 ++#define Key_XF86Next_VMode 0x056 ++#define Key_KP_End 0x057 ++#define Key_KP_1 0x057 ++#define Key_KP_Down 0x058 ++#define Key_KP_2 0x058 ++#define Key_KP_Next 0x059 ++#define Key_KP_3 0x059 ++#define Key_KP_Insert 0x05a ++#define Key_KP_0 0x05a ++#define Key_KP_Delete 0x05b ++#define Key_KP_Decimal 0x05b ++#define Key_ISO_Level3_Shift 0x05c ++#define Key_less2 0x05e ++#define Key_greater2 0x05e ++#define Key_bar2 0x05e ++#define Key_brokenbar 0x05e ++#define Key_F11 0x05f ++#define Key_XF86Switch_VT_11 0x05f ++#define Key_F12 0x060 ++#define Key_XF86Switch_VT_12 0x060 ++#define Key_Katakana 0x062 ++#define Key_Hiragana 0x063 ++#define Key_Henkan_Mode 0x064 ++#define Key_Hiragana_Katakana 0x065 ++#define Key_Muhenkan 0x066 ++#define Key_KP_Enter 0x068 ++#define Key_Control_R 0x069 ++#define Key_KP_Divide 0x06a ++#define Key_XF86Ungrab 0x06a ++#define Key_Print 0x06b ++#define Key_Sys_Req 0x06b ++#define Key_Alt_R 0x06c ++#define Key_Meta_R 0x06c ++#define Key_Linefeed 0x06d ++#define Key_Home 0x06e ++#define Key_Up 0x06f ++#define Key_Prior 0x070 ++#define Key_Left 0x071 ++#define Key_Right 0x072 ++#define Key_End 0x073 ++#define Key_Down 0x074 ++#define Key_Next 0x075 ++#define Key_Insert 0x076 ++#define Key_Delete 0x077 ++#define Key_XF86AudioMute 0x079 ++#define Key_XF86AudioLowerVolume 0x07a ++#define Key_XF86AudioRaiseVolume 0x07b ++#define Key_XF86PowerOff 0x07c ++#define Key_KP_Equal 0x07d ++#define Key_plusminus 0x07e ++#define Key_Pause 0x07f ++#define Key_Break 0x07f ++#define Key_XF86LaunchA 0x080 ++#define Key_KP_Decimal2 0x081 ++#define Key_Hangul 0x082 ++#define Key_Hangul_Hanja 0x083 ++#define Key_Super_L 0x085 ++#define Key_Super_R 0x086 ++#define Key_Menu 0x087 ++#define Key_Cancel 0x088 ++#define Key_Redo 0x089 ++#define Key_SunProps 0x08a ++#define Key_Undo 0x08b ++#define Key_SunFront 0x08c ++#define Key_XF86Copy 0x08d ++#define Key_XF86Open 0x08e ++#define Key_XF86Paste 0x08f ++#define Key_Find 0x090 ++#define Key_XF86Cut 0x091 ++#define Key_Help 0x092 ++#define Key_XF86MenuKB 0x093 ++#define Key_XF86Calculator 0x094 ++#define Key_XF86Sleep 0x096 ++#define Key_XF86WakeUp 0x097 ++#define Key_XF86Explorer 0x098 ++#define Key_XF86Send 0x099 ++#define Key_XF86Xfer 0x09b ++#define Key_XF86Launch1 0x09c ++#define Key_XF86Launch2 0x09d ++#define Key_XF86WWW 0x09e ++#define Key_XF86DOS 0x09f ++#define Key_XF86ScreenSaver 0x0a0 ++#define Key_XF86RotateWindows 0x0a1 ++#define Key_XF86TaskPane 0x0a2 ++#define Key_XF86Mail 0x0a3 ++#define Key_XF86Favorites 0x0a4 ++#define Key_XF86MyComputer 0x0a5 ++#define Key_XF86Back 0x0a6 ++#define Key_XF86Forward 0x0a7 ++#define Key_XF86Eject 0x0a9 ++#define Key_XF86Eject2 0x0aa ++#define Key_XF86AudioNext 0x0ab ++#define Key_XF86AudioPlay 0x0ac ++#define Key_XF86AudioPause 0x0ac ++#define Key_XF86AudioPrev 0x0ad ++#define Key_XF86AudioStop 0x0ae ++#define Key_XF86Eject3 0x0ae ++#define Key_XF86AudioRecord 0x0af ++#define Key_XF86AudioRewind 0x0b0 ++#define Key_XF86Phone 0x0b1 ++#define Key_XF86Tools 0x0b3 ++#define Key_XF86HomePage 0x0b4 ++#define Key_XF86Reload 0x0b5 ++#define Key_XF86Close 0x0b6 ++#define Key_XF86ScrollUp 0x0b9 ++#define Key_XF86ScrollDown 0x0ba ++#define Key_parenleft2 0x0bb ++#define Key_parenright2 0x0bc ++#define Key_XF86New 0x0bd ++#define Key_Redo2 0x0be ++#define Key_XF86Tools2 0x0bf ++#define Key_XF86Launch5 0x0c0 ++#define Key_XF86Launch6 0x0c1 ++#define Key_XF86Launch7 0x0c2 ++#define Key_XF86Launch8 0x0c3 ++#define Key_XF86Launch9 0x0c4 ++#define Key_XF86AudioMicMute 0x0c6 ++#define Key_XF86TouchpadToggle 0x0c7 ++#define Key_XF86TouchpadOn 0x0c8 ++#define Key_XF86TouchpadOff 0x0c9 ++#define Key_ISO_Level5_Shift 0x0cb ++#define Key_Alt_L2 0x0cc ++#define Key_Meta_L2 0x0cd ++#define Key_Super_L2 0x0ce ++#define Key_Hyper_L 0x0cf ++#define Key_XF86AudioPlay2 0x0d0 ++#define Key_XF86AudioPause2 0x0d1 ++#define Key_XF86Launch3 0x0d2 ++#define Key_XF86Launch4 0x0d3 ++#define Key_XF86LaunchB 0x0d4 ++#define Key_XF86Suspend 0x0d5 ++#define Key_XF86Close2 0x0d6 ++#define Key_XF86AudioPlay3 0x0d7 ++#define Key_XF86AudioForward 0x0d8 ++#define Key_Print2 0x0da ++#define Key_XF86WebCam 0x0dc ++#define Key_XF86AudioPreset 0x0dd ++#define Key_XF86Mail2 0x0df ++#define Key_XF86Messenger 0x0e0 ++#define Key_XF86Search 0x0e1 ++#define Key_XF86Go 0x0e2 ++#define Key_XF86Finance 0x0e3 ++#define Key_XF86Game 0x0e4 ++#define Key_XF86Shop 0x0e5 ++#define Key_Cancel2 0x0e7 ++#define Key_XF86MonBrightnessDown 0x0e8 ++#define Key_XF86MonBrightnessUp 0x0e9 ++#define Key_XF86AudioMedia 0x0ea ++#define Key_XF86Display 0x0eb ++#define Key_XF86KbdLightOnOff 0x0ec ++#define Key_XF86KbdBrightnessDown 0x0ed ++#define Key_XF86KbdBrightnessUp 0x0ee ++#define Key_XF86Send2 0x0ef ++#define Key_XF86Reply 0x0f0 ++#define Key_XF86MailForward 0x0f1 ++#define Key_XF86Save 0x0f2 ++#define Key_XF86Documents 0x0f3 ++#define Key_XF86Battery 0x0f4 ++#define Key_XF86Bluetooth 0x0f5 ++#define Key_XF86WLAN 0x0f6 ++#define Key_XF86UWB 0x0f7 ++#define Key_XF86Next_VMode2 0x0f9 ++#define Key_XF86Prev_VMode2 0x0fa ++#define Key_XF86MonBrightnessCycle 0x0fb ++#define Key_XF86BrightnessAuto 0x0fc ++#define Key_XF86DisplayOff 0x0fd ++#define Key_XF86WWAN 0x0fe ++#define Key_XF86RFKill 0x0ff ++#define Key_XF86AudioMicMute2 0x100 ++#define Key_XF86Info 0x16e ++#define Key_XF86Favorites2 0x174 ++#define Key_XF86CycleAngle 0x17b ++#define Key_XF86FullScreen 0x17c ++#define Key_XF86Keyboard 0x17e ++#define Key_XF86AspectRatio 0x17f ++#define Key_XF86DVD 0x18d ++#define Key_XF86Audio 0x190 ++#define Key_XF86Video 0x191 ++#define Key_XF86Calendar 0x195 ++#define Key_XF86ChannelUp 0x19a ++#define Key_XF86ChannelDown 0x19b ++#define Key_XF86AudioRandomPlay 0x1a2 ++#define Key_XF86Break 0x1a3 ++#define Key_XF86VideoPhone 0x1a8 ++#define Key_XF86Game2 0x1a9 ++#define Key_XF86ZoomIn 0x1aa ++#define Key_XF86ZoomOut 0x1ab ++#define Key_XF86ZoomReset 0x1ac ++#define Key_XF86Word 0x1ad ++#define Key_XF86Editor 0x1ae ++#define Key_XF86Excel 0x1af ++#define Key_XF86GraphicsEditor 0x1b0 ++#define Key_XF86Presentation 0x1b1 ++#define Key_XF86Database 0x1b2 ++#define Key_XF86News 0x1b3 ++#define Key_XF86Voicemail 0x1b4 ++#define Key_XF86Addressbook 0x1b5 ++#define Key_XF86Messenger2 0x1b6 ++#define Key_XF86DisplayToggle 0x1b7 ++#define Key_XF86SpellCheck 0x1b8 ++#define Key_XF86LogOff 0x1b9 ++#define Key_dollar2 0x1ba ++#define Key_EuroSign 0x1bb ++#define Key_XF86FrameBack 0x1bc ++#define Key_XF86FrameForward 0x1bd ++#define Key_XF86ContextMenu 0x1be ++#define Key_XF86MediaRepeat 0x1bf ++#define Key_XF8610ChannelsUp 0x1c0 ++#define Key_XF8610ChannelsDown 0x1c1 ++#define Key_XF86Images 0x1c2 ++#define Key_XF86NotificationCenter 0x1c4 ++#define Key_XF86PickupPhone 0x1c5 ++#define Key_XF86HangupPhone 0x1c6 ++#define Key_XF86Fn 0x1d8 ++#define Key_XF86Fn_Esc 0x1d9 ++#define Key_XF86FnRightShift 0x1ed ++#define Key_braille_dot_1 0x1f9 ++#define Key_braille_dot_2 0x1fa ++#define Key_braille_dot_3 0x1fb ++#define Key_braille_dot_4 0x1fc ++#define Key_braille_dot_5 0x1fd ++#define Key_braille_dot_6 0x1fe ++#define Key_braille_dot_7 0x1ff ++#define Key_braille_dot_8 0x200 ++#define Key_braille_dot_9 0x201 ++#define Key_braille_dot_1_2 0x202 ++#define Key_XF86Numeric0 0x208 ++#define Key_XF86Numeric1 0x209 ++#define Key_XF86Numeric2 0x20a ++#define Key_XF86Numeric3 0x20b ++#define Key_XF86Numeric4 0x20c ++#define Key_XF86Numeric5 0x20d ++#define Key_XF86Numeric6 0x20e ++#define Key_XF86Numeric7 0x20f ++#define Key_XF86Numeric8 0x210 ++#define Key_XF86Numeric9 0x211 ++#define Key_XF86NumericStar 0x212 ++#define Key_XF86NumericPound 0x213 ++#define Key_XF86NumericA 0x214 ++#define Key_XF86NumericB 0x215 ++#define Key_XF86NumericC 0x216 ++#define Key_XF86NumericD 0x217 ++#define Key_XF86CameraFocus 0x218 ++#define Key_XF86WPSButton 0x219 ++#define Key_XF86TouchpadToggle2 0x21a ++#define Key_XF86TouchpadOn2 0x21b ++#define Key_XF86TouchpadOff2 0x21c ++#define Key_XF86CameraZoomIn 0x21d ++#define Key_XF86CameraZoomOut 0x21e ++#define Key_XF86CameraUp 0x21f ++#define Key_XF86CameraDown 0x220 ++#define Key_XF86CameraLeft 0x221 ++#define Key_XF86CameraRight 0x222 ++#define Key_XF86AttendantOn 0x223 ++#define Key_XF86AttendantOff 0x224 ++#define Key_XF86AttendantToggle 0x225 ++#define Key_XF86LightsToggle 0x226 ++#define Key_XF86ALSToggle 0x238 ++#define Key_XF86RotationLockToggle 0x239 ++#define Key_XF86Buttonconfig 0x248 ++#define Key_XF86Taskmanager 0x249 ++#define Key_XF86Journal 0x24a ++#define Key_XF86ControlPanel 0x24b ++#define Key_XF86AppSelect 0x24c ++#define Key_XF86Screensaver 0x24d ++#define Key_XF86VoiceCommand 0x24e ++#define Key_XF86Assistant 0x24f ++#define Key_ISO_Next_Group 0x250 ++#define Key_XF86EmojiPicker 0x251 ++#define Key_XF86Dictate 0x252 ++#define Key_XF86CameraAccessEnable 0x253 ++#define Key_XF86CameraAccessDisable 0x254 ++#define Key_XF86CameraAccessToggle 0x255 ++#define Key_XF86BrightnessMin 0x258 ++#define Key_XF86BrightnessMax 0x259 ++#define Key_XF86KbdInputAssistPrev 0x268 ++#define Key_XF86KbdInputAssistNext 0x269 ++#define Key_XF86KbdInputAssistPrevgroup 0x26a ++#define Key_XF86KbdInputAssistNextgroup 0x26b ++#define Key_XF86KbdInputAssistAccept 0x26c ++#define Key_XF86KbdInputAssistCancel 0x26d ++#define Key_XF86RightUp 0x26e ++#define Key_XF86RightDown 0x26f ++#define Key_XF86LeftUp 0x270 ++#define Key_XF86LeftDown 0x271 ++#define Key_XF86RootMenu 0x272 ++#define Key_XF86MediaTopMenu 0x273 ++#define Key_XF86Numeric11 0x274 ++#define Key_XF86Numeric12 0x275 ++#define Key_XF86AudioDesc 0x276 ++#define Key_XF863DMode 0x277 ++#define Key_XF86NextFavorite 0x278 ++#define Key_XF86StopRecord 0x279 ++#define Key_XF86PauseRecord 0x27a ++#define Key_XF86VOD 0x27b ++#define Key_XF86Unmute 0x27c ++#define Key_XF86FastReverse 0x27d ++#define Key_XF86SlowReverse 0x27e ++#define Key_XF86Data 0x27f ++#define Key_XF86OnScreenKeyboard 0x280 ++#define Key_XF86PrivacyScreenToggle 0x281 ++#define Key_XF86SelectiveScreenshot 0x282 ++#define Key_XF86NextElement 0x283 ++#define Key_XF86PreviousElement 0x284 ++#define Key_XF86AutopilotEngageToggle 0x285 ++#define Key_XF86MarkWaypoint 0x286 ++#define Key_XF86Sos 0x287 ++#define Key_XF86NavChart 0x288 ++#define Key_XF86FishingChart 0x289 ++#define Key_XF86SingleRangeRadar 0x28a ++#define Key_XF86DualRangeRadar 0x28b ++#define Key_XF86RadarOverlay 0x28c ++#define Key_XF86TraditionalSonar 0x28d ++#define Key_XF86ClearvuSonar 0x28e ++#define Key_XF86SidevuSonar 0x28f ++#define Key_XF86NavInfo 0x290 ++#define Key_XF86BrightnessAdjust 0x291 ++#define Key_XF86Macro1 0x298 ++#define Key_XF86Macro2 0x299 ++#define Key_XF86Macro3 0x29a ++#define Key_XF86Macro4 0x29b ++#define Key_XF86Macro5 0x29c ++#define Key_XF86Macro6 0x29d ++#define Key_XF86Macro7 0x29e ++#define Key_XF86Macro8 0x29f ++#define Key_XF86Macro9 0x2a0 ++#define Key_XF86Macro10 0x2a1 ++#define Key_XF86Macro11 0x2a2 ++#define Key_XF86Macro12 0x2a3 ++#define Key_XF86Macro13 0x2a4 ++#define Key_XF86Macro14 0x2a5 ++#define Key_XF86Macro15 0x2a6 ++#define Key_XF86Macro16 0x2a7 ++#define Key_XF86Macro17 0x2a8 ++#define Key_XF86Macro18 0x2a9 ++#define Key_XF86Macro19 0x2aa ++#define Key_XF86Macro20 0x2ab ++#define Key_XF86Macro21 0x2ac ++#define Key_XF86Macro22 0x2ad ++#define Key_XF86Macro23 0x2ae ++#define Key_XF86Macro24 0x2af ++#define Key_XF86Macro25 0x2b0 ++#define Key_XF86Macro26 0x2b1 ++#define Key_XF86Macro27 0x2b2 ++#define Key_XF86Macro28 0x2b3 ++#define Key_XF86Macro29 0x2b4 ++#define Key_XF86Macro30 0x2b5 ++#define Key_XF86MacroRecordStart 0x2b8 ++#define Key_XF86MacroRecordStop 0x2b9 ++#define Key_XF86MacroPresetCycle 0x2ba ++#define Key_XF86MacroPreset1 0x2bb ++#define Key_XF86MacroPreset2 0x2bc ++#define Key_XF86MacroPreset3 0x2bd ++#define Key_XF86KbdLcdMenu1 0x2c0 ++#define Key_XF86KbdLcdMenu2 0x2c1 ++#define Key_XF86KbdLcdMenu3 0x2c2 ++#define Key_XF86KbdLcdMenu4 0x2c3 ++#define Key_XF86KbdLcdMenu5 0x2c4 +-- +2.46.0 + + +From cc7d880886d91cd8d39ab31a48d03d1f745598d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 19 Mar 2023 15:50:07 -0600 +Subject: [PATCH 2/2] add program to generate keys.h +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + generate-keys.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) + create mode 100644 generate-keys.c + +diff --git a/generate-keys.c b/generate-keys.c +new file mode 100644 +index 00000000..37655611 +--- /dev/null ++++ b/generate-keys.c +@@ -0,0 +1,95 @@ ++/****************************************************************** ++ * Copyright 2023-2024 Leonardo Hernández Hernández ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the “Software”), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ++ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ ******************************************************************/ ++ ++/* cc -lxkbcommon -o generate-keys generate-keys.c */ ++ ++#define _DEFAULT_SOURCE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int ++main(void) ++{ ++ /* Allow generate keys with a different layout and variant. ++ * You can also use XKB_DEFAULT_* environmental variables and let this as is */ ++ struct xkb_rule_names rules = { ++ 0 ++ }; ++ struct xkb_context *context = NULL; ++ struct xkb_keymap *keymap = NULL; ++ xkb_keycode_t keycode, min_keycode, max_keycode; ++ xkb_layout_index_t layout, num_layouts; ++ xkb_level_index_t level, num_levels; ++ int i, nsyms; ++ const xkb_keysym_t *syms; ++ char keyname[64]; ++ bool ok = false; ++ FILE *file = fopen("keys.h", "w"); ++ if (!file) { ++ perror("Couldn't open keys.h"); ++ return EXIT_FAILURE; ++ } ++ ++ if (!(context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon context\n", stderr); ++ goto out; ++ } ++ ++ if (!(keymap = xkb_keymap_new_from_names(context, &rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon keymap\n", stderr); ++ goto out; ++ } ++ ++ min_keycode = xkb_keymap_min_keycode(keymap); ++ max_keycode = xkb_keymap_max_keycode(keymap); ++ ++ for (keycode = min_keycode; keycode <= max_keycode; keycode++) { ++ num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode); ++ for (layout = 0; layout < num_layouts; layout++) { ++ num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout); ++ for (level = 0; level < num_levels; level++) { ++ nsyms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms); ++ for (i = 0; i < nsyms; i++) { ++ xkb_keysym_get_name(syms[i], keyname, sizeof(keyname) / sizeof(keyname[0])); ++ fprintf(file, "#define Key_%-27s %#05"PRIx32"\n", keyname, keycode); ++ } ++ } ++ } ++ } ++ ++ ok = true; ++ sync(); ++ ++out: ++ fclose(file); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ return !ok; ++} +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/keycodes/keycodes.patch b/dwl-bak/dwl-patches/patches/keycodes/keycodes.patch new file mode 100644 index 0000000..cc7e896 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/keycodes/keycodes.patch @@ -0,0 +1,883 @@ +From 308eda668e1d6496a605e44be34fd44c841a1133 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Fri, 4 Jun 2021 16:51:01 -0500 +Subject: [PATCH 1/2] allow use keycodes instead keysyms +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 85 ++++----- + dwl.c | 35 ++-- + keys.h | 514 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 569 insertions(+), 65 deletions(-) + create mode 100644 keys.h + +diff --git a/config.def.h b/config.def.h +index 22d2171d..87a6e60f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -109,11 +109,11 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +-#define TAGKEYS(KEY,SKEY,TAG) \ ++#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_SHIFT, KEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,KEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -122,51 +122,52 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#include "keys.h" + static const Key keys[] = { +- /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, +- TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), +- TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), +- TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +- TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), +- TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), +- TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), +- TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), +- TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), +- TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ /* modifier key function argument */ ++ { MODKEY, Key_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_Return, spawn, {.v = termcmd} }, ++ { MODKEY, Key_j, focusstack, {.i = +1} }, ++ { MODKEY, Key_k, focusstack, {.i = -1} }, ++ { MODKEY, Key_i, incnmaster, {.i = +1} }, ++ { MODKEY, Key_d, incnmaster, {.i = -1} }, ++ { MODKEY, Key_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, Key_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, Key_Return, zoom, {0} }, ++ { MODKEY, Key_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_c, killclient, {0} }, ++ { MODKEY, Key_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, Key_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, Key_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, Key_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_space, togglefloating, {0} }, ++ { MODKEY, Key_e, togglefullscreen, {0} }, ++ { MODKEY, Key_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_0, tag, {.ui = ~0} }, ++ { MODKEY, Key_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, Key_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_period, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ TAGKEYS( Key_1, 0), ++ TAGKEYS( Key_2, 1), ++ TAGKEYS( Key_3, 2), ++ TAGKEYS( Key_4, 3), ++ TAGKEYS( Key_5, 4), ++ TAGKEYS( Key_6, 5), ++ TAGKEYS( Key_7, 6), ++ TAGKEYS( Key_8, 7), ++ TAGKEYS( Key_9, 8), ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,Key_BackSpace, quit, {0} }, ++#define CHVT(KEY,n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT, KEY, chvt, {.ui = (n)} } + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } +- CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), +- CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++ CHVT(Key_F1, 1), CHVT(Key_F2, 2), CHVT(Key_F3, 3), CHVT(Key_F4, 4), ++ CHVT(Key_F5, 5), CHVT(Key_F6, 6), CHVT(Key_F7, 7), CHVT(Key_F8, 8), ++ CHVT(Key_F9, 9), CHVT(Key_F10, 10), CHVT(Key_F11, 11), CHVT(Key_F12, 12), + }; + + static const Button buttons[] = { +diff --git a/dwl.c b/dwl.c +index ad21e1ba..1c9e0ae5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,7 +143,7 @@ typedef struct { + + typedef struct { + uint32_t mod; +- xkb_keysym_t keysym; ++ xkb_keycode_t keycode; + void (*func)(const Arg *); + const Arg arg; + } Key; +@@ -151,9 +151,8 @@ typedef struct { + typedef struct { + struct wlr_keyboard_group *wlr_group; + +- int nsyms; +- const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ +- uint32_t mods; /* invalid if nsyms == 0 */ ++ xkb_keycode_t keycode; ++ uint32_t mods; /* invalid if keycode == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; +@@ -291,7 +290,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keycode_t keycode); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1597,7 +1596,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keycode_t keycode) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1607,7 +1606,7 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && keycode == k->keycode && k->func) { + k->func(&k->arg); + return 1; + } +@@ -1618,17 +1617,12 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + void + keypress(struct wl_listener *listener, void *data) + { +- int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +- /* Get a list of keysyms based on the keymap for this keyboard */ +- const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); +@@ -1637,19 +1631,16 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { +- for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; +- } ++ if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ handled = keybinding(mods, keycode); + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; +- group->keysyms = syms; +- group->nsyms = nsyms; ++ group->keycode = keycode; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { +- group->nsyms = 0; ++ group->keycode = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + +@@ -1679,15 +1670,13 @@ int + keyrepeat(void *data) + { + KeyboardGroup *group = data; +- int i; +- if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) ++ if (!group->keycode || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + +- for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keycode); + + return 0; + } +diff --git a/keys.h b/keys.h +new file mode 100644 +index 00000000..047b76b0 +--- /dev/null ++++ b/keys.h +@@ -0,0 +1,514 @@ ++/* You can use the macros within this file ++ * instead of search the keycodes yourself ++ * with wev or something like that ++ * You probably are also searching these: ++ * Key_XF86AudioMute ++ * Key_XF86AudioLowerVolume ++ * Key_XF86AudioRaiseVolume ++ * Key_XF86MonBrightnessDown ++ * Key_XF86MonBrightnessUp ++*/ ++ ++#define Key_Escape 0x009 ++#define Key_1 0x00a ++#define Key_exclam 0x00a ++#define Key_2 0x00b ++#define Key_at 0x00b ++#define Key_3 0x00c ++#define Key_numbersign 0x00c ++#define Key_4 0x00d ++#define Key_dollar 0x00d ++#define Key_5 0x00e ++#define Key_percent 0x00e ++#define Key_6 0x00f ++#define Key_asciicircum 0x00f ++#define Key_7 0x010 ++#define Key_ampersand 0x010 ++#define Key_8 0x011 ++#define Key_asterisk 0x011 ++#define Key_9 0x012 ++#define Key_parenleft 0x012 ++#define Key_0 0x013 ++#define Key_parenright 0x013 ++#define Key_minus 0x014 ++#define Key_underscore 0x014 ++#define Key_equal 0x015 ++#define Key_plus 0x015 ++#define Key_BackSpace 0x016 ++#define Key_Tab 0x017 ++#define Key_ISO_Left_Tab 0x017 ++#define Key_q 0x018 ++#define Key_Q 0x018 ++#define Key_w 0x019 ++#define Key_W 0x019 ++#define Key_e 0x01a ++#define Key_E 0x01a ++#define Key_r 0x01b ++#define Key_R 0x01b ++#define Key_t 0x01c ++#define Key_T 0x01c ++#define Key_y 0x01d ++#define Key_Y 0x01d ++#define Key_u 0x01e ++#define Key_U 0x01e ++#define Key_i 0x01f ++#define Key_I 0x01f ++#define Key_o 0x020 ++#define Key_O 0x020 ++#define Key_p 0x021 ++#define Key_P 0x021 ++#define Key_bracketleft 0x022 ++#define Key_braceleft 0x022 ++#define Key_bracketright 0x023 ++#define Key_braceright 0x023 ++#define Key_Return 0x024 ++#define Key_Control_L 0x025 ++#define Key_a 0x026 ++#define Key_A 0x026 ++#define Key_s 0x027 ++#define Key_S 0x027 ++#define Key_d 0x028 ++#define Key_D 0x028 ++#define Key_f 0x029 ++#define Key_F 0x029 ++#define Key_g 0x02a ++#define Key_G 0x02a ++#define Key_h 0x02b ++#define Key_H 0x02b ++#define Key_j 0x02c ++#define Key_J 0x02c ++#define Key_k 0x02d ++#define Key_K 0x02d ++#define Key_l 0x02e ++#define Key_L 0x02e ++#define Key_semicolon 0x02f ++#define Key_colon 0x02f ++#define Key_apostrophe 0x030 ++#define Key_quotedbl 0x030 ++#define Key_grave 0x031 ++#define Key_asciitilde 0x031 ++#define Key_Shift_L 0x032 ++#define Key_backslash 0x033 ++#define Key_bar 0x033 ++#define Key_z 0x034 ++#define Key_Z 0x034 ++#define Key_x 0x035 ++#define Key_X 0x035 ++#define Key_c 0x036 ++#define Key_C 0x036 ++#define Key_v 0x037 ++#define Key_V 0x037 ++#define Key_b 0x038 ++#define Key_B 0x038 ++#define Key_n 0x039 ++#define Key_N 0x039 ++#define Key_m 0x03a ++#define Key_M 0x03a ++#define Key_comma 0x03b ++#define Key_less 0x03b ++#define Key_period 0x03c ++#define Key_greater 0x03c ++#define Key_slash 0x03d ++#define Key_question 0x03d ++#define Key_Shift_R 0x03e ++#define Key_KP_Multiply 0x03f ++#define Key_XF86ClearGrab 0x03f ++#define Key_Alt_L 0x040 ++#define Key_Meta_L 0x040 ++#define Key_space 0x041 ++#define Key_Caps_Lock 0x042 ++#define Key_F1 0x043 ++#define Key_XF86Switch_VT_1 0x043 ++#define Key_F2 0x044 ++#define Key_XF86Switch_VT_2 0x044 ++#define Key_F3 0x045 ++#define Key_XF86Switch_VT_3 0x045 ++#define Key_F4 0x046 ++#define Key_XF86Switch_VT_4 0x046 ++#define Key_F5 0x047 ++#define Key_XF86Switch_VT_5 0x047 ++#define Key_F6 0x048 ++#define Key_XF86Switch_VT_6 0x048 ++#define Key_F7 0x049 ++#define Key_XF86Switch_VT_7 0x049 ++#define Key_F8 0x04a ++#define Key_XF86Switch_VT_8 0x04a ++#define Key_F9 0x04b ++#define Key_XF86Switch_VT_9 0x04b ++#define Key_F10 0x04c ++#define Key_XF86Switch_VT_10 0x04c ++#define Key_Num_Lock 0x04d ++#define Key_Scroll_Lock 0x04e ++#define Key_KP_Home 0x04f ++#define Key_KP_7 0x04f ++#define Key_KP_Up 0x050 ++#define Key_KP_8 0x050 ++#define Key_KP_Prior 0x051 ++#define Key_KP_9 0x051 ++#define Key_KP_Subtract 0x052 ++#define Key_XF86Prev_VMode 0x052 ++#define Key_KP_Left 0x053 ++#define Key_KP_4 0x053 ++#define Key_KP_Begin 0x054 ++#define Key_KP_5 0x054 ++#define Key_KP_Right 0x055 ++#define Key_KP_6 0x055 ++#define Key_KP_Add 0x056 ++#define Key_XF86Next_VMode 0x056 ++#define Key_KP_End 0x057 ++#define Key_KP_1 0x057 ++#define Key_KP_Down 0x058 ++#define Key_KP_2 0x058 ++#define Key_KP_Next 0x059 ++#define Key_KP_3 0x059 ++#define Key_KP_Insert 0x05a ++#define Key_KP_0 0x05a ++#define Key_KP_Delete 0x05b ++#define Key_KP_Decimal 0x05b ++#define Key_ISO_Level3_Shift 0x05c ++#define Key_less2 0x05e ++#define Key_greater2 0x05e ++#define Key_bar2 0x05e ++#define Key_brokenbar 0x05e ++#define Key_F11 0x05f ++#define Key_XF86Switch_VT_11 0x05f ++#define Key_F12 0x060 ++#define Key_XF86Switch_VT_12 0x060 ++#define Key_Katakana 0x062 ++#define Key_Hiragana 0x063 ++#define Key_Henkan_Mode 0x064 ++#define Key_Hiragana_Katakana 0x065 ++#define Key_Muhenkan 0x066 ++#define Key_KP_Enter 0x068 ++#define Key_Control_R 0x069 ++#define Key_KP_Divide 0x06a ++#define Key_XF86Ungrab 0x06a ++#define Key_Print 0x06b ++#define Key_Sys_Req 0x06b ++#define Key_Alt_R 0x06c ++#define Key_Meta_R 0x06c ++#define Key_Linefeed 0x06d ++#define Key_Home 0x06e ++#define Key_Up 0x06f ++#define Key_Prior 0x070 ++#define Key_Left 0x071 ++#define Key_Right 0x072 ++#define Key_End 0x073 ++#define Key_Down 0x074 ++#define Key_Next 0x075 ++#define Key_Insert 0x076 ++#define Key_Delete 0x077 ++#define Key_XF86AudioMute 0x079 ++#define Key_XF86AudioLowerVolume 0x07a ++#define Key_XF86AudioRaiseVolume 0x07b ++#define Key_XF86PowerOff 0x07c ++#define Key_KP_Equal 0x07d ++#define Key_plusminus 0x07e ++#define Key_Pause 0x07f ++#define Key_Break 0x07f ++#define Key_XF86LaunchA 0x080 ++#define Key_KP_Decimal2 0x081 ++#define Key_Hangul 0x082 ++#define Key_Hangul_Hanja 0x083 ++#define Key_Super_L 0x085 ++#define Key_Super_R 0x086 ++#define Key_Menu 0x087 ++#define Key_Cancel 0x088 ++#define Key_Redo 0x089 ++#define Key_SunProps 0x08a ++#define Key_Undo 0x08b ++#define Key_SunFront 0x08c ++#define Key_XF86Copy 0x08d ++#define Key_XF86Open 0x08e ++#define Key_XF86Paste 0x08f ++#define Key_Find 0x090 ++#define Key_XF86Cut 0x091 ++#define Key_Help 0x092 ++#define Key_XF86MenuKB 0x093 ++#define Key_XF86Calculator 0x094 ++#define Key_XF86Sleep 0x096 ++#define Key_XF86WakeUp 0x097 ++#define Key_XF86Explorer 0x098 ++#define Key_XF86Send 0x099 ++#define Key_XF86Xfer 0x09b ++#define Key_XF86Launch1 0x09c ++#define Key_XF86Launch2 0x09d ++#define Key_XF86WWW 0x09e ++#define Key_XF86DOS 0x09f ++#define Key_XF86ScreenSaver 0x0a0 ++#define Key_XF86RotateWindows 0x0a1 ++#define Key_XF86TaskPane 0x0a2 ++#define Key_XF86Mail 0x0a3 ++#define Key_XF86Favorites 0x0a4 ++#define Key_XF86MyComputer 0x0a5 ++#define Key_XF86Back 0x0a6 ++#define Key_XF86Forward 0x0a7 ++#define Key_XF86Eject 0x0a9 ++#define Key_XF86Eject2 0x0aa ++#define Key_XF86AudioNext 0x0ab ++#define Key_XF86AudioPlay 0x0ac ++#define Key_XF86AudioPause 0x0ac ++#define Key_XF86AudioPrev 0x0ad ++#define Key_XF86AudioStop 0x0ae ++#define Key_XF86Eject3 0x0ae ++#define Key_XF86AudioRecord 0x0af ++#define Key_XF86AudioRewind 0x0b0 ++#define Key_XF86Phone 0x0b1 ++#define Key_XF86Tools 0x0b3 ++#define Key_XF86HomePage 0x0b4 ++#define Key_XF86Reload 0x0b5 ++#define Key_XF86Close 0x0b6 ++#define Key_XF86ScrollUp 0x0b9 ++#define Key_XF86ScrollDown 0x0ba ++#define Key_parenleft2 0x0bb ++#define Key_parenright2 0x0bc ++#define Key_XF86New 0x0bd ++#define Key_Redo2 0x0be ++#define Key_XF86Tools2 0x0bf ++#define Key_XF86Launch5 0x0c0 ++#define Key_XF86Launch6 0x0c1 ++#define Key_XF86Launch7 0x0c2 ++#define Key_XF86Launch8 0x0c3 ++#define Key_XF86Launch9 0x0c4 ++#define Key_XF86AudioMicMute 0x0c6 ++#define Key_XF86TouchpadToggle 0x0c7 ++#define Key_XF86TouchpadOn 0x0c8 ++#define Key_XF86TouchpadOff 0x0c9 ++#define Key_ISO_Level5_Shift 0x0cb ++#define Key_Alt_L2 0x0cc ++#define Key_Meta_L2 0x0cd ++#define Key_Super_L2 0x0ce ++#define Key_Hyper_L 0x0cf ++#define Key_XF86AudioPlay2 0x0d0 ++#define Key_XF86AudioPause2 0x0d1 ++#define Key_XF86Launch3 0x0d2 ++#define Key_XF86Launch4 0x0d3 ++#define Key_XF86LaunchB 0x0d4 ++#define Key_XF86Suspend 0x0d5 ++#define Key_XF86Close2 0x0d6 ++#define Key_XF86AudioPlay3 0x0d7 ++#define Key_XF86AudioForward 0x0d8 ++#define Key_Print2 0x0da ++#define Key_XF86WebCam 0x0dc ++#define Key_XF86AudioPreset 0x0dd ++#define Key_XF86Mail2 0x0df ++#define Key_XF86Messenger 0x0e0 ++#define Key_XF86Search 0x0e1 ++#define Key_XF86Go 0x0e2 ++#define Key_XF86Finance 0x0e3 ++#define Key_XF86Game 0x0e4 ++#define Key_XF86Shop 0x0e5 ++#define Key_Cancel2 0x0e7 ++#define Key_XF86MonBrightnessDown 0x0e8 ++#define Key_XF86MonBrightnessUp 0x0e9 ++#define Key_XF86AudioMedia 0x0ea ++#define Key_XF86Display 0x0eb ++#define Key_XF86KbdLightOnOff 0x0ec ++#define Key_XF86KbdBrightnessDown 0x0ed ++#define Key_XF86KbdBrightnessUp 0x0ee ++#define Key_XF86Send2 0x0ef ++#define Key_XF86Reply 0x0f0 ++#define Key_XF86MailForward 0x0f1 ++#define Key_XF86Save 0x0f2 ++#define Key_XF86Documents 0x0f3 ++#define Key_XF86Battery 0x0f4 ++#define Key_XF86Bluetooth 0x0f5 ++#define Key_XF86WLAN 0x0f6 ++#define Key_XF86UWB 0x0f7 ++#define Key_XF86Next_VMode2 0x0f9 ++#define Key_XF86Prev_VMode2 0x0fa ++#define Key_XF86MonBrightnessCycle 0x0fb ++#define Key_XF86BrightnessAuto 0x0fc ++#define Key_XF86DisplayOff 0x0fd ++#define Key_XF86WWAN 0x0fe ++#define Key_XF86RFKill 0x0ff ++#define Key_XF86AudioMicMute2 0x100 ++#define Key_XF86Info 0x16e ++#define Key_XF86Favorites2 0x174 ++#define Key_XF86CycleAngle 0x17b ++#define Key_XF86FullScreen 0x17c ++#define Key_XF86Keyboard 0x17e ++#define Key_XF86AspectRatio 0x17f ++#define Key_XF86DVD 0x18d ++#define Key_XF86Audio 0x190 ++#define Key_XF86Video 0x191 ++#define Key_XF86Calendar 0x195 ++#define Key_XF86ChannelUp 0x19a ++#define Key_XF86ChannelDown 0x19b ++#define Key_XF86AudioRandomPlay 0x1a2 ++#define Key_XF86Break 0x1a3 ++#define Key_XF86VideoPhone 0x1a8 ++#define Key_XF86Game2 0x1a9 ++#define Key_XF86ZoomIn 0x1aa ++#define Key_XF86ZoomOut 0x1ab ++#define Key_XF86ZoomReset 0x1ac ++#define Key_XF86Word 0x1ad ++#define Key_XF86Editor 0x1ae ++#define Key_XF86Excel 0x1af ++#define Key_XF86GraphicsEditor 0x1b0 ++#define Key_XF86Presentation 0x1b1 ++#define Key_XF86Database 0x1b2 ++#define Key_XF86News 0x1b3 ++#define Key_XF86Voicemail 0x1b4 ++#define Key_XF86Addressbook 0x1b5 ++#define Key_XF86Messenger2 0x1b6 ++#define Key_XF86DisplayToggle 0x1b7 ++#define Key_XF86SpellCheck 0x1b8 ++#define Key_XF86LogOff 0x1b9 ++#define Key_dollar2 0x1ba ++#define Key_EuroSign 0x1bb ++#define Key_XF86FrameBack 0x1bc ++#define Key_XF86FrameForward 0x1bd ++#define Key_XF86ContextMenu 0x1be ++#define Key_XF86MediaRepeat 0x1bf ++#define Key_XF8610ChannelsUp 0x1c0 ++#define Key_XF8610ChannelsDown 0x1c1 ++#define Key_XF86Images 0x1c2 ++#define Key_XF86NotificationCenter 0x1c4 ++#define Key_XF86PickupPhone 0x1c5 ++#define Key_XF86HangupPhone 0x1c6 ++#define Key_XF86Fn 0x1d8 ++#define Key_XF86Fn_Esc 0x1d9 ++#define Key_XF86FnRightShift 0x1ed ++#define Key_braille_dot_1 0x1f9 ++#define Key_braille_dot_2 0x1fa ++#define Key_braille_dot_3 0x1fb ++#define Key_braille_dot_4 0x1fc ++#define Key_braille_dot_5 0x1fd ++#define Key_braille_dot_6 0x1fe ++#define Key_braille_dot_7 0x1ff ++#define Key_braille_dot_8 0x200 ++#define Key_braille_dot_9 0x201 ++#define Key_braille_dot_1_2 0x202 ++#define Key_XF86Numeric0 0x208 ++#define Key_XF86Numeric1 0x209 ++#define Key_XF86Numeric2 0x20a ++#define Key_XF86Numeric3 0x20b ++#define Key_XF86Numeric4 0x20c ++#define Key_XF86Numeric5 0x20d ++#define Key_XF86Numeric6 0x20e ++#define Key_XF86Numeric7 0x20f ++#define Key_XF86Numeric8 0x210 ++#define Key_XF86Numeric9 0x211 ++#define Key_XF86NumericStar 0x212 ++#define Key_XF86NumericPound 0x213 ++#define Key_XF86NumericA 0x214 ++#define Key_XF86NumericB 0x215 ++#define Key_XF86NumericC 0x216 ++#define Key_XF86NumericD 0x217 ++#define Key_XF86CameraFocus 0x218 ++#define Key_XF86WPSButton 0x219 ++#define Key_XF86TouchpadToggle2 0x21a ++#define Key_XF86TouchpadOn2 0x21b ++#define Key_XF86TouchpadOff2 0x21c ++#define Key_XF86CameraZoomIn 0x21d ++#define Key_XF86CameraZoomOut 0x21e ++#define Key_XF86CameraUp 0x21f ++#define Key_XF86CameraDown 0x220 ++#define Key_XF86CameraLeft 0x221 ++#define Key_XF86CameraRight 0x222 ++#define Key_XF86AttendantOn 0x223 ++#define Key_XF86AttendantOff 0x224 ++#define Key_XF86AttendantToggle 0x225 ++#define Key_XF86LightsToggle 0x226 ++#define Key_XF86ALSToggle 0x238 ++#define Key_XF86RotationLockToggle 0x239 ++#define Key_XF86Buttonconfig 0x248 ++#define Key_XF86Taskmanager 0x249 ++#define Key_XF86Journal 0x24a ++#define Key_XF86ControlPanel 0x24b ++#define Key_XF86AppSelect 0x24c ++#define Key_XF86Screensaver 0x24d ++#define Key_XF86VoiceCommand 0x24e ++#define Key_XF86Assistant 0x24f ++#define Key_ISO_Next_Group 0x250 ++#define Key_XF86EmojiPicker 0x251 ++#define Key_XF86Dictate 0x252 ++#define Key_XF86CameraAccessEnable 0x253 ++#define Key_XF86CameraAccessDisable 0x254 ++#define Key_XF86CameraAccessToggle 0x255 ++#define Key_XF86BrightnessMin 0x258 ++#define Key_XF86BrightnessMax 0x259 ++#define Key_XF86KbdInputAssistPrev 0x268 ++#define Key_XF86KbdInputAssistNext 0x269 ++#define Key_XF86KbdInputAssistPrevgroup 0x26a ++#define Key_XF86KbdInputAssistNextgroup 0x26b ++#define Key_XF86KbdInputAssistAccept 0x26c ++#define Key_XF86KbdInputAssistCancel 0x26d ++#define Key_XF86RightUp 0x26e ++#define Key_XF86RightDown 0x26f ++#define Key_XF86LeftUp 0x270 ++#define Key_XF86LeftDown 0x271 ++#define Key_XF86RootMenu 0x272 ++#define Key_XF86MediaTopMenu 0x273 ++#define Key_XF86Numeric11 0x274 ++#define Key_XF86Numeric12 0x275 ++#define Key_XF86AudioDesc 0x276 ++#define Key_XF863DMode 0x277 ++#define Key_XF86NextFavorite 0x278 ++#define Key_XF86StopRecord 0x279 ++#define Key_XF86PauseRecord 0x27a ++#define Key_XF86VOD 0x27b ++#define Key_XF86Unmute 0x27c ++#define Key_XF86FastReverse 0x27d ++#define Key_XF86SlowReverse 0x27e ++#define Key_XF86Data 0x27f ++#define Key_XF86OnScreenKeyboard 0x280 ++#define Key_XF86PrivacyScreenToggle 0x281 ++#define Key_XF86SelectiveScreenshot 0x282 ++#define Key_XF86NextElement 0x283 ++#define Key_XF86PreviousElement 0x284 ++#define Key_XF86AutopilotEngageToggle 0x285 ++#define Key_XF86MarkWaypoint 0x286 ++#define Key_XF86Sos 0x287 ++#define Key_XF86NavChart 0x288 ++#define Key_XF86FishingChart 0x289 ++#define Key_XF86SingleRangeRadar 0x28a ++#define Key_XF86DualRangeRadar 0x28b ++#define Key_XF86RadarOverlay 0x28c ++#define Key_XF86TraditionalSonar 0x28d ++#define Key_XF86ClearvuSonar 0x28e ++#define Key_XF86SidevuSonar 0x28f ++#define Key_XF86NavInfo 0x290 ++#define Key_XF86BrightnessAdjust 0x291 ++#define Key_XF86Macro1 0x298 ++#define Key_XF86Macro2 0x299 ++#define Key_XF86Macro3 0x29a ++#define Key_XF86Macro4 0x29b ++#define Key_XF86Macro5 0x29c ++#define Key_XF86Macro6 0x29d ++#define Key_XF86Macro7 0x29e ++#define Key_XF86Macro8 0x29f ++#define Key_XF86Macro9 0x2a0 ++#define Key_XF86Macro10 0x2a1 ++#define Key_XF86Macro11 0x2a2 ++#define Key_XF86Macro12 0x2a3 ++#define Key_XF86Macro13 0x2a4 ++#define Key_XF86Macro14 0x2a5 ++#define Key_XF86Macro15 0x2a6 ++#define Key_XF86Macro16 0x2a7 ++#define Key_XF86Macro17 0x2a8 ++#define Key_XF86Macro18 0x2a9 ++#define Key_XF86Macro19 0x2aa ++#define Key_XF86Macro20 0x2ab ++#define Key_XF86Macro21 0x2ac ++#define Key_XF86Macro22 0x2ad ++#define Key_XF86Macro23 0x2ae ++#define Key_XF86Macro24 0x2af ++#define Key_XF86Macro25 0x2b0 ++#define Key_XF86Macro26 0x2b1 ++#define Key_XF86Macro27 0x2b2 ++#define Key_XF86Macro28 0x2b3 ++#define Key_XF86Macro29 0x2b4 ++#define Key_XF86Macro30 0x2b5 ++#define Key_XF86MacroRecordStart 0x2b8 ++#define Key_XF86MacroRecordStop 0x2b9 ++#define Key_XF86MacroPresetCycle 0x2ba ++#define Key_XF86MacroPreset1 0x2bb ++#define Key_XF86MacroPreset2 0x2bc ++#define Key_XF86MacroPreset3 0x2bd ++#define Key_XF86KbdLcdMenu1 0x2c0 ++#define Key_XF86KbdLcdMenu2 0x2c1 ++#define Key_XF86KbdLcdMenu3 0x2c2 ++#define Key_XF86KbdLcdMenu4 0x2c3 ++#define Key_XF86KbdLcdMenu5 0x2c4 +-- +2.48.0 + + +From 16c0b9be41ba111bf551fd2e5e2bdaa537c6990d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 19 Mar 2023 15:50:07 -0600 +Subject: [PATCH 2/2] add program to generate keys.h +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + generate-keys.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) + create mode 100644 generate-keys.c + +diff --git a/generate-keys.c b/generate-keys.c +new file mode 100644 +index 00000000..37655611 +--- /dev/null ++++ b/generate-keys.c +@@ -0,0 +1,95 @@ ++/****************************************************************** ++ * Copyright 2023-2024 Leonardo Hernández Hernández ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the “Software”), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ++ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ ******************************************************************/ ++ ++/* cc -lxkbcommon -o generate-keys generate-keys.c */ ++ ++#define _DEFAULT_SOURCE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int ++main(void) ++{ ++ /* Allow generate keys with a different layout and variant. ++ * You can also use XKB_DEFAULT_* environmental variables and let this as is */ ++ struct xkb_rule_names rules = { ++ 0 ++ }; ++ struct xkb_context *context = NULL; ++ struct xkb_keymap *keymap = NULL; ++ xkb_keycode_t keycode, min_keycode, max_keycode; ++ xkb_layout_index_t layout, num_layouts; ++ xkb_level_index_t level, num_levels; ++ int i, nsyms; ++ const xkb_keysym_t *syms; ++ char keyname[64]; ++ bool ok = false; ++ FILE *file = fopen("keys.h", "w"); ++ if (!file) { ++ perror("Couldn't open keys.h"); ++ return EXIT_FAILURE; ++ } ++ ++ if (!(context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon context\n", stderr); ++ goto out; ++ } ++ ++ if (!(keymap = xkb_keymap_new_from_names(context, &rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon keymap\n", stderr); ++ goto out; ++ } ++ ++ min_keycode = xkb_keymap_min_keycode(keymap); ++ max_keycode = xkb_keymap_max_keycode(keymap); ++ ++ for (keycode = min_keycode; keycode <= max_keycode; keycode++) { ++ num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode); ++ for (layout = 0; layout < num_layouts; layout++) { ++ num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout); ++ for (level = 0; level < num_levels; level++) { ++ nsyms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms); ++ for (i = 0; i < nsyms; i++) { ++ xkb_keysym_get_name(syms[i], keyname, sizeof(keyname) / sizeof(keyname[0])); ++ fprintf(file, "#define Key_%-27s %#05"PRIx32"\n", keyname, keycode); ++ } ++ } ++ } ++ } ++ ++ ok = true; ++ sync(); ++ ++out: ++ fclose(file); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ return !ok; ++} +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/launchtag/README.md b/dwl-bak/dwl-patches/patches/launchtag/README.md new file mode 100644 index 0000000..86ad447 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/launchtag/README.md @@ -0,0 +1,12 @@ +### Description +Straight port of the dwm taglayouts patch, renamed because I have a hard time +finding it on the suckless site. + +This patch allows you to set the default layout for each tag. + +This is intended to be applied on top of pertag. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/launchtag/launchtag.patch) +### Authors - latest at top +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl-bak/dwl-patches/patches/launchtag/launchtag.patch b/dwl-bak/dwl-patches/patches/launchtag/launchtag.patch new file mode 100644 index 0000000..c298fd6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/launchtag/launchtag.patch @@ -0,0 +1,49 @@ +From e94f4b8363f4122f9b00f774a5b541a00c49fd35 Mon Sep 17 00:00:00 2001 +From: MayOrMayNotBeACat +Date: Fri, 6 Jun 2025 22:42:16 -0400 +Subject: [PATCH] Allow initial layout to be set per tag. + +--- + config.def.h | 1 + + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8f2fade 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -16,6 +16,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) ++static int tag_layout[TAGCOUNT] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; /* Modify as needed to match TAGCOUNT */ + + /* logging */ + static int log_level = WLR_ERROR; +diff --git a/dwl.c b/dwl.c +index d234cd2..da46816 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1045,11 +1045,18 @@ createmon(struct wl_listener *listener, void *data) + m->pertag->nmasters[i] = m->nmaster; + m->pertag->mfacts[i] = m->mfact; + +- m->pertag->ltidxs[i][0] = m->lt[0]; ++ if (i != 0) ++ m->pertag->ltidxs[i][0] = &layouts[tag_layout[i-1]]; ++ else ++ m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + } + ++ m->lt[0] = m->pertag->ltidxs[1][0]; ++ m->lt[1] = m->pertag->ltidxs[1][1]; ++ strncpy(m->ltsymbol, m->pertag->ltidxs[1][0]->symbol, LENGTH(m->ltsymbol)); ++ + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/limitnmaster/README.md b/dwl-bak/dwl-patches/patches/limitnmaster/README.md new file mode 100644 index 0000000..e750300 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/limitnmaster/README.md @@ -0,0 +1,8 @@ +### Description +Limits nmaster to within the range of currently-opened windows (nmaster will not change past the full horizontal split layout) + +### Download +- [2024-03-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/limitnmaster/limitnmaster.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl-bak/dwl-patches/patches/limitnmaster/limitnmaster.patch b/dwl-bak/dwl-patches/patches/limitnmaster/limitnmaster.patch new file mode 100644 index 0000000..f4339c8 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/limitnmaster/limitnmaster.patch @@ -0,0 +1,33 @@ +From 20c948398af900564a59007fc08d15eaa0b65da3 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Fri, 15 Mar 2024 17:33:27 -0500 +Subject: [PATCH] Added limitnmaster + +--- + dwl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..210c41d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1391,9 +1391,15 @@ handlesig(int signo) + void + incnmaster(const Arg *arg) + { ++ unsigned int n = 0; ++ Client *c; ++ + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) ++ n++; ++ selmon->nmaster = MIN(MAX(selmon->nmaster + arg->i, 0), n); + arrange(selmon); + } + +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/lockedkeys/README.md b/dwl-bak/dwl-patches/patches/lockedkeys/README.md new file mode 100644 index 0000000..50f0fd2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/lockedkeys/README.md @@ -0,0 +1,23 @@ +### Description +This patch allows you to add keybindings to the lockscreen. + +```c +static const Key lockedkeys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/lockedkeys) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/fc4146f3068dcd46035a2a11fe9d6109a97ae6d6/lockedkeys/lockedkeys.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/2a6560c167e5c9afc5598ac5431d23d90de8846c/lockedkeys/lockedkeys.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/lockedkeys/lockedkeys.patch b/dwl-bak/dwl-patches/patches/lockedkeys/lockedkeys.patch new file mode 100644 index 0000000..f683536 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/lockedkeys/lockedkeys.patch @@ -0,0 +1,79 @@ +From 70dc03a3817b8fd933244c2db1bb849d9626b12b Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 11 Apr 2024 13:16:40 -0500 +Subject: [PATCH] allow to add keybindings in lockscreen + +--- + config.def.h | 11 +++++++++++ + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8847e58..0d4a4f8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -164,6 +164,17 @@ static const Key keys[] = { + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; + ++static const Key lockedkeys[] = { ++ /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ ++ /* modifier key function argument */ ++ ++ /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } ++ CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), ++ CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++}; ++ + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +diff --git a/dwl.c b/dwl.c +index bf763df..db4bb2b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -287,6 +287,7 @@ static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int lockedkeybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1446,6 +1447,21 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + return 0; + } + ++int ++lockedkeybinding(uint32_t mods, xkb_keysym_t sym) ++{ ++ int handled = 0; ++ const Key *k; ++ for (k = lockedkeys; k < END(lockedkeys); k++) { ++ if (CLEANMASK(mods) == CLEANMASK(k->mod) && ++ sym == k->keysym && k->func) { ++ k->func(&k->arg); ++ handled = 1; ++ } ++ } ++ return handled; ++} ++ + void + keypress(struct wl_listener *listener, void *data) + { +@@ -1473,6 +1489,10 @@ keypress(struct wl_listener *listener, void *data) + handled = keybinding(mods, syms[i]) || handled; + } + ++ if (locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ for (i = 0; i < nsyms; i++) ++ handled = lockedkeybinding(mods, syms[i]) || handled; ++ + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; +-- +2.43.2 diff --git a/dwl-bak/dwl-patches/patches/mastercolumn/README.md b/dwl-bak/dwl-patches/patches/mastercolumn/README.md new file mode 100644 index 0000000..14db13c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/mastercolumn/README.md @@ -0,0 +1,16 @@ +### Description +This patch adds a layout, `mastercol`, in which the windows in the master area are arranged in columns of equal size. The number of columns is always nmaster + 1, and the last column is a stack of leftover windows (as in the normal tile layout). It effectively differs from the default tile layout only in that master windows are arranged horizontally rather than vertically. + +For gaps, apply `mastercolumn-gaps.patch` on top of `mastercolumn.patch` and `gaps.patch`. + +### Download +##### `mastercolumn.patch` +- [git branch](/shivers/dwl/src/branch/mastercolumn) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mastercolumn/mastercolumn.patch) + +##### `mastercolumn-gaps.patch` +- [git branch](/shivers/dwl/src/branch/mastercolumn-gaps) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mastercolumn/mastercolumn-gaps.patch) + +### Authors +- [shivers](https://codeberg.org/shivers) diff --git a/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch b/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch new file mode 100644 index 0000000..943c4bd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch @@ -0,0 +1,63 @@ +From b6f2ee09778cdea8a1450d16bcf24a8a75e10b40 Mon Sep 17 00:00:00 2001 +From: moe +Date: Tue, 16 Jul 2024 13:56:24 -0400 +Subject: [PATCH 1/1] add mastercolumn gaps + +--- + dwl.c | 23 +++++++++++++++-------- + 1 file changed, 15 insertions(+), 8 deletions(-) + +diff --git a/dwl.c b/dwl.c +index b121094..be33c01 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1755,7 +1755,7 @@ unset_fullscreen: + void + mastercol(Monitor *m) + { +- unsigned int mw, mx, ty; ++ unsigned int h, w, r, e = m->gaps, mw, mx, ty; + int i, n = 0; + Client *c; + +@@ -1764,23 +1764,30 @@ mastercol(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; + + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; +- i = mx = ty = 0; ++ i = 0; ++ mx = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y, +- .width = (mw - mx) / (MIN(n, m->nmaster) - i), .height = m->w.height}, 0); +- mx += c->geom.width; ++ r = MIN(n, m->nmaster) - i; ++ w = (mw - mx - gappx*e - gappx*e * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + gappx*e, ++ .width = w, .height = m->w.height - 2*gappx*e}, 0); ++ mx += c->geom.width + gappx*e; + } else { ++ r = n - i; ++ h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ ty += c->geom.height + gappx*e; + } + i++; + } +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn.patch b/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn.patch new file mode 100644 index 0000000..425e414 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/mastercolumn/mastercolumn.patch @@ -0,0 +1,87 @@ +From eb0c6ff53ba823f26d13f18627a084959c353627 Mon Sep 17 00:00:00 2001 +From: moe +Date: Sat, 10 Aug 2024 15:58:15 -0400 +Subject: [PATCH] add mastercolumn layout + +--- + config.def.h | 2 ++ + dwl.c | 36 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..68b27a7 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "||=", mastercol }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..49f65ba 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -301,6 +301,7 @@ static int keyrepeat(void *data); + static void killclient(const Arg *arg); + static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); ++static void mastercol(Monitor *m); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); +@@ -1748,6 +1749,41 @@ unset_fullscreen: + } + } + ++void ++mastercol(Monitor *m) ++{ ++ unsigned int mw, mx, ty; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ else ++ mw = m->w.width; ++ i = mx = ty = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) { ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y, ++ .width = (mw - mx) / (MIN(n, m->nmaster) - i), .height = m->w.height}, 0); ++ mx += c->geom.width; ++ } else { ++ resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ ty += c->geom.height; ++ } ++ i++; ++ } ++} ++ ++ + void + maximizenotify(struct wl_listener *listener, void *data) + { +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/menu/README.md b/dwl-bak/dwl-patches/patches/menu/README.md new file mode 100644 index 0000000..ad37016 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/menu/README.md @@ -0,0 +1,18 @@ +### Description + +This patch adds `menu` command, which allows dwl to interface with dmenu-like +programs. + +By default, two menus are available: +- focusing a window by its title/appid by pressing `Alt+o` +- selecting a layout from a list by pressing `Alt+Shift+o` + +Edit `menus` array in `config.h` to add/change menus and use a different dmenu +program (`wmenu` is the default). + +### Download +- [2025-03-21 v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/menu/menu.patch) +- [2024-07-13 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/menu/menu.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/menu/menu.patch b/dwl-bak/dwl-patches/patches/menu/menu.patch new file mode 100644 index 0000000..c1dea4d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/menu/menu.patch @@ -0,0 +1,227 @@ +From da9861cf0448ca94011470634fd61c3ef2129a25 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 21 Mar 2025 21:48:42 +0100 +Subject: [PATCH] Add menu command + +--- + config.def.h | 8 +++ + dwl.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 164 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..a5914ca 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,12 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++static const Menu menus[] = { ++ /* command feed function action function */ ++ { "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction }, ++ { "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction }, ++}; ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +@@ -140,6 +146,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +diff --git a/dwl.c b/dwl.c +index def2562..b0e8310 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -242,6 +242,12 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *cmd; /* command to run a menu */ ++ void (*feed)(FILE *f); /* feed input to menu */ ++ void (*action)(char *line); /* do action based on menu output */ ++} Menu; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -302,6 +308,12 @@ static void killclient(const Arg *arg); + static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); ++static void menu(const Arg *arg); ++static int menuread(int fd, uint32_t mask, void *data); ++static void menuwinfeed(FILE *f); ++static void menuwinaction(char *line); ++static void menulayoutfeed(FILE *f); ++static void menulayoutaction(char *line); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, +@@ -413,6 +425,11 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const Menu *menu_current; ++static int menu_fd; ++static pid_t menu_pid; ++static struct wl_event_source *menu_source; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1768,6 +1785,145 @@ maximizenotify(struct wl_listener *listener, void *data) + wlr_xdg_surface_schedule_configure(c->surface.xdg); + } + ++void ++menu(const Arg *arg) ++{ ++ FILE *f; ++ int fd_right[2], fd_left[2]; ++ ++ if (menu_current != NULL) { ++ wl_event_source_remove(menu_source); ++ close(menu_fd); ++ kill(menu_pid, SIGTERM); ++ menu_current = NULL; ++ if (!arg->v) ++ return; ++ } ++ ++ if (pipe(fd_right) == -1 || pipe(fd_left) == -1) ++ return; ++ if ((menu_pid = fork()) == -1) ++ return; ++ if (menu_pid == 0) { ++ close(fd_right[1]); ++ close(fd_left[0]); ++ dup2(fd_right[0], STDIN_FILENO); ++ close(fd_right[0]); ++ dup2(fd_left[1], STDOUT_FILENO); ++ close(fd_left[1]); ++ execl("/bin/sh", "/bin/sh", "-c", ((Menu *)(arg->v))->cmd, NULL); ++ die("dwl: execl %s failed:", "/bin/sh"); ++ } ++ ++ close(fd_right[0]); ++ close(fd_left[1]); ++ menu_fd = fd_left[0]; ++ if (fd_set_nonblock(menu_fd) == -1) ++ return; ++ if (!(f = fdopen(fd_right[1], "w"))) ++ return; ++ menu_current = arg->v; ++ menu_current->feed(f); ++ fclose(f); ++ menu_source = wl_event_loop_add_fd(event_loop, ++ menu_fd, WL_EVENT_READABLE, menuread, NULL); ++} ++ ++int ++menuread(int fd, uint32_t mask, void *data) ++{ ++ char *s; ++ int n; ++ static char line[512]; ++ static int i = 0; ++ ++ if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { ++ i = 0; ++ menu(&(const Arg){ .v = NULL }); ++ return 0; ++ } ++ if ((n = read(menu_fd, line + i, LENGTH(line) - 1 - i)) == -1) { ++ if (errno != EAGAIN) { ++ i = 0; ++ menu(&(const Arg){ .v = NULL }); ++ } ++ return 0; ++ } ++ line[i + n] = '\0'; ++ if (!(s = strchr(line + i, '\n'))) { ++ i += n; ++ return 0; ++ } ++ i = 0; ++ *s = '\0'; ++ menu_current->action(line); ++ return 0; ++} ++ ++void ++menuwinfeed(FILE *f) ++{ ++ Client *c; ++ const char *title, *appid; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!(title = client_get_title(c))) ++ continue; ++ fprintf(f, "%s", title); ++ if ((appid = client_get_appid(c))) ++ fprintf(f, " | %s", appid); ++ fputc('\n', f); ++ } ++} ++ ++void ++menuwinaction(char *line) ++{ ++ Client *c; ++ const char *appid, *title; ++ static char buf[512]; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!(title = client_get_title(c))) ++ continue; ++ appid = client_get_appid(c); ++ snprintf(buf, LENGTH(buf) - 1, "%s%s%s", ++ title, appid ? " | " : "", appid ? appid : ""); ++ if (strcmp(line, buf) == 0) ++ goto found; ++ } ++ return; ++ ++found: ++ if (!c->mon) ++ return; ++ wl_list_remove(&c->flink); ++ wl_list_insert(&fstack, &c->flink); ++ selmon = c->mon; ++ view(&(const Arg){ .ui = c->tags }); ++} ++ ++void ++menulayoutfeed(FILE *f) ++{ ++ const Layout *l; ++ for (l = layouts; l < END(layouts); l++) ++ fprintf(f, "%s\n", l->symbol); ++} ++ ++void ++menulayoutaction(char *line) ++{ ++ const Layout *l; ++ for (l = layouts; l < END(layouts); l++) ++ if (strcmp(line, l->symbol) == 0) ++ goto found; ++ return; ++ ++found: ++ setlayout(&(const Arg){ .v = l }); ++} ++ + void + monocle(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/menurule/README.md b/dwl-bak/dwl-patches/patches/menurule/README.md new file mode 100644 index 0000000..8d8b757 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/menurule/README.md @@ -0,0 +1,70 @@ +### Description + +This patch adds a dmenu interface to [setrule][setrule], which allows to add or +change client rules at runtime. It must be applied on top of [setrule][setrule] +and [menu][menu] patches. + +You can invoke the menu by pressing `Alt+R`. The menu lists all the rules, plus +a shortcut to define a new one that would apply to the currently focused client +(marked with `(NEW)`). Rules that already apply to the focused client are marked +with `<`. + +![menurule in action](menurule.png) + +To edit a rule, you need to select it, press `Tab`, change what you need and +finally press `Enter`. You can remove a rule by prepending it with `-`. To add a +new rule, just put new values into `[appid|title]`. + +To add support for new rules, you need to edit `fprintf` and `sscanf` calls in +`menurulefeed` and `menuruleaction` functions respectively. + +For example, this is what I did to add support for [swallow][swallow] patch +rules. + +```diff +diff --git a/dwl.c b/dwl.c +index 34397fc..f1b31ea 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2441,10 +2441,14 @@ menurulefeed(FILE *f) + fprintf(f, "%-*s " + " tags:%-4"PRIi32 + " isfloating:%-2d" ++ " isterm:%-2d" ++ " noswallow:%-2d" + " monitor:%-2d" + "%s\n", wid, buf, + r->tags, + r->isfloating, ++ r->isterm, ++ r->noswallow, + r->monitor, + (r == &t) ? " (NEW)" : match ? " <" : ""); + } +@@ -2465,10 +2469,14 @@ menuruleaction(char *line) + sscanf(line, "[%255[^|]|%255[^]]]" + " tags:%"SCNu32 + " isfloating:%d" ++ " isterm:%d" ++ " noswallow:%d" + " monitor:%d" + "%n", appid, title, + &r.tags, + &r.isfloating, ++ &r.isterm, ++ &r.noswallow, + &r.monitor, + &end); +``` + +[setrule]: /dwl/dwl-patches/src/branch/main/patches/setrule +[menu]: /dwl/dwl-patches/src/branch/main/patches/menu +[swallow]: /dwl/dwl-patches/src/branch/main/patches/swallow + +### Download + +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/menurule/menurule.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/menurule/menurule.patch b/dwl-bak/dwl-patches/patches/menurule/menurule.patch new file mode 100644 index 0000000..b74a600 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/menurule/menurule.patch @@ -0,0 +1,167 @@ +From 7b578d9f4647d84f79a2e8a46a1c65cbacf8d90b Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Wed, 19 Mar 2025 02:28:46 +0100 +Subject: [PATCH] Add menurule to tweak rules at runtime + +--- + config.def.h | 2 + + dwl.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 118 insertions(+) + +diff --git a/config.def.h b/config.def.h +index e03a754..77b10ff 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -24,6 +24,7 @@ static const Menu menus[] = { + /* command feed function action function */ + { "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction }, + { "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction }, ++ { "wmenu -i -l 10 -p Rules", menurulefeed, menuruleaction }, + }; + + /* Max amount of dynamically added rules */ +@@ -151,6 +152,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} }, ++ { MODKEY, XKB_KEY_r, menu, {.v = &menus[2]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{0} }, +diff --git a/dwl.c b/dwl.c +index be007d8..df4901f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -316,6 +316,8 @@ static void menuwinfeed(FILE *f); + static void menuwinaction(char *line); + static void menulayoutfeed(FILE *f); + static void menulayoutaction(char *line); ++static void menurulefeed(FILE *f); ++static void menuruleaction(char *line); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, +@@ -1974,6 +1976,120 @@ found: + setlayout(&(const Arg){ .v = l }); + } + ++void ++menurulefeed(FILE *f) ++{ ++ Rule t, *p, *r; ++ const char *appid, *title; ++ static char buf[515]; ++ Client *c = focustop(selmon); ++ int n, wid = 0, match; ++ ++ t = (Rule){ 0 }; ++ t.monitor = -1; ++ if (c) { ++ t.id = client_get_appid(c); ++ t.title = client_get_title(c); ++ appid = t.id ? t.id : broken; ++ title = t.title ? t.title : broken; ++ } ++ ++ for (p = drules; p <= drules + druleslen; p++) { ++ r = (p == drules + druleslen) ? &t : p; ++ n = 0; ++ n += strlen(r->id ? r->id : "NULL"); ++ n += strlen(r->title ? r->title : "NULL"); ++ n += 3; ++ wid = MAX(wid, n); ++ } ++ wid = MIN(wid, 40); ++ ++ for (p = drules; p <= drules + druleslen; p++) { ++ match = 0; ++ /* Check if rule applies to the focused client */ ++ if (c && p < drules + druleslen) { ++ match = (!p->title || strstr(title, p->title)) ++ && (!p->id || strstr(appid, p->id)); ++ if (match && p->id) ++ t.id = NULL; ++ if (match && p->title) ++ t.title = NULL; ++ } ++ r = (p == drules + druleslen) ? &t : p; ++ if (r == &t && t.id) ++ t.title = NULL; ++ /* Do not suggest to add a new empty rule */ ++ if (r == &t && !(t.id || t.title)) ++ continue; ++ if (r->id && r->title && ++ strcmp(r->id, "removedrule") == 0 && strcmp(r->title, "removedrule") == 0) ++ continue; ++ snprintf(buf, LENGTH(buf) - 1, "[%s|%s]", ++ r->id ? r->id : "NULL", r->title ? r->title : "NULL"); ++ fprintf(f, "%-*s " ++ " tags:%-4"PRIi32 ++ " isfloating:%-2d" ++ " monitor:%-2d" ++ "%s\n", wid, buf, ++ r->tags, ++ r->isfloating, ++ r->monitor, ++ (r == &t) ? " (NEW)" : match ? " <" : ""); ++ } ++} ++ ++void ++menuruleaction(char *line) ++{ ++ Rule r, *f; ++ static char appid[256], title[256]; ++ int del = 0, end; ++ ++ if (line[0] == '-') { ++ del = 1; ++ line++; ++ } ++ end = 0; ++ sscanf(line, "[%255[^|]|%255[^]]]" ++ " tags:%"SCNu32 ++ " isfloating:%d" ++ " monitor:%d" ++ "%n", appid, title, ++ &r.tags, ++ &r.isfloating, ++ &r.monitor, ++ &end); ++ ++ /* Full line was not parsed, exit */ ++ if (!end) ++ return; ++ ++ r.id = (strcmp(appid, "NULL") != 0) ? appid : NULL; ++ r.title = (strcmp(title, "NULL") != 0) ? title : NULL; ++ ++ /* Find which rule we are trying to edit */ ++ for (f = drules; f < drules + druleslen; f++) ++ if (((!r.title && !f->title) || (r.title && f->title && !strcmp(r.title, f->title))) ++ && (((!r.id && !f->id) || (r.id && f->id && !strcmp(r.id, f->id))))) ++ goto found; ++ ++ if (druleslen >= LENGTH(rules) + RULES_MAX) ++ return; /* No free slots left */ ++ ++ f = drules + druleslen++; ++ f->id = r.id ? strdup(r.id) : NULL; ++ f->title = r.title ? strdup(r.title) : NULL; ++ ++found: ++ if (del) { ++ f->id = f->title = "removedrule"; ++ return; ++ } ++ r.id = f->id; ++ r.title = f->title; ++ *f = r; ++} ++ + void + monocle(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/menurule/menurule.png b/dwl-bak/dwl-patches/patches/menurule/menurule.png new file mode 100644 index 0000000..20faac2 Binary files /dev/null and b/dwl-bak/dwl-patches/patches/menurule/menurule.png differ diff --git a/dwl-bak/dwl-patches/patches/meson/README.md b/dwl-bak/dwl-patches/patches/meson/README.md new file mode 100644 index 0000000..4641bbf --- /dev/null +++ b/dwl-bak/dwl-patches/patches/meson/README.md @@ -0,0 +1,19 @@ +### Description +Add the meson build system. + +This is useful for people who do not want to self-manage a wlroots installation. + +To enable Xwayland support, you will need to enable it in the wlroots subproject: +```sh +meson setup -Dwlroots:xwayland=enabled build +``` +It is also reccomended to see the wlroots meson project configuration logs for any +unusual checks, such as requiring `hwdata` for the DRM backend. + +### Download +- [git branch](/sewn/dwl/src/branch/meson) +- [2024-12-07](/dwl/dwl-patches/raw/branch/main/patches/meson/meson.patch) + +### Authors +- [sewn](/sewn) + diff --git a/dwl-bak/dwl-patches/patches/meson/meson.patch b/dwl-bak/dwl-patches/patches/meson/meson.patch new file mode 100644 index 0000000..6d78036 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/meson/meson.patch @@ -0,0 +1,135 @@ +From 136cdeb302fdfe28e5cd5c6a1693b05c3d1bfb58 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sat, 7 Dec 2024 09:59:01 +0300 +Subject: [PATCH] add meson + +--- + .gitignore | 1 + + meson.build | 93 ++++++++++++++++++++++++++++++++++++++++ + subprojects/wlroots.wrap | 5 +++ + 3 files changed, 99 insertions(+) + create mode 100644 meson.build + create mode 100644 subprojects/wlroots.wrap + +diff --git a/.gitignore b/.gitignore +index 0dde90e..9246a31 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -4,3 +4,4 @@ dwl + *-protocol.h + .ccls-cache + config.h ++subprojects/wlroots +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000..e2219ec +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,93 @@ ++project( ++ 'dwl', ++ 'c', ++ version: run_command('git', 'describe', '--tags', '--dirty', check: false).stdout().strip(), ++ license: [ 'GPL-3.0-only', 'CC0-1.0', 'MIT' ], ++ meson_version: '>=1.3', ++ default_options: [ ++ 'c_std=c99', ++ 'warning_level=2', ++ ], ++) ++ ++configure_file( ++ input: 'config.def.h', ++ output: 'config.h', ++ copy: true, ++ install_dir: '.', ++) ++ ++cc = meson.get_compiler('c') ++ ++add_project_arguments([ ++ '-DWLR_USE_UNSTABLE', ++ '-D_POSIX_C_SOURCE=200809L', ++ '-DVERSION="@0@"'.format(meson.project_version()), ++ ++ '-Wno-unused-parameter', ++], language: 'c') ++ ++wlroots = subproject('wlroots', ++ default_options: [ ++ 'backends=drm,libinput,auto', ++ 'default_library=static', ++ 'examples=false', ++ 'session=enabled', ++ ], ++) ++wlroots_has_xwlr = wlroots.get_variable('features').get('xwayland') ++ ++libinput = dependency('libinput') ++math = cc.find_library('m') ++wayland_server = dependency('wayland-server') ++xcb = dependency('xcb', required: wlroots_has_xwlr) ++xcb_icccm = dependency('xcb-icccm', required: wlroots_has_xwlr) ++xkbcommon = dependency('xkbcommon') ++ ++dwl_deps = [ ++ libinput, ++ math, ++ wayland_server, ++ wlroots.get_variable('wlroots'), ++ xkbcommon, ++] ++ ++if wlroots_has_xwlr ++ add_project_arguments('-DXWAYLAND', language: 'c') ++ dwl_deps += [ xcb, xcb_icccm ] ++endif ++ ++wayland_protos = dependency('wayland-protocols') ++wayland_scanner = dependency('wayland-scanner') ++wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir') ++wayland_scanner_prog = find_program( ++ wayland_scanner.get_variable('wayland_scanner'), ++ native: true, ++) ++ ++protocols = [ ++ wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', ++ wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', ++ 'protocols/wlr-layer-shell-unstable-v1.xml', ++ 'protocols/wlr-output-power-management-unstable-v1.xml', ++ wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', ++] ++protocols_src = [] ++ ++wayland_scanner_server = generator( ++ wayland_scanner_prog, ++ output: '@BASENAME@-protocol.h', ++ arguments: ['server-header', '@INPUT@', '@OUTPUT@'], ++) ++ ++foreach xml : protocols ++ protocols_src += wayland_scanner_server.process(xml) ++endforeach ++ ++executable( ++ 'dwl', ++ [ 'dwl.c', 'util.c', protocols_src ], ++ include_directories: [include_directories('.')], ++ dependencies: dwl_deps, ++ install: true, ++) +diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap +new file mode 100644 +index 0000000..3d9cbfa +--- /dev/null ++++ b/subprojects/wlroots.wrap +@@ -0,0 +1,5 @@ ++[wrap-git] ++url = https://gitlab.freedesktop.org/wlroots/wlroots.git ++revision = master ++depth = 1 ++clone-recursive = true +-- +2.47.1 + diff --git a/dwl-bak/dwl-patches/patches/minimalborders/README.md b/dwl-bak/dwl-patches/patches/minimalborders/README.md new file mode 100644 index 0000000..2a670c9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/minimalborders/README.md @@ -0,0 +1,34 @@ +### Description +Dynamically adjusts the borders between adjacent windows to make them visually merge + +**NOTE:** to disable minimalborders after applying this patch, set `draw_minimal_borders` to `0` + +```c +static const int draw_minimal_borders = 0; /* disable minimalborders */ +``` + +
+Preview: +
+with:
+
+```c
+static const unsigned int borderpx         = 10;  /* border pixel of windows */
+```
+
+Before applying the patch
+
+
+After applying the patch
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/minimalborders) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/minimalborders/minimalborders.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/7a5c3420822074c544fa102e030b7c30aa6b6be8/minimalborders/minimalborders.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/be3735bc6a5c64ff76c200a8679453bd179be456/minimalborders/minimalborders.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/minimalborders/minimalborders.patch b/dwl-bak/dwl-patches/patches/minimalborders/minimalborders.patch new file mode 100644 index 0000000..cb19900 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/minimalborders/minimalborders.patch @@ -0,0 +1,161 @@ +From ce0eb92fb100801f343fbe9b76639847a9e39160 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:22:57 -0500 +Subject: [PATCH] implement minimalborders + +--- + config.def.h | 1 + + dwl.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 72 insertions(+), 6 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..0322dbf 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const int draw_minimal_borders = 1; /* merge adjacent borders */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +diff --git a/dwl.c b/dwl.c +index dc0437e..198061b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -106,6 +106,7 @@ typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ ++ int interact; + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; +@@ -316,7 +317,8 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resizeapply(Client *c, struct wlr_box geo, int interact); ++static void resizenoapply(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -408,6 +410,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static void (*resize)(Client *c, struct wlr_box geo, int interact) = resizeapply; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -476,6 +480,35 @@ applyrules(Client *c) + setmon(c, mon, newtags); + } + ++void ++applyminimalborders(Client *c, Monitor *m) ++{ ++ struct wlr_box geom = c->geom; ++ ++ geom.x -= borderpx; ++ geom.width += borderpx; ++ geom.y -= borderpx; ++ geom.height += borderpx; ++ ++ if (geom.x < m->w.x) { ++ geom.x += borderpx; ++ geom.width -= borderpx; ++ } ++ if (geom.x + geom.width > m->w.width - (int)borderpx) { ++ geom.width -= borderpx; ++ } ++ ++ if (geom.y < m->w.y) { ++ geom.y += borderpx; ++ geom.height -= borderpx; ++ } ++ if (geom.y + geom.height > m->w.height - (int)borderpx) { ++ geom.height -= borderpx; ++ } ++ ++ resize(c, geom, 0); ++} ++ + void + arrange(Monitor *m) + { +@@ -510,8 +543,28 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) +- m->lt[m->sellt]->arrange(m); ++ if (m->lt[m->sellt]->arrange) { ++ if (draw_minimal_borders) { ++ int save_width = m->w.width; ++ int save_height = m->w.height; ++ m->w.width += borderpx; ++ m->w.height += borderpx; ++ resize = resizenoapply; ++ m->lt[m->sellt]->arrange(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (draw_minimal_borders) ++ applyminimalborders(c, m); ++ resizeapply(c, c->geom, c->interact); ++ } ++ m->w.width = save_width; ++ m->w.height = save_height; ++ resize = resizeapply; ++ } else { ++ m->lt[m->sellt]->arrange(m); ++ } ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -1962,8 +2015,13 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && +- sloppyfocus && time && c && !client_is_unmanaged(c)) +- focusclient(c, 0); ++ sloppyfocus && time && c && !client_is_unmanaged(c)) { ++ if (c->isfloating || c->isfullscreen) { ++ focusclient(c, 0); ++ } else { ++ focusclient(c, 1); ++ } ++ } + + /* If surface is NULL, clear pointer focus */ + if (!surface) { +@@ -2128,7 +2186,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resizeapply(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2160,6 +2218,13 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++void ++resizenoapply(Client *c, struct wlr_box geo, int interact) ++{ ++ c->geom = geo; ++ c->interact = interact; ++} ++ + void + run(char *startup_cmd) + { +-- +2.45.1 diff --git a/dwl-bak/dwl-patches/patches/modes/README.md b/dwl-bak/dwl-patches/patches/modes/README.md new file mode 100644 index 0000000..c855af9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/modes/README.md @@ -0,0 +1,35 @@ +### Description +Implement modes, that way each mapping is associated with a mode and is only active while in that mode, default mode is `NORMAL` + +### Example + +In the example below, you declare a mode: `BROWSER`, which is activated when you press modkey + b. Then, you can press f to launch `Firefox` and return to the default `NORMAL` mode. + +```c +enum { + BROWSER, +}; +const char *modes_labels[] = { + "browser", +}; + +static const Key keys[] = { + // ... + { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} }, + // ... +}; + +static const Modekey modekeys[] = { + /* mode modifier key function argument */ + { BROWSER, { 0, XKB_KEY_f, spawn, SHCMD("firefox") } }, + { BROWSER, { 0, XKB_KEY_f, entermode, {.i = NORMAL} } }, + { BROWSER, { 0, XKB_KEY_Escape, entermode, {.i = NORMAL} } }, +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/modes) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/modes/modes.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/modes/modes.patch b/dwl-bak/dwl-patches/patches/modes/modes.patch new file mode 100644 index 0000000..5ecf846 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/modes/modes.patch @@ -0,0 +1,165 @@ +From a32b85018ff2cea0fc9f9137789860a4aadc3b3a Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 6 Mar 2024 07:31:17 -0500 +Subject: [PATCH] implement modes + +like sway/river modes +--- + config.def.h | 20 ++++++++++++++++++++ + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 66 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..1616136 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* You can also use glsl colors */ + ++enum { ++ BROWSER, ++}; ++const char *modes_labels[] = { ++ "browser", ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -152,6 +159,8 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + ++ { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} }, ++ + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is +@@ -162,6 +171,17 @@ static const Key keys[] = { + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; + ++static const Modekey modekeys[] = { ++ /* mode modifier key function argument */ ++ { BROWSER, { 0, XKB_KEY_f, spawn, SHCMD("firefox") } }, ++ { BROWSER, { 0, XKB_KEY_f, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_b, spawn, SHCMD("brave") } }, ++ { BROWSER, { 0, XKB_KEY_b, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_g, spawn, SHCMD("google-chrome-stable") } }, ++ { BROWSER, { 0, XKB_KEY_g, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_Escape, entermode, {.i = NORMAL} } }, ++}; ++ + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +diff --git a/dwl.c b/dwl.c +index ef27a1d..1ada006 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,11 @@ typedef struct { + const Arg arg; + } Key; + ++typedef struct { ++ int mode_index; ++ Key key; ++} Modekey; ++ + typedef struct { + struct wl_list link; + struct wlr_keyboard *wlr_keyboard; +@@ -270,6 +275,7 @@ static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int modekeybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -327,6 +333,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void entermode(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -377,6 +384,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const int NORMAL = -1; ++static int active_mode_index = NORMAL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1372,6 +1382,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + */ + int handled = 0; + const Key *k; ++ ++ if (active_mode_index >= 0) { ++ return modekeybinding(mods, sym); ++ } ++ + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) && + sym == k->keysym && k->func) { +@@ -1382,6 +1397,29 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + return handled; + } + ++int ++modekeybinding(uint32_t mods, xkb_keysym_t sym) ++{ ++ int handled = 0; ++ const Modekey *mk; ++ const Key *k; ++ ++ for (mk = modekeys; mk < END(modekeys); mk++) { ++ if (active_mode_index != mk->mode_index) { ++ continue; ++ } ++ ++ k = &mk->key; ++ if (CLEANMASK(mods) == CLEANMASK(k->mod) && ++ sym == k->keysym && k->func) { ++ k->func(&k->arg); ++ handled = 1; ++ } ++ } ++ ++ return handled; ++} ++ + void + keypress(struct wl_listener *listener, void *data) + { +@@ -1851,6 +1889,7 @@ printstatus(void) + printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags], + sel, urg); + printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); ++ printf("%s mode %s\n", m->wlr_output->name, modes_labels[active_mode_index] ? modes_labels[active_mode_index] : ""); + } + fflush(stdout); + } +@@ -2746,6 +2785,13 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++entermode(const Arg *arg) ++{ ++ active_mode_index = arg->i; ++ printstatus(); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.42.0 + diff --git a/dwl-bak/dwl-patches/patches/monitorconfig/README.md b/dwl-bak/dwl-patches/patches/monitorconfig/README.md new file mode 100644 index 0000000..313e5a0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/monitorconfig/README.md @@ -0,0 +1,9 @@ +### Description +Allows more monitor configuration in config.h + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/monitorconfig) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/monitorconfig/monitorconfig.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/monitorconfig/monitorconfig.patch b/dwl-bak/dwl-patches/patches/monitorconfig/monitorconfig.patch new file mode 100644 index 0000000..4922f8d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/monitorconfig/monitorconfig.patch @@ -0,0 +1,95 @@ +From 73f70cd9d817a307030f360f6c8a2500046b8b76 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Mon, 4 Apr 2022 16:08:29 +0200 +Subject: [PATCH] Updated patch now allowing setting x and y +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: Leonardo Hernández Hernández +--- + config.def.h | 11 +++++++---- + dwl.c | 25 +++++++++++++++++++------ + 2 files changed, 26 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8f498d2..4ccacd2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -38,12 +38,15 @@ static const Layout layouts[] = { + /* monitors */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ +- /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ /* name mfact nmaster scale layout rotate/reflect x y resx resy rate mode adaptive*/ ++ /* example of a HiDPI laptop monitor at 120Hz: ++ { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 120.000f, 1, 1}, ++ * mode let's the user decide on how dwl should implement the modes: ++ * -1 Sets a custom mode following the users choice ++ * All other number's set the mode at the index n, 0 is the standard mode; see wlr-randr + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 0 ,1}, + }; + + /* keyboard */ +diff --git a/dwl.c b/dwl.c +index 52bfbc8..9609b6d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -215,6 +215,11 @@ typedef struct { + const Layout *lt; + enum wl_output_transform rr; + int x, y; ++ int resx; ++ int resy; ++ float rate; ++ int mode; ++ int adaptive; + } MonitorRule; + + typedef struct { +@@ -865,6 +870,7 @@ createmon(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; ++ struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.next, mode, link); + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +@@ -893,16 +899,23 @@ createmon(struct wl_listener *listener, void *data) + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); ++ ++ wlr_output_state_set_adaptive_sync_enabled(&state, r->adaptive); ++ ++ if(r->mode == -1) ++ wlr_output_state_set_custom_mode(&state, r->resx, r->resy, ++ (int) (r->rate > 0 ? r->rate * 1000 : 0)); ++ else if (!wl_list_empty(&wlr_output->modes)) { ++ for (int j = 0; j < r->mode; j++) { ++ mode = wl_container_of(mode->link.next, mode, link); ++ } ++ wlr_output_state_set_mode(&state, mode); ++ } ++ + break; + } + } + +- /* The mode is a tuple of (width, height, refresh rate), and each +- * monitor supports only a specific set of modes. We just pick the +- * monitor's preferred mode; a more sophisticated compositor would let +- * the user configure it. */ +- wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); +- + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/mouse-trackpad-split/README.md b/dwl-bak/dwl-patches/patches/mouse-trackpad-split/README.md new file mode 100644 index 0000000..a3e6e44 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/mouse-trackpad-split/README.md @@ -0,0 +1,8 @@ +### Description +Separates natural scrolling and acceleration settings for trackpad and mouse. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch) + +### Authors +- [mmistika](https://codeberg.org/mmistika) diff --git a/dwl-bak/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch b/dwl-bak/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch new file mode 100644 index 0000000..89e33ac --- /dev/null +++ b/dwl-bak/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch @@ -0,0 +1,96 @@ +From 211b52988756e9fecccf071fdea82832e1e17a0c Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Tue, 24 Jun 2025 22:25:00 +0200 +Subject: [PATCH] Separate trackpad/mouse natural scroll and accel + +Signed-off-by: mmistika +--- + config.def.h | 12 +++++++++--- + dwl.c | 26 ++++++++++++++++++-------- + 2 files changed, 27 insertions(+), 11 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d05a89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -67,10 +67,14 @@ static const int repeat_delay = 600; + static const int tap_to_click = 1; + static const int tap_and_drag = 1; + static const int drag_lock = 1; +-static const int natural_scrolling = 0; + static const int disable_while_typing = 1; + static const int left_handed = 0; + static const int middle_button_emulation = 0; ++ ++/* Natural scrolling */ ++static const int trackpad_natural_scrolling = 0; ++static const int mouse_natural_scrolling = 0; ++ + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +@@ -97,8 +101,10 @@ static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE + */ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; ++static const enum libinput_config_accel_profile trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; ++static const double trackpad_accel_speed = 0.0; ++static const enum libinput_config_accel_profile mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; ++static const double mouse_accel_speed = 0.0; + + /* You can choose between: + LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +diff --git a/dwl.c b/dwl.c +index c717c1d..f05d6c5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1083,14 +1083,29 @@ createpointer(struct wlr_pointer *pointer) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { ++ /* Trackpad */ + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); +- } + +- if (libinput_device_config_scroll_has_natural_scroll(device)) +- libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, trackpad_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, trackpad_accel_profile); ++ libinput_device_config_accel_set_speed(device, trackpad_accel_speed); ++ } ++ } else { ++ /* Mouse */ ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, mouse_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, mouse_accel_profile); ++ libinput_device_config_accel_set_speed(device, mouse_accel_speed); ++ } ++ } + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); +@@ -1109,11 +1124,6 @@ createpointer(struct wlr_pointer *pointer) + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); +- +- if (libinput_device_config_accel_is_available(device)) { +- libinput_device_config_accel_set_profile(device, accel_profile); +- libinput_device_config_accel_set_speed(device, accel_speed); +- } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +-- +2.50.0 + diff --git a/dwl-bak/dwl-patches/patches/movecenter/README.md b/dwl-bak/dwl-patches/patches/movecenter/README.md new file mode 100644 index 0000000..aff8f7d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/movecenter/README.md @@ -0,0 +1,28 @@ +### Description + +> This patch is no longer being maintained by me [wochap](https://codeberg.org/wochap), since I'm now using a different patch specific to my use case: https://codeberg.org/wochap/dwl/src/branch/v0.6-c/betterfloat/betterfloat-diff.patch. + +This patch provides a keybinding to center the focused floating window. + +Press MODKEY + x to center the focused floating window. + +It does NOT center windows that are not floating. + +The variable `respect_monitor_reserved_area` allows the user to choose whether to center relative to the monitor or relative to the window area. + +
+Explanation of respect_monitor_reserved_area: +
+The "Monitor area" refers to the space enclosed by the green rectangle, while the "Window area" refers to the space enclosed by the red rectangle.
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6/movecenter) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/commit/b1ca929ee645cd3e175f198e250448b54624acd6/patches/movecenter/movecenter.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/187d7f511572457750fcf6e42c99cdc7befe05e7/patches/movecenter/movecenter.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) + diff --git a/dwl-bak/dwl-patches/patches/movecenter/movecenter.patch b/dwl-bak/dwl-patches/patches/movecenter/movecenter.patch new file mode 100644 index 0000000..f96bd36 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/movecenter/movecenter.patch @@ -0,0 +1,82 @@ +From bc5206882c71b32198dae5f1c85601a863a7c0a9 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 31 Jul 2024 07:43:10 -0500 +Subject: [PATCH] implement movecenter fn + +--- + config.def.h | 2 ++ + dwl.c | 31 +++++++++++++++++++++++++++++++ + 2 files changed, 33 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..f5225d9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -142,6 +143,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_x, movecenter, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..791e598 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -336,6 +336,8 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void _movecenter(Client *c, int interact); ++static void movecenter(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2683,6 +2685,35 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++_movecenter(Client *c, int interact) ++{ ++ struct wlr_box b; ++ ++ if (!c || !c->mon) { ++ return; ++ } ++ ++ if (!c->isfloating) { ++ return; ++ } ++ ++ b = respect_monitor_reserved_area ? c->mon->w : c->mon->m; ++ resize(c, (struct wlr_box){ ++ .x = (b.width - c->geom.width) / 2 + b.x, ++ .y = (b.height - c->geom.height) / 2 + b.y, ++ .width = c->geom.width, ++ .height = c->geom.height, ++ }, interact); ++} ++ ++void ++movecenter(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ _movecenter(c, 1); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/moveresizekb/README.md b/dwl-bak/dwl-patches/patches/moveresizekb/README.md new file mode 100644 index 0000000..0c53caf --- /dev/null +++ b/dwl-bak/dwl-patches/patches/moveresizekb/README.md @@ -0,0 +1,20 @@ +### Description +This allows the user to change size and placement of floating windows using only the keyboard, default keybindings: + +| Keybinding | Action | +| :--- | :--- | +| MODKEY + Up | move 40px up | +| MODKEY + Down | move 40px down | +| MODKEY + Left | move 40px left | +| MODKEY + Right | move 40px right | +| MODKEY + Shift + Up | shrink height 40px | +| MODKEY + Shift + Down | grow height 40px | +| MODKEY + Shift + Left | shrink width 40px | +| MODKEY + Shift + Right | grow width 40px | + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/moveresizekb) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/moveresizekb/moveresizekb.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/moveresizekb/moveresizekb.patch b/dwl-bak/dwl-patches/patches/moveresizekb/moveresizekb.patch new file mode 100644 index 0000000..aeb1e5e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/moveresizekb/moveresizekb.patch @@ -0,0 +1,70 @@ +From c8af428f964679089599e4ffbe7d28d08a4e875f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 5 Mar 2024 23:42:55 -0500 +Subject: [PATCH] implement keybindings to move and resize focused floating + window + +--- + config.def.h | 8 ++++++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 27 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..d0570b8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -135,6 +135,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 40, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, -40, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Right, moveresizekb, {.v = (int []){ 40, 0, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Left, moveresizekb, {.v = (int []){ -40, 0, 0, 0 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 0, 0, 40 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, 0, 0, -40 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, moveresizekb, {.v = (int []){ 0, 0, 40, 0 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, moveresizekb, {.v = (int []){ 0, 0, -40, 0 }}}, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index ef27a1d..251472b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -313,6 +313,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void moveresizekb(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2454,6 +2455,24 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++moveresizekb(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ Monitor *m = selmon; ++ ++ if(!(m && arg && arg->v && c && c->isfloating)) { ++ return; ++ } ++ ++ resize(c, (struct wlr_box){ ++ .x = c->geom.x + ((int *)arg->v)[0], ++ .y = c->geom.y + ((int *)arg->v)[1], ++ .width = c->geom.width + ((int *)arg->v)[2], ++ .height = c->geom.height + ((int *)arg->v)[3], ++ }, 1); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.42.0 + diff --git a/dwl-bak/dwl-patches/patches/movestack/README.md b/dwl-bak/dwl-patches/patches/movestack/README.md new file mode 100644 index 0000000..f50bf68 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/movestack/README.md @@ -0,0 +1,13 @@ +### Description +Allows you to move a window up and down the stack. + +### Download +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/movestack) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/movestack/movestack.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [sam-barr](https://github.com/ss7m) +- [Dmitry Zakharchenko](https://github.com/dm1tz) +- [Abanoub8](https://github.com/Abanoub8) +- [Nikita Ivanov](https://github.com/NikitaIvanovV) diff --git a/dwl-bak/dwl-patches/patches/movestack/movestack.patch b/dwl-bak/dwl-patches/patches/movestack/movestack.patch new file mode 100644 index 0000000..2543c66 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/movestack/movestack.patch @@ -0,0 +1,87 @@ +From 08230817bd3926e29d9897657eb1852cb27d461f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 23:21:19 +0100 +Subject: [PATCH] Allows you to move a window up and down the stack + +--- + config.def.h | 2 ++ + dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..2c129f2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index def2562..045d6fa 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -303,6 +303,7 @@ static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); ++static void movestack(const Arg *arg); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +@@ -1786,6 +1787,48 @@ monocle(Monitor *m) + wlr_scene_node_raise_to_top(&c->scene->node); + } + ++void ++movestack(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ ++ if (!sel) { ++ return; ++ } ++ ++ if (wl_list_length(&clients) <= 1) { ++ return; ++ } ++ ++ if (arg->i > 0) { ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ } else { ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ /* backup one client */ ++ c = wl_container_of(c->link.prev, c, link); ++ } ++ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ arrange(selmon); ++} ++ + void + motionabsolute(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/namedscratchpads/README.md b/dwl-bak/dwl-patches/patches/namedscratchpads/README.md new file mode 100644 index 0000000..e436583 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/namedscratchpads/README.md @@ -0,0 +1,19 @@ +### Description +Allows for the creation of multiple scratchpad windows, each assigned to a different keybinding. In simple terms, it enables 'run or raise' functionality + +This patch adds the following functions: + +* `togglescratch`: simply toggles the scratchpad window +* `focusortogglescratch`: change the focus to the scratchpad window if it is visible and toggles it if it is already in focus +* `focusortogglematchingscratch`: similar to `focusortogglescratch` but also closes all other scratchpad windows + +If you don't assign keybindings to any of the above functions and so get a compiler warning about them not being used, just remove them from your dwl branch to stop the warning. + +### Download +- [git branch (v0.6)](https://codeberg.org/bencc/dwl/src/branch/namedscratchpads) +- [2024-07-13 (v0.6)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/namedscratchpads/namedscratchpads.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [wochap](https://codeberg.org/wochap) +- [Louis-Michel Raynauld](https://github.com/loumray) diff --git a/dwl-bak/dwl-patches/patches/namedscratchpads/namedscratchpads.patch b/dwl-bak/dwl-patches/patches/namedscratchpads/namedscratchpads.patch new file mode 100644 index 0000000..7a0a257 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/namedscratchpads/namedscratchpads.patch @@ -0,0 +1,278 @@ +From bd83d56b3a268112b029961ad6ff4232b2d6f00d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 16:35:05 +1000 +Subject: [PATCH 1/2] namedscratchpads + +--- + config.def.h | 11 ++++++++--- + dwl.c | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 47 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..36a691a9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor scratchkey */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, -1, 0 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1, 0 }, /* Start on ONLY tag "9" */ ++ { NULL, "scratchpad", 0, 1, -1, 's' }, + }; + + /* layout(s) */ +@@ -122,11 +123,15 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++/* named scratchpads - First arg only serves to match against key in rules*/ ++static const char *scratchpadcmd[] = { "s", "alacritty", "-t", "scratchpad", NULL }; ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_grave, togglescratch, {.v = scratchpadcmd } }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index dc0437e0..eb0eb775 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ char scratchkey; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -230,6 +231,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ const char scratchkey; + } Rule; + + typedef struct { +@@ -330,12 +332,14 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void spawnscratch(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + 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 togglefullscreen(const Arg *arg); ++static void togglescratch(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -456,6 +460,7 @@ applyrules(Client *c) + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); ++ c->scratchkey = 0; + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) +@@ -465,6 +470,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->scratchkey = r->scratchkey; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -2603,6 +2609,16 @@ spawn(const Arg *arg) + } + } + ++void spawnscratch(const Arg *arg) ++{ ++ if (fork() == 0) { ++ dup2(STDERR_FILENO, STDOUT_FILENO); ++ setsid(); ++ execvp(((char **)arg->v)[1], ((char **)arg->v)+1); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[1]); ++ } ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +@@ -2686,6 +2702,29 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglescratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ ++ /* search for first window that matches the scratchkey */ ++ wl_list_for_each(c, &clients, link) ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ found = 1; ++ break; ++ } ++ ++ if (found) { ++ c->tags = VISIBLEON(c, selmon) ? 0 : selmon->tagset[selmon->seltags]; ++ ++ focusclient(c->tags == 0 ? focustop(selmon) : c, 1); ++ arrange(selmon); ++ } else{ ++ spawnscratch(arg); ++ } ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + + +From 4963c34b4958fba9d53a23a1c9929d554a4e8b3d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 13 Jul 2024 14:50:45 +1000 +Subject: [PATCH 2/2] namedscratchpads: focusortoggle functions + +--- + config.def.h | 2 ++ + dwl.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 89 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 36a691a9..21bb66bb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -132,6 +132,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_grave, togglescratch, {.v = scratchpadcmd } }, ++ // { MODKEY, XKB_KEY_grave, focusortogglescratch, {.v = scratchpadcmd } }, ++ // { MODKEY, XKB_KEY_grave, focusortogglematchingscratch, {.v = scratchpadcmd } }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index eb0eb775..d7c5552f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -286,6 +286,8 @@ static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); ++static void focusortogglematchingscratch(const Arg *arg); ++static void focusortogglescratch(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); +@@ -1405,6 +1407,91 @@ focusmon(const Arg *arg) + focusclient(focustop(selmon), 1); + } + ++void ++focusortogglematchingscratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ unsigned int hide = 0; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->scratchkey == 0) { ++ continue; ++ } ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ if (VISIBLEON(c, selmon)) { ++ if (found == 1) { ++ if (hide == 1) { ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ } ++ continue; ++ } ++ if (focustop(selmon) == c) { ++ // hide ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ hide = 1; ++ } else { ++ // focus ++ focusclient(c, 1); ++ } ++ } else { ++ // show ++ c->tags = selmon->tagset[selmon->seltags]; ++ // focus ++ focusclient(c, 1); ++ } ++ found = 1; ++ continue; ++ } ++ if (VISIBLEON(c, selmon)) { ++ // hide ++ c->tags = 0; ++ } ++ } ++ ++ if (found) { ++ arrange(selmon); ++ } else { ++ spawnscratch(arg); ++ } ++} ++ ++void ++focusortogglescratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ ++ /* search for first window that matches the scratchkey */ ++ wl_list_for_each(c, &clients, link) ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ found = 1; ++ break; ++ } ++ ++ if (found) { ++ if (VISIBLEON(c, selmon)) { ++ if (focustop(selmon) == c) { ++ // hide ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ } else { ++ // focus ++ focusclient(c, 1); ++ } ++ } else { ++ // show ++ c->tags = selmon->tagset[selmon->seltags]; ++ focusclient(c, 1); ++ } ++ arrange(selmon); ++ } else{ ++ spawnscratch(arg); ++ } ++} ++ + void + focusstack(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/nextlayout/README.md b/dwl-bak/dwl-patches/patches/nextlayout/README.md new file mode 100644 index 0000000..ea1d4a6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/nextlayout/README.md @@ -0,0 +1,9 @@ +### Description +Change the current layout to the next available one. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/nextlayout/nextlayout.patch) + +### Authors +- [sewn](/sewn) + diff --git a/dwl-bak/dwl-patches/patches/nextlayout/nextlayout.patch b/dwl-bak/dwl-patches/patches/nextlayout/nextlayout.patch new file mode 100644 index 0000000..11f4dc7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/nextlayout/nextlayout.patch @@ -0,0 +1,65 @@ +From 7d8cfa63681830a3af4512799b8260f8249bc514 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sun, 8 Sep 2024 22:49:33 +0300 +Subject: [PATCH] add feature to switch to next available layout + +ported from suckless cyclelayouts to be slightly more useful +https://dwm.suckless.org/patches/cyclelayouts/ +--- + config.def.h | 2 ++ + dwl.c | 12 ++++++++++++ + 2 files changed, 14 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..f88a615 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { NULL, NULL }, /* terminate */ + }; + + /* monitors */ +@@ -140,6 +141,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_n, nextlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..a66d9d9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -308,6 +308,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static void nextlayout(const Arg *arg); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -1927,6 +1928,17 @@ moveresize(const Arg *arg) + } + } + ++void ++nextlayout(const Arg *arg) ++{ ++ Layout *l; ++ for(l = (Layout *)layouts; l != selmon->lt[selmon->sellt]; l++); ++ if(l->symbol && (l + 1)->symbol) ++ setlayout(&((Arg) { .v = (l + 1) })); ++ else ++ setlayout(&((Arg) { .v = layouts })); ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/numlock-capslock/README.md b/dwl-bak/dwl-patches/patches/numlock-capslock/README.md new file mode 100644 index 0000000..54a4f34 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/numlock-capslock/README.md @@ -0,0 +1,10 @@ +### Description +Allows activating numlock or capslock at startup. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/numlock-capslock) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/numlock-capslock/numlock-capslock.patch) +- [numlock-capslock.patch](/dwl/dwl-patches/raw/branch/main/patches/numlock-capslock/numlock-capslock-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch b/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch new file mode 100644 index 0000000..7b30be3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch @@ -0,0 +1,84 @@ +From cbacfb5031b91bc6677b0fb7c07dbe79cc2e0177 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 4 Apr 2021 19:56:09 -0500 +Subject: [PATCH] add option to enable numlock/capslock +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 4 ++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..21dc6201 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,10 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* numlock and capslock */ ++static const int numlock = 1; ++static const int capslock = 0; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index a2711f67..a11f0bcf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -360,6 +361,7 @@ static void zoom(const Arg *arg); + static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; ++static uint32_t locked_mods = 0; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wl_event_loop *event_loop; +@@ -877,6 +879,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + ++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); ++ + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); + } +@@ -898,6 +902,21 @@ createkeyboardgroup(void) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); ++ if (numlock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (capslock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (locked_mods) ++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0); ++ + xkb_keymap_unref(keymap); + xkb_context_unref(context); + +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock.patch b/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock.patch new file mode 100644 index 0000000..adf9f38 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/numlock-capslock/numlock-capslock.patch @@ -0,0 +1,84 @@ +From ec5dbcd9f4629549d3d14b1791305a42479a935f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 4 Apr 2021 19:56:09 -0500 +Subject: [PATCH] add option to enable numlock/capslock +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 4 ++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..21dc6201 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,10 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* numlock and capslock */ ++static const int numlock = 1; ++static const int capslock = 0; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..d0059ec8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -355,6 +356,7 @@ static void zoom(const Arg *arg); + /* variables */ + static pid_t child_pid = -1; + static int locked; ++static uint32_t locked_mods = 0; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wl_event_loop *event_loop; +@@ -936,6 +938,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + ++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); ++ + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); + } +@@ -957,6 +961,21 @@ createkeyboardgroup(void) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); ++ if (numlock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (capslock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (locked_mods) ++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0); ++ + xkb_keymap_unref(keymap); + xkb_context_unref(context); + +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/passthrough/README.md b/dwl-bak/dwl-patches/patches/passthrough/README.md new file mode 100644 index 0000000..0380951 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/passthrough/README.md @@ -0,0 +1,13 @@ +### Description +allows pausing keybind handling + +also allows for bitcarrying-esque control of nested instances + +default keybind is Ctrl+Logo+Alt+Shift+Esc, can be customized in config.h + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/passthrough) +- [2024-06-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/passthrough/passthrough.patch) +- [2024-06-22](https://codeberg.org/dwl/dwl-patches/raw/commit/3f44fb23d8cb6c7d700f41525dc00493e392083c/patches/passthrough/passthrough.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/passthrough/passthrough.patch b/dwl-bak/dwl-patches/patches/passthrough/passthrough.patch new file mode 100644 index 0000000..d87f00e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/passthrough/passthrough.patch @@ -0,0 +1,82 @@ +From cd67c8386b0188daa15348c1d0d99187a556e461 Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 2 Jan 2023 13:00:29 +0800 +Subject: [PATCH] passthrough: allow pausing keybind handling + +also allows for bitcarrying-esque control of nested instances +--- + config.def.h | 4 ++++ + dwl.c | 14 ++++++++++++++ + 2 files changed, 18 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 646a3d6..2d14e2a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,9 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* passthrough */ ++static int passthrough = 0; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ +@@ -156,6 +159,7 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), ++ { WLR_MODIFIER_ALT|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, XKB_KEY_Escape, togglepassthrough, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +diff --git a/dwl.c b/dwl.c +index 9fb50a7..a1c65b4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -339,6 +339,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepassthrough(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -620,6 +621,11 @@ buttonpress(struct wl_listener *listener, void *data) + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && + event->button == b->button && b->func) { ++ if (passthrough) { ++ if (b->func != togglepassthrough) continue; ++ b->func(&b->arg); ++ break; ++ } + b->func(&b->arg); + return; + } +@@ -1509,6 +1515,8 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { ++ if (passthrough && k->func != togglepassthrough) ++ continue; + k->func(&k->arg); + return 1; + } +@@ -2677,6 +2685,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepassthrough(const Arg *arg) ++{ ++ passthrough = !passthrough; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/perinputconfig/README.md b/dwl-bak/dwl-patches/patches/perinputconfig/README.md new file mode 100644 index 0000000..ab1602c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/perinputconfig/README.md @@ -0,0 +1,12 @@ +### Description +Replace the singular keyboard and pointer input configuration with an array allowing to set different variables matching by name. + +Tip to find the names: Grep for `device_name` and add a line after it to print to stdout. Then run EX: `dwl > /tmp/print_device_names.log`, exit dwl, and should see the names. + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_perinputconfig) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/perinputconfig/perinputconfig.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/perinputconfig/perinputconfig-v0.5.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) diff --git a/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch b/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch new file mode 100644 index 0000000..25a2a92 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch @@ -0,0 +1,173 @@ +From 9388faea3c4648aa99c01b9e4ce9287237b28b38 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Mon, 1 Apr 2024 21:23:39 +0100 +Subject: [PATCH] Backport perinputconfig to v0.5 + +- Array replaced singular variables for configuration +- Only applies to enable-state, acceleration profile, and speed +- Like EX: Rules, requires NULL/default set at the end +- Keyboards can now also set by name +--- + config.def.h | 37 +++++++++++++++++++++++-------------- + dwl.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 69 insertions(+), 17 deletions(-) + +diff --git a/config.def.h b/config.def.h +index db0babc..861a937 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -46,12 +46,13 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ ++/* NOTE: Always include a fallback rule at the end (name as NULL) */ ++static const KeyboardRule kbrules[] = { ++ /* name rules model layout variant options */ + /* example: +- .options = "ctrl:nocaps", ++ { "keyboard", NULL, NULL, "us,de", NULL, "ctrl:nocaps" }, + */ +- .options = NULL, ++ { NULL, NULL, NULL, NULL, NULL, NULL }, + }; + + static const int repeat_rate = 25; +@@ -81,23 +82,31 @@ LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER + static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + /* You can choose between: ++LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle ++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; ++ ++/* ++send_events_mode: You can choose between: + LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +-*/ +-static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +-/* You can choose between: ++accel_profile: You can choose between: + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE ++ ++NOTE: Always include a fallback rule at the end (name as NULL) + */ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; +-/* You can choose between: +-LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +-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 InputRule inputrules[] = { ++ /* name send_events_mode accel_profile accel_speed*/ ++ /* examples: ++ { "SynPS/2 Synaptics TouchPad", LIBINPUT_CONFIG_SEND_EVENTS_DISABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++ { "TPPS/2 IBM TrackPoint", LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0.0 }, ++ */ ++ { NULL, LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++}; + + /* 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 ef27a1d..a35f480 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -226,6 +226,22 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *name; ++ uint32_t send_events_mode; ++ enum libinput_config_accel_profile accel_profile; ++ double accel_speed; ++} InputRule; ++ ++typedef struct { ++ const char *name; ++ const char *rules; ++ const char *model; ++ const char *layout; ++ const char *variant; ++ const char *options; ++} KeyboardRule; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -766,11 +782,31 @@ createidleinhibitor(struct wl_listener *listener, void *data) + void + createkeyboard(struct wlr_keyboard *keyboard) + { ++ struct xkb_rule_names xkb_rules; ++ struct libinput_device *libinput_device = NULL; + struct xkb_context *context; + struct xkb_keymap *keymap; ++ const KeyboardRule *krule = NULL; ++ const char *device_name = ""; + Keyboard *kb = keyboard->data = ecalloc(1, sizeof(*kb)); + kb->wlr_keyboard = keyboard; + ++ if (wlr_input_device_is_libinput(&keyboard->base) ++ && (libinput_device = wlr_libinput_get_device_handle(&keyboard->base))) { ++ device_name = libinput_device_get_name(libinput_device); ++ } ++ for (krule = kbrules; krule < END(kbrules); krule++) { ++ if (!krule->name || strstr(device_name, krule->name)) ++ break; ++ } ++ if (krule) { ++ xkb_rules.rules = krule->rules; ++ xkb_rules.model = krule->model; ++ xkb_rules.layout = krule->layout; ++ xkb_rules.variant = krule->variant; ++ xkb_rules.options = krule->options; ++ } ++ + /* Prepare an XKB keymap and assign it to the keyboard. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + keymap = xkb_keymap_new_from_names(context, &xkb_rules, +@@ -989,10 +1025,17 @@ createnotify(struct wl_listener *listener, void *data) + void + createpointer(struct wlr_pointer *pointer) + { ++ const InputRule *irule; + if (wlr_input_device_is_libinput(&pointer->base)) { + struct libinput_device *libinput_device = (struct libinput_device*) + wlr_libinput_get_device_handle(&pointer->base); + ++ const char *device_name = libinput_device_get_name(libinput_device); ++ for (irule = inputrules; irule < END(inputrules); irule++) { ++ if (!irule->name || strstr(device_name, irule->name)) ++ break; ++ } ++ + if (libinput_device_config_tap_get_finger_count(libinput_device)) { + libinput_device_config_tap_set_enabled(libinput_device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(libinput_device, tap_and_drag); +@@ -1019,11 +1062,11 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_click_set_method (libinput_device, click_method); + + if (libinput_device_config_send_events_get_modes(libinput_device)) +- libinput_device_config_send_events_set_mode(libinput_device, send_events_mode); ++ libinput_device_config_send_events_set_mode(libinput_device, irule->send_events_mode); + + if (libinput_device_config_accel_is_available(libinput_device)) { +- libinput_device_config_accel_set_profile(libinput_device, accel_profile); +- libinput_device_config_accel_set_speed(libinput_device, accel_speed); ++ libinput_device_config_accel_set_profile(libinput_device, irule->accel_profile); ++ libinput_device_config_accel_set_speed(libinput_device, irule->accel_speed); + } + } + +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig.patch b/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig.patch new file mode 100644 index 0000000..dae4bc8 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/perinputconfig/perinputconfig.patch @@ -0,0 +1,295 @@ +From c268707811fb5d8244115f23a0430f024e4e11a9 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 8 Jun 2024 11:30:34 +0100 +Subject: [PATCH] perinputconfig - 2024-06-08 Update + +* Array replaced singular variables for configuration +* Only applies to enable-state, acceleration profile, and speed +* Like EX: Rules, requires NULL/default set at the end +* Keyboards can now also set by name +--- + config.def.h | 36 +++++++++++++--------- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++++++++--------- + 2 files changed, 95 insertions(+), 28 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4..c733137 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -49,12 +49,13 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ ++/* NOTE: Always include a fallback rule at the end (name as NULL) */ ++static const KeyboardRule kbrules[] = { ++ /* name rules model layout variant options */ + /* example: +- .options = "ctrl:nocaps", ++ { "keyboard", NULL, NULL, "us,de", NULL, "ctrl:nocaps" }, + */ +- .options = NULL, ++ { NULL, NULL, NULL, NULL, NULL, NULL }, + }; + + static const int repeat_rate = 25; +@@ -84,24 +85,31 @@ LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER + static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + /* You can choose between: ++LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle ++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; ++ ++/* ++send_events_mode: You can choose between: + LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +-*/ +-static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +-/* You can choose between: ++accel_profile: You can choose between: + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +-*/ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; + +-/* You can choose between: +-LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +-LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right ++NOTE: Always include a fallback rule at the end (name as NULL) + */ +-static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; ++static const InputRule inputrules[] = { ++ /* name send_events_mode accel_profile accel_speed*/ ++ /* examples: ++ { "SynPS/2 Synaptics TouchPad", LIBINPUT_CONFIG_SEND_EVENTS_DISABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++ { "TPPS/2 IBM TrackPoint", LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0.0 }, ++ */ ++ { NULL, LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++}; + + /* 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 6f041a0..0673a05 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -240,6 +240,22 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *name; ++ uint32_t send_events_mode; ++ enum libinput_config_accel_profile accel_profile; ++ double accel_speed; ++} InputRule; ++ ++typedef struct { ++ const char *name; ++ const char *rules; ++ const char *model; ++ const char *layout; ++ const char *variant; ++ const char *options; ++} KeyboardRule; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -259,7 +275,7 @@ static void commitnotify(struct wl_listener *listener, void *data); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +-static KeyboardGroup *createkeyboardgroup(void); ++static KeyboardGroup *createkeyboardgroup(struct xkb_rule_names *new_xkb_rules); + static void createlayersurface(struct wl_listener *listener, void *data); + static void createlocksurface(struct wl_listener *listener, void *data); + static void createmon(struct wl_listener *listener, void *data); +@@ -396,7 +412,7 @@ static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; +-static KeyboardGroup *kb_group; ++static struct wl_list kb_groups; + static struct wlr_surface *held_grab; + static unsigned int cursor_mode; + static Client *grabc; +@@ -671,6 +687,8 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ KeyboardGroup *kb_group; ++ + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -683,7 +701,8 @@ cleanup(void) + wlr_xcursor_manager_destroy(cursor_mgr); + wlr_output_layout_destroy(output_layout); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ wl_list_for_each(kb_group, &kb_groups, link) ++ destroykeyboardgroup(&kb_group->destroy, NULL); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) +@@ -803,6 +822,30 @@ createidleinhibitor(struct wl_listener *listener, void *data) + void + createkeyboard(struct wlr_keyboard *keyboard) + { ++ KeyboardGroup *kb_group; ++ const char *device_name = ""; ++ const KeyboardRule *krule = NULL; ++ struct libinput_device *device = NULL; ++ ++ if (wlr_input_device_is_libinput(&keyboard->base) ++ && (device = wlr_libinput_get_device_handle(&keyboard->base))) { ++ device_name = libinput_device_get_name(device); ++ } ++ for (krule = kbrules; krule < END(kbrules); krule++) { ++ if (!krule->name || strstr(device_name, krule->name)) ++ break; ++ } ++ if (krule) { ++ struct xkb_rule_names xkb_rules; ++ xkb_rules.rules = krule->rules; ++ xkb_rules.model = krule->model; ++ xkb_rules.layout = krule->layout; ++ xkb_rules.variant = krule->variant; ++ xkb_rules.options = krule->options; ++ kb_group = createkeyboardgroup(&xkb_rules); ++ } else ++ wl_list_for_each(kb_group, &kb_groups, link); ++ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + +@@ -811,11 +854,16 @@ createkeyboard(struct wlr_keyboard *keyboard) + } + + KeyboardGroup * +-createkeyboardgroup(void) ++createkeyboardgroup(struct xkb_rule_names *new_xkb_rules) + { + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; ++ struct xkb_rule_names xkb_rules; ++ ++ memset(&xkb_rules, 0, sizeof(struct xkb_rule_names)); ++ if (new_xkb_rules) ++ xkb_rules = *new_xkb_rules; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; +@@ -845,6 +893,9 @@ createkeyboardgroup(void) + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); ++ ++ wl_list_init(&group->destroy.link); ++ wl_list_insert(&kb_groups, &group->link); + return group; + } + +@@ -1042,9 +1093,15 @@ createnotify(struct wl_listener *listener, void *data) + void + createpointer(struct wlr_pointer *pointer) + { ++ const InputRule *irule; + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ const char *device_name = libinput_device_get_name(device); ++ for (irule = inputrules; irule < END(inputrules); irule++) { ++ if (!irule->name || strstr(device_name, irule->name)) ++ break; ++ } + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); +@@ -1072,11 +1129,11 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) +- libinput_device_config_send_events_set_mode(device, send_events_mode); ++ libinput_device_config_send_events_set_mode(device, irule->send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { +- libinput_device_config_accel_set_profile(device, accel_profile); +- libinput_device_config_accel_set_speed(device, accel_speed); ++ libinput_device_config_accel_set_profile(device, irule->accel_profile); ++ libinput_device_config_accel_set_speed(device, irule->accel_speed); + } + } + +@@ -1277,7 +1334,6 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); +- free(group); + } + + Monitor * +@@ -1467,6 +1523,7 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ KeyboardGroup *group; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +@@ -1485,8 +1542,11 @@ inputdevice(struct wl_listener *listener, void *data) + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; +- if (!wl_list_empty(&kb_group->wlr_group->devices)) +- caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ wl_list_for_each(group, &kb_groups, link) ++ if (!wl_list_empty(&group->wlr_group->devices)) { ++ caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ break; ++ } + wlr_seat_set_capabilities(seat, caps); + } + +@@ -2431,6 +2491,7 @@ setup(void) + */ + wl_list_init(&clients); + wl_list_init(&fstack); ++ wl_list_init(&kb_groups); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_surface, createnotify); +@@ -2514,8 +2575,7 @@ setup(void) + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +- kb_group = createkeyboardgroup(); +- wl_list_init(&kb_group->destroy.link); ++ createkeyboardgroup(NULL); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); +@@ -2857,10 +2917,9 @@ virtualkeyboard(struct wl_listener *listener, void *data) + { + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ +- KeyboardGroup *group = createkeyboardgroup(); ++ KeyboardGroup *group = createkeyboardgroup(NULL); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); +- LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/pertag/README.md b/dwl-bak/dwl-patches/patches/pertag/README.md new file mode 100644 index 0000000..7473da0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/pertag/README.md @@ -0,0 +1,12 @@ +### Description +Makes layout, mwfact and nmaster individual for every tag. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6/pertag) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/pertag/pertag.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/bf098459219e7a473d8edb4c0435aeb6a4b82e38/pertag/pertag.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/3f9a58cde9e3aa02991b3e5a22d371b153cb1459/pertag/pertag.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Guido Cella](https://github.com/guidocella) diff --git a/dwl-bak/dwl-patches/patches/pertag/pertag.patch b/dwl-bak/dwl-patches/patches/pertag/pertag.patch new file mode 100644 index 0000000..971732a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/pertag/pertag.patch @@ -0,0 +1,170 @@ +From d3b551ffe3ec85e16341962e322150b81af6722f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 31 Jul 2024 08:27:26 -0500 +Subject: [PATCH] makes layout, mwfact and nmaster individual for every tag + +inspiration: https://github.com/djpohly/dwl/wiki/pertag +--- + dwl.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 70 insertions(+), 5 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 145fd01..2f364bc 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -102,6 +102,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct Pertag Pertag; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -199,6 +200,7 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ Pertag *pertag; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -427,6 +429,14 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++struct Pertag { ++ unsigned int curtag, prevtag; /* current and previous tag */ ++ int nmasters[TAGCOUNT + 1]; /* number of windows in master area */ ++ float mfacts[TAGCOUNT + 1]; /* mfacts per tag */ ++ unsigned int sellts[TAGCOUNT + 1]; /* selected layouts */ ++ const Layout *ltidxs[TAGCOUNT + 1][2]; /* matrix of tags and layouts indexes */ ++}; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -712,6 +722,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ free(m->pertag); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -983,6 +994,18 @@ createmon(struct wl_listener *listener, void *data) + wl_list_insert(&mons, &m->link); + printstatus(); + ++ m->pertag = calloc(1, sizeof(Pertag)); ++ m->pertag->curtag = m->pertag->prevtag = 1; ++ ++ for (i = 0; i <= TAGCOUNT; i++) { ++ m->pertag->nmasters[i] = m->nmaster; ++ m->pertag->mfacts[i] = m->mfact; ++ ++ m->pertag->ltidxs[i][0] = m->lt[0]; ++ m->pertag->ltidxs[i][1] = m->lt[1]; ++ m->pertag->sellts[i] = m->sellt; ++ } ++ + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +@@ -1472,7 +1495,7 @@ incnmaster(const Arg *arg) + { + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); + } + +@@ -2305,9 +2328,9 @@ setlayout(const Arg *arg) + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +- selmon->sellt ^= 1; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) +- selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2324,7 +2347,7 @@ setmfact(const Arg *arg) + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; +- selmon->mfact = f; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); + } + +@@ -2701,9 +2724,29 @@ void + toggleview(const Arg *arg) + { + uint32_t newtagset; ++ size_t i; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ if (newtagset == (uint32_t)~0) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = 0; ++ } ++ ++ /* test if the user did not select the same tag */ ++ if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ for (i = 0; !(newtagset & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ ++ /* apply settings for this view */ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +@@ -2892,11 +2935,33 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { ++ size_t i, tmptag; ++ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ +- if (arg->ui & TAGMASK) ++ if (arg->ui & ~0) { + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ ++ if (arg->ui == TAGMASK) ++ selmon->pertag->curtag = 0; ++ else { ++ for (i = 0; !(arg->ui & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ } else { ++ tmptag = selmon->pertag->prevtag; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = tmptag; ++ } ++ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/README.md b/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/README.md new file mode 100644 index 0000000..3ca5432 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/README.md @@ -0,0 +1,16 @@ +### Description +Forward the following events to client: +swipe_begin, swipe_update, swipe_end, pinch_begin, pinch_update and pinch_end + +This patch allows you to pinch zoom in Chrome, for example. In combination with the following patches [gestures](https://codeberg.org/dwl/dwl-patches/wiki/gestures) and [shiftview](https://codeberg.org/dwl/dwl-patches/wiki/shiftview), it would allow you to switch workspaces by performing a 3-finger swipe on your touchpad. + + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/pointer-gestures-unstable-v1) +- [2024-07-12](https://codeberg.org/dwl/dwl-patches/raw/commit/05dbce217b676e989b0fc9e0eecf83b386ac9e07/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/2322f3efeae8da44227e0acc760ffd3dea153716/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/c676de59d51e613bd52ac46c77a24b1cac9a61a1/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/fc4146f3068dcd46035a2a11fe9d6109a97ae6d6/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch b/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch new file mode 100644 index 0000000..f76648a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch @@ -0,0 +1,186 @@ +From be7e98d28fc59aab67026e7d5efdcaeb26029713 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 12 Jul 2024 11:30:17 -0500 +Subject: [PATCH] implement pointer-gestures-unstable-v1 + +--- + dwl.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 136 insertions(+) + +diff --git a/dwl.c b/dwl.c +index dc0437e..e5805b1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -250,6 +251,14 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static void swipe_begin(struct wl_listener *listener, void *data); ++static void swipe_update(struct wl_listener *listener, void *data); ++static void swipe_end(struct wl_listener *listener, void *data); ++static void pinch_begin(struct wl_listener *listener, void *data); ++static void pinch_update(struct wl_listener *listener, void *data); ++static void pinch_end(struct wl_listener *listener, void *data); ++static void hold_begin(struct wl_listener *listener, void *data); ++static void hold_end(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); +@@ -383,6 +392,7 @@ static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; + static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; + static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; + static struct wlr_output_power_manager_v1 *power_mgr; ++static struct wlr_pointer_gestures_v1 *pointer_gestures; + + static struct wlr_pointer_constraints_v1 *pointer_constraints; + static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +@@ -644,6 +654,122 @@ buttonpress(struct wl_listener *listener, void *data) + event->time_msec, event->button, event->state); + } + ++void ++swipe_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_begin_event *event = data; ++ ++ // Forward swipe begin event to client ++ wlr_pointer_gestures_v1_send_swipe_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++swipe_update(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_update_event *event = data; ++ ++ // Forward swipe update event to client ++ wlr_pointer_gestures_v1_send_swipe_update( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->dx, ++ event->dy ++ ); ++} ++ ++void ++swipe_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_end_event *event = data; ++ ++ // Forward swipe end event to client ++ wlr_pointer_gestures_v1_send_swipe_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ ++void ++pinch_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_begin_event *event = data; ++ ++ // Forward pinch begin event to client ++ wlr_pointer_gestures_v1_send_pinch_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++pinch_update(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_update_event *event = data; ++ ++ // Forward pinch update event to client ++ wlr_pointer_gestures_v1_send_pinch_update( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->dx, ++ event->dy, ++ event->scale, ++ event->rotation ++ ); ++} ++ ++void ++pinch_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_end_event *event = data; ++ ++ // Forward pinch end event to client ++ wlr_pointer_gestures_v1_send_pinch_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ ++void ++hold_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_hold_begin_event *event = data; ++ ++ // Forward hold begin event to client ++ wlr_pointer_gestures_v1_send_hold_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++hold_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_hold_end_event *event = data; ++ ++ // Forward hold end event to client ++ wlr_pointer_gestures_v1_send_hold_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ + void + chvt(const Arg *arg) + { +@@ -2556,6 +2682,16 @@ setup(void) + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + ++ pointer_gestures = wlr_pointer_gestures_v1_create(dpy); ++ LISTEN_STATIC(&cursor->events.swipe_begin, swipe_begin); ++ LISTEN_STATIC(&cursor->events.swipe_update, swipe_update); ++ LISTEN_STATIC(&cursor->events.swipe_end, swipe_end); ++ LISTEN_STATIC(&cursor->events.pinch_begin, pinch_begin); ++ LISTEN_STATIC(&cursor->events.pinch_update, pinch_update); ++ LISTEN_STATIC(&cursor->events.pinch_end, pinch_end); ++ LISTEN_STATIC(&cursor->events.hold_begin, hold_begin); ++ LISTEN_STATIC(&cursor->events.hold_end, hold_end); ++ + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +-- +2.45.1 diff --git a/dwl-bak/dwl-patches/patches/primaryselection/README.md b/dwl-bak/dwl-patches/patches/primaryselection/README.md new file mode 100644 index 0000000..1154452 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/primaryselection/README.md @@ -0,0 +1,10 @@ +### Description +Adds a config option to disable/enable primary selection (middle-click paste). + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_primaryselection) +- [2024-04-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/primaryselection/primaryselection.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) +- [Palanix](https://github.com/PalanixYT) - Previous Primary-Selection patch diff --git a/dwl-bak/dwl-patches/patches/primaryselection/primaryselection.patch b/dwl-bak/dwl-patches/patches/primaryselection/primaryselection.patch new file mode 100644 index 0000000..ab6bb64 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/primaryselection/primaryselection.patch @@ -0,0 +1,50 @@ +From 4fc77fde2f6015564544e029f9905fc1678fcb59 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 6 Apr 2024 14:19:44 +0100 +Subject: [PATCH] primaryselection - disable/enable primary selection + +* Just simply adds a config to disable/enable primary selection +--- + config.def.h | 1 + + dwl.c | 6 ++++-- + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..057e1c3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -66,6 +66,7 @@ static const int natural_scrolling = 0; + static const int disable_while_typing = 1; + static const int left_handed = 0; + static const int middle_button_emulation = 0; ++static const int enable_primary_selection = 0; + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +diff --git a/dwl.c b/dwl.c +index bf763df..7e8d8f2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2339,7 +2339,8 @@ setup(void) + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); +- wlr_primary_selection_v1_device_manager_create(dpy); ++ if (enable_primary_selection) ++ wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); +@@ -2449,7 +2450,8 @@ setup(void) + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +- LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); ++ if (enable_primary_selection) ++ LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/push/README.md b/dwl-bak/dwl-patches/patches/push/README.md new file mode 100644 index 0000000..5582bfd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/push/README.md @@ -0,0 +1,11 @@ +### Description +Adds functions `pushup` and `pushdown` to move windows within the tiling order. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/push) +- [2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/push/push.patch) +- [push-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/push/push-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Devin J. Pohly](https://github.com/djpohly) diff --git a/dwl-bak/dwl-patches/patches/push/push-0.7.patch b/dwl-bak/dwl-patches/patches/push/push-0.7.patch new file mode 100644 index 0000000..4afad97 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/push/push-0.7.patch @@ -0,0 +1,127 @@ +From 01290daca2b01131c5c022389afd0b593b4707eb Mon Sep 17 00:00:00 2001 +From: "Devin J. Pohly" +Date: Thu, 4 Mar 2021 00:45:50 -0600 +Subject: [PATCH] port dwm "push" patch to dwl +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + Makefile | 2 +- + dwl.c | 2 ++ + push.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + push.h | 4 ++++ + 4 files changed, 70 insertions(+), 1 deletion(-) + create mode 100644 push.c + create mode 100644 push.h + +diff --git a/Makefile b/Makefile +index 3358bae9..87bf3160 100644 +--- a/Makefile ++++ b/Makefile +@@ -19,7 +19,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c client.h config.h config.mk push.h cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +diff --git a/dwl.c b/dwl.c +index a2711f67..c3d78aa3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -427,7 +427,9 @@ static xcb_atom_t netatom[NetLast]; + #endif + + /* configuration, allows nested code to access above variables */ ++#include "push.h" + #include "config.h" ++#include "push.c" + + /* attempt to encapsulate suck into one file */ + #include "client.h" +diff --git a/push.c b/push.c +new file mode 100644 +index 00000000..323c317e +--- /dev/null ++++ b/push.c +@@ -0,0 +1,63 @@ ++static Client * ++nexttiled(Client *sel) { ++ Client *c; ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static Client * ++prevtiled(Client *sel) { ++ Client *c; ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static void ++pushup(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = prevtiled(sel))) { ++ /* attach before c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(c->link.prev, &sel->link); ++ } else { ++ /* move to the end */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(clients.prev, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++static void ++pushdown(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = nexttiled(sel))) { ++ /* attach after c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ } else { ++ /* move to the front */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&clients, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} +diff --git a/push.h b/push.h +new file mode 100644 +index 00000000..59c0f80e +--- /dev/null ++++ b/push.h +@@ -0,0 +1,4 @@ ++static Client *nexttiled(Client *sel); ++static Client *prevtiled(Client *sel); ++static void pushdown(const Arg *arg); ++static void pushup(const Arg *arg); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/push/push.patch b/dwl-bak/dwl-patches/patches/push/push.patch new file mode 100644 index 0000000..5f6cba9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/push/push.patch @@ -0,0 +1,127 @@ +From 06d86c26da60e4196ec3c5228b04c66ac042a1f8 Mon Sep 17 00:00:00 2001 +From: "Devin J. Pohly" +Date: Thu, 4 Mar 2021 00:45:50 -0600 +Subject: [PATCH] port dwm "push" patch to dwl +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + Makefile | 2 +- + dwl.c | 2 ++ + push.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + push.h | 4 ++++ + 4 files changed, 70 insertions(+), 1 deletion(-) + create mode 100644 push.c + create mode 100644 push.h + +diff --git a/Makefile b/Makefile +index 578194f2..f919a61e 100644 +--- a/Makefile ++++ b/Makefile +@@ -19,7 +19,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c client.h config.h config.mk push.h cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +diff --git a/dwl.c b/dwl.c +index ad21e1ba..69753921 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -450,7 +450,9 @@ static struct wlr_xwayland *xwayland; + #endif + + /* configuration, allows nested code to access above variables */ ++#include "push.h" + #include "config.h" ++#include "push.c" + + /* attempt to encapsulate suck into one file */ + #include "client.h" +diff --git a/push.c b/push.c +new file mode 100644 +index 00000000..323c317e +--- /dev/null ++++ b/push.c +@@ -0,0 +1,63 @@ ++static Client * ++nexttiled(Client *sel) { ++ Client *c; ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static Client * ++prevtiled(Client *sel) { ++ Client *c; ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static void ++pushup(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = prevtiled(sel))) { ++ /* attach before c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(c->link.prev, &sel->link); ++ } else { ++ /* move to the end */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(clients.prev, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++static void ++pushdown(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = nexttiled(sel))) { ++ /* attach after c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ } else { ++ /* move to the front */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&clients, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} +diff --git a/push.h b/push.h +new file mode 100644 +index 00000000..59c0f80e +--- /dev/null ++++ b/push.h +@@ -0,0 +1,4 @@ ++static Client *nexttiled(Client *sel); ++static Client *prevtiled(Client *sel); ++static void pushdown(const Arg *arg); ++static void pushup(const Arg *arg); +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/regexrules/README.md b/dwl-bak/dwl-patches/patches/regexrules/README.md new file mode 100644 index 0000000..8c014b2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/regexrules/README.md @@ -0,0 +1,19 @@ +### Description +Allows the use of regular expressions for window rules "app_id" and "title" + +```c +static const Rule rules[] = { + // ... + { "kitty-htop", NULL, 1 << 8, 0, -1 }, + { "^kitty$", NULL, 0, 0, -1 }, + // ... +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/regexrules) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/2a6560c167e5c9afc5598ac5431d23d90de8846c/regexrules/regexrules.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/98cba933c9f4099202e54f39acbf17e05bde828a/regexrules/regexrules.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/regexrules/regexrules.patch b/dwl-bak/dwl-patches/patches/regexrules/regexrules.patch new file mode 100644 index 0000000..f7207d3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/regexrules/regexrules.patch @@ -0,0 +1,76 @@ +From 7fed9449575b1e4f58d519d2f87b7e66e2056125 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 11 Apr 2024 12:45:47 -0500 +Subject: [PATCH] implement regex support in rules for app_id and title Enables + the use of regular expressions for window rules "app_id" and "title" + +--- + config.def.h | 1 + + dwl.c | 19 +++++++++++++++++-- + 2 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..89f5b60 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -25,6 +25,7 @@ static const Rule rules[] = { + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "^kitty_EXAMPLE$", NULL, 0, 0, -1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index bf763df..fc185af 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -347,6 +348,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static int regex_match(const char *pattern, const char *str); + + /* variables */ + static const char broken[] = "broken"; +@@ -459,8 +461,8 @@ applyrules(Client *c) + title = broken; + + for (r = rules; r < END(rules); r++) { +- if ((!r->title || strstr(title, r->title)) +- && (!r->id || strstr(appid, r->id))) { ++ if ((!r->title || regex_match(r->title, title)) ++ && (!r->id || regex_match(r->id, appid))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; +@@ -2929,6 +2931,19 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++int ++regex_match(const char *pattern, const char *str) { ++ regex_t regex; ++ int reti; ++ if (regcomp(®ex, pattern, REG_EXTENDED) != 0) ++ return 0; ++ reti = regexec(®ex, str, (size_t)0, NULL, 0); ++ regfree(®ex); ++ if (reti == 0) ++ return 1; ++ return 0; ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.2 diff --git a/dwl-bak/dwl-patches/patches/regions/README.md b/dwl-bak/dwl-patches/patches/regions/README.md new file mode 100644 index 0000000..8b5679a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/regions/README.md @@ -0,0 +1,11 @@ +### Description +This patch will allow for a program to be used and have the current window regions on all monitors to be passed to the program as standard input. + +example is `grim -g "$(slurp)"` + +### Download +- [git branch](https://codeberg.org/sewn/dwl/src/branch/regions) +- [2024-02-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/regions/regions.patch) + +### Authors +- [sewn](https://github.com/apprehensions) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/regions/regions.patch b/dwl-bak/dwl-patches/patches/regions/regions.patch new file mode 100644 index 0000000..207c0bd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/regions/regions.patch @@ -0,0 +1,71 @@ +From 9991f8bbf2e379dfca8eb356c03d3d20085255a8 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sun, 23 Jul 2023 08:13:52 +0300 +Subject: [PATCH] pass window regions to given program as stdin + +--- + config.def.h | 1 + + dwl.c | 28 ++++++++++++++++++++++++++++ + 2 files changed, 29 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..79d0236 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -122,6 +122,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_r, regions, SHCMD("grim -g \"$(slurp)\"") }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index fa76db2..9588e36 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -334,6 +334,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void regions(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -2823,6 +2824,33 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++regions(const Arg *arg) ++{ ++ int pipefd[2]; ++ Client *c; ++ Monitor *m; ++ ++ if (pipe(pipefd) == -1) ++ return; ++ if (fork() == 0) { ++ close(pipefd[1]); ++ dup2(pipefd[0], STDIN_FILENO); ++ close(pipefd[0]); ++ setsid(); ++ execvp(((char **)arg->v)[0], (char **)arg->v); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[0]); ++ } ++ ++ close(pipefd[0]); ++ wl_list_for_each(m, &mons, link) ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m)) ++ dprintf(pipefd[1], "%d,%d %dx%d\n", ++ c->geom.x, c->geom.y, c->geom.width, c->geom.height); ++ close(pipefd[1]); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.1 + diff --git a/dwl-bak/dwl-patches/patches/relative-mouse-resize/README.md b/dwl-bak/dwl-patches/patches/relative-mouse-resize/README.md new file mode 100644 index 0000000..ee462ce --- /dev/null +++ b/dwl-bak/dwl-patches/patches/relative-mouse-resize/README.md @@ -0,0 +1,13 @@ +### Description +When resizing windows, the mouse will jump and resize the window in the quadrant that the resize starts at. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/relative-mouse-resize) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/0bd725d0786248e1dfedbe6aa7453edfe736de43/patches/relative-mouse-resize/relative-mouse-resize.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/655fd2916c1bcaa022ce6dcdfb370051cf64df66/relative-mouse-resize/relative-mouse-resize.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/b828e21717fa584affeb3245359c3ab615759fa4/relative-mouse-resize/relative-mouse-resize.patch) +- [v0.4](https://codeberg.org/schance995/dwl/commit/c9a0c55daeb5c75cc0defa9b82f82eccd13f06c7.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [schance995](https://codeberg.org/schance995) diff --git a/dwl-bak/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch b/dwl-bak/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch new file mode 100644 index 0000000..e1dfc57 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch @@ -0,0 +1,102 @@ +From 5c75c67fe49e5ab89e4a61dfb2fe74c768477b90 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:13:53 -0500 +Subject: [PATCH] implement relative-muse-resize + +--- + dwl.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- + 1 file changed, 59 insertions(+), 7 deletions(-) + +diff --git a/dwl.c b/dwl.c +index dc0437e..ebf9ef1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -401,7 +401,8 @@ static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; + static Client *grabc; +-static int grabcx, grabcy; /* client-relative */ ++static Client initial_grabc; ++static int grabcx, grabcy, grabx, graby, grabcenterx, grabcentery; /* client-relative */ + + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; +@@ -1821,8 +1822,27 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ if (grabcenterx < grabx) { ++ if (grabcentery < graby) { ++ /* bottom-right */ ++ resize(grabc, (struct wlr_box){.x = initial_grabc.geom.x, .y = initial_grabc.geom.y, ++ .width = (int)round(cursor->x) - initial_grabc.geom.x, .height = (int)round(cursor->y) - initial_grabc.geom.y}, 1); ++ } else { ++ /* top-right */ ++ resize(grabc, (struct wlr_box){.x = initial_grabc.geom.x, .y = (int)round(cursor->y), ++ .width = (int)round(cursor->x) - initial_grabc.geom.x, .height = initial_grabc.geom.y + initial_grabc.geom.height - (int)round(cursor->y)}, 1); ++ } ++ } else { ++ if (grabcentery < graby) { ++ /* bottom-left */ ++ resize(grabc, (struct wlr_box){.x = (int)round(cursor->x), .y = initial_grabc.geom.y, ++ .width = initial_grabc.geom.x + initial_grabc.geom.width - (int)round(cursor->x), .height = (int)round(cursor->y) - initial_grabc.geom.y}, 1); ++ } else { ++ /* top-left */ ++ resize(grabc, (struct wlr_box){.x = (int)round(cursor->x), .y = (int)round(cursor->y), ++ .width = initial_grabc.geom.x + initial_grabc.geom.width - (int)round(cursor->x), .height = initial_grabc.geom.y + initial_grabc.geom.height - (int)round(cursor->y)}, 1); ++ } ++ } + return; + } + +@@ -1870,10 +1890,42 @@ moveresize(const Arg *arg) + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ initial_grabc = *grabc; ++ grabx = (int)round(cursor->x); ++ graby = (int)round(cursor->y); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ grabcenterx = grabc->geom.width / 2 + grabc->geom.x; ++ grabcentery = grabc->geom.height / 2 + grabc->geom.y; ++ if (grabcenterx < grabx) { ++ if (grabcentery < graby) { ++ /* bottom-right */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ } else { ++ /* top-right */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "ne-resize"); ++ } ++ } else { ++ if (grabcentery < graby) { ++ /* bottom-left */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "sw-resize"); ++ } else { ++ /* top-left */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x, ++ grabc->geom.y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "nw-resize"); ++ } ++ } + break; + } + } +-- +2.45.1 \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/restore-monitor/README.md b/dwl-bak/dwl-patches/patches/restore-monitor/README.md new file mode 100644 index 0000000..8380967 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/restore-monitor/README.md @@ -0,0 +1,8 @@ +### Description +Moves clients to their old output when it is reattached. + +### Download +- [git branch](https://codeberg.org/eyusupov/dwl/src/branch/restore-monitor) +- [2024-04-07](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/restore-monitor/restore-monitor.patch) +### Authors +- [eyusupov](https://codeberg.org/eyusupov) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/restore-monitor/restore-monitor.patch b/dwl-bak/dwl-patches/patches/restore-monitor/restore-monitor.patch new file mode 100644 index 0000000..0d6da98 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/restore-monitor/restore-monitor.patch @@ -0,0 +1,82 @@ +From e42ca1c539437d3098d80983cfe2ad6f938d7a08 Mon Sep 17 00:00:00 2001 +From: Eldar Yusupov +Date: Sun, 17 Mar 2024 19:12:29 +0300 +Subject: [PATCH] Restore correct montior for client when it is reattached + +--- + dwl.c | 24 ++++++++++++++++++++++-- + 1 file changed, 22 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index bf763df..d8d8139 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -107,6 +107,7 @@ typedef struct { + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; ++ char *output; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; +@@ -869,6 +870,7 @@ createmon(struct wl_listener *listener, void *data) + size_t i; + struct wlr_output_state state; + Monitor *m; ++ Client *c; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; +@@ -938,6 +940,13 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); ++ ++ wl_list_for_each(c, &clients, link) { ++ if (strcmp(wlr_output->name, c->output) == 0) { ++ c->mon = m; ++ } ++ } ++ updatemons(NULL, NULL); + } + + void +@@ -1186,6 +1195,7 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } ++ free(c->output); + free(c); + } + +@@ -1618,6 +1628,10 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ c->output = strdup(c->mon->wlr_output->name); ++ if (c->output == NULL) { ++ die("oom"); ++ } + printstatus(); + + unset_fullscreen: +@@ -2565,8 +2579,14 @@ void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) +- setmon(sel, dirtomon(arg->i), 0); ++ if (!sel) ++ return; ++ setmon(sel, dirtomon(arg->i), 0); ++ free(sel->output); ++ sel->output = strdup(sel->mon->wlr_output->name); ++ if (sel->output == NULL) { ++ die("oom"); ++ } + } + + void +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/right/README.md b/dwl-bak/dwl-patches/patches/right/README.md new file mode 100644 index 0000000..7708ded --- /dev/null +++ b/dwl-bak/dwl-patches/patches/right/README.md @@ -0,0 +1,8 @@ +### Description +Put newly connected monitors on the right, like X does. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/right/right.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl-bak/dwl-patches/patches/right/right.patch b/dwl-bak/dwl-patches/patches/right/right.patch new file mode 100644 index 0000000..baf8eeb --- /dev/null +++ b/dwl-bak/dwl-patches/patches/right/right.patch @@ -0,0 +1,50 @@ +From 9d4b0ebb95381525351b05aad6a98f1747ae96e2 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Thu, 21 Jul 2022 21:14:14 +0300 +Subject: [PATCH] extend the display to the right + +--- + dwl.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index d48bf40..fb795e1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -923,7 +923,8 @@ createmon(struct wl_listener *listener, void *data) + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +- Monitor *m; ++ Monitor *om, *m; ++ int max_x = 0, max_x_y = 0, width, height; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; +@@ -967,6 +968,14 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ wl_list_for_each(om, &mons, link) { ++ wlr_output_effective_resolution(om->wlr_output, &width, &height); ++ if (om->m.x + width > max_x) { ++ max_x = om->m.x + width; ++ max_x_y = om->m.y; ++ } ++ } ++ + wl_list_insert(&mons, &m->link); + printstatus(); + +@@ -990,7 +999,7 @@ createmon(struct wl_listener *listener, void *data) + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) +- wlr_output_layout_add_auto(output_layout, wlr_output); ++ wlr_output_layout_add(output_layout, wlr_output, max_x, max_x_y); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); + } +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/rlimit_max/README.md b/dwl-bak/dwl-patches/patches/rlimit_max/README.md new file mode 100644 index 0000000..2bf7355 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rlimit_max/README.md @@ -0,0 +1,11 @@ +### Description +Sets the current maximum open file descriptors to the maximum available limit. + +This patch is useful - and solves issue [#628](https://codeberg.org/dwl/dwl/issues/628) for running heavy Xwayland applications on systems that do not provide limits out of the box. + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/rlimit_max/rlimit_max.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl-bak/dwl-patches/patches/rlimit_max/rlimit_max.patch b/dwl-bak/dwl-patches/patches/rlimit_max/rlimit_max.patch new file mode 100644 index 0000000..81111b3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rlimit_max/rlimit_max.patch @@ -0,0 +1,91 @@ +From d25a8222651671613322677d17b2f987135e02cd Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sat, 24 Aug 2024 19:26:26 +0300 +Subject: [PATCH] set max open file descriptors to available max + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f6..163ebdd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -316,6 +317,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); ++static void restorerlimit(void); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -358,6 +360,7 @@ static void zoom(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; ++static struct rlimit og_rlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2096,6 +2099,15 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++restorerlimit(void) ++{ ++ if (og_rlimit.rlim_cur == 0) ++ return; ++ if (setrlimit(RLIMIT_NOFILE, &og_rlimit) < 0) ++ die("setrlimit:"); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +@@ -2232,6 +2244,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ restorerlimit(); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2429,10 +2442,18 @@ setsel(struct wl_listener *listener, void *data) + void + setup(void) + { ++ struct rlimit new_rlimit; + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + ++ if (getrlimit(RLIMIT_NOFILE, &og_rlimit) < 0) ++ die("getrlimit:"); ++ new_rlimit = og_rlimit; ++ new_rlimit.rlim_cur = new_rlimit.rlim_max; ++ if (setrlimit(RLIMIT_NOFILE, &new_rlimit) < 0) ++ die("setrlimit:"); ++ + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +@@ -2649,6 +2670,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ restorerlimit(); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/rotate-clients/README.md b/dwl-bak/dwl-patches/patches/rotate-clients/README.md new file mode 100644 index 0000000..aaf8f67 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rotate-clients/README.md @@ -0,0 +1,14 @@ +### Description +Rotate clients on current monitor. + +Example Configuration: +```c + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, rotate_clients, {.i = +1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, rotate_clients, {.i = -1} }, +``` + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/rotate-clients/rotate-clients.patch) + +### Authors +- [folk](https://codeberg.org/folk) diff --git a/dwl-bak/dwl-patches/patches/rotate-clients/rotate-clients.patch b/dwl-bak/dwl-patches/patches/rotate-clients/rotate-clients.patch new file mode 100644 index 0000000..8f5f557 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rotate-clients/rotate-clients.patch @@ -0,0 +1,56 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..6029666 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, rotate_clients, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, rotate_clients, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..6dfd87d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -355,6 +355,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void rotate_clients(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -3054,6 +3055,30 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++static void rotate_clients(const Arg *arg) { ++ Monitor* m = selmon; ++ Client *c; ++ Client *first = NULL; ++ Client *last = NULL; ++ ++ if (arg->i == 0) ++ return; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ if (first == NULL) first = c; ++ last = c; ++ } ++ } ++ if (first != last) { ++ struct wl_list *append_to = (arg->i > 0) ? &last->link : first->link.prev; ++ struct wl_list *elem = (arg->i > 0) ? &first->link : &last->link; ++ wl_list_remove(elem); ++ wl_list_insert(append_to, elem); ++ arrange(selmon); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) diff --git a/dwl-bak/dwl-patches/patches/rotatetags/README.md b/dwl-bak/dwl-patches/patches/rotatetags/README.md new file mode 100644 index 0000000..f981847 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rotatetags/README.md @@ -0,0 +1,9 @@ +### Description +This patch provides the ability to rotate the tagset left / right. It implements a new function rotatetags which modifies the current tagset. Same as original dwm patch. Also adds ability to move focused client to left / right adjacent tag by specifying appropriate enum value as argument. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/rotatetags) +- [2024-01-23](https://codeberg.org/korei999/dwl-patches/raw/branch/main/rotatetags/rotatetags.patch) + +### Authors +- [korei999](https://codeberg.org/korei999) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/rotatetags/rotatetags.patch b/dwl-bak/dwl-patches/patches/rotatetags/rotatetags.patch new file mode 100644 index 0000000..94e776f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/rotatetags/rotatetags.patch @@ -0,0 +1,91 @@ +From 308c668010bb7526ea40ad12dbaa1af62f9d7421 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Tue, 23 Jan 2024 12:01:48 +0200 +Subject: [PATCH] add rotatetags patch + +--- + config.def.h | 13 ++++++++++++- + dwl.c | 29 +++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..a80207a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,6 +14,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++enum { ++ VIEW_L = -1, ++ VIEW_R = 1, ++ SHIFT_L = -2, ++ SHIFT_R = 2, ++} RotateTags; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -125,7 +132,11 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_i, incnmaster, {.i = -1} }, ++ { MODKEY, XKB_KEY_a, rotatetags, {.i = VIEW_L} }, ++ { MODKEY, XKB_KEY_d, rotatetags, {.i = VIEW_R} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_a, rotatetags, {.i = SHIFT_L} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_d, rotatetags, {.i = SHIFT_R} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index bf02a6d..e737e34 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -332,6 +332,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void rotatetags(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -2798,6 +2799,34 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++static void ++rotatetags(const Arg *arg) ++{ ++ Arg newarg; ++ int i = arg->i; ++ int nextseltags = 0, curseltags = selmon->tagset[selmon->seltags]; ++ bool shift = false; ++ ++ switch(abs(i)) { ++ default: break; ++ case SHIFT_R: ++ shift = true; ++ break; ++ }; ++ ++ if (i > 0) ++ nextseltags = (curseltags << 1) | (curseltags >> (TAGCOUNT - 1)); ++ else ++ nextseltags = (curseltags >> 1) | (curseltags << (TAGCOUNT - 1)); ++ ++ newarg.i = nextseltags; ++ ++ if (shift) ++ tag(&newarg); ++ else ++ view(&newarg); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/scenefx/README.md b/dwl-bak/dwl-patches/patches/scenefx/README.md new file mode 100644 index 0000000..e1054b3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/scenefx/README.md @@ -0,0 +1,113 @@ +### Description + +Implement https://github.com/wlrfx/scenefx + +```c +/* available options */ + +static const int opacity = 0; /* flag to enable opacity */ +static const float opacity_inactive = 0.5; +static const float opacity_active = 1.0; + +static const int shadow = 1; /* flag to enable shadow */ +static const int shadow_only_floating = 0; /* only apply shadow to floating windows */ +static const float shadow_color[4] = COLOR(0x0000FFff); +static const float shadow_color_focus[4] = COLOR(0xFF0000ff); +static const int shadow_blur_sigma = 20; +static const int shadow_blur_sigma_focus = 40; +static const char *const shadow_ignore_list[] = { NULL }; /* list of app-id to ignore */ + +static const int corner_radius = 8; /* 0 disables corner_radius */ +static const int corner_radius_inner = 8; /* 0 disables corner_radius */ +static const int corner_radius_only_floating = 0; /* only apply corner_radius and corner_radius_inner to floating windows */ + +static const int blur = 1; /* flag to enable blur */ +static const int blur_xray = 0; /* flag to make transparent fs and floating windows display your background */ +static const int blur_ignore_transparent = 1; +static const struct blur_data blur_data = { + .radius = 5, + .num_passes = 3, + .noise = (float)0.02, + .brightness = (float)0.9, + .contrast = (float)0.9, + .saturation = (float)1.1, +}; +``` + +> **NOTE:** If you are using nix with flakes, scenefx has a flake for scenefx https://github.com/wlrfx/scenefx/blob/main/flake.nix + +> **NOTE:** Blur doesn't work on windows with opacity set (opacity_active, opacity_inactive) + +> **NOTE:** In DWL's Makefile `scenefx-0.2` must be placed before `wlroots-0.18`, e.g. `PKGS = scenefx-0.2 wlroots-0.18 wayland-server ...` + +
+Preview +
+
+
+
+ +### Download + +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.8-a/scenefx-b) + +- [0.8-dev](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/scenefx/scenefx.patch) + + **NOTE:** This patch was tested with the `87c0e8b6d5c86557a800445e8e4c322f387fe19c` commit on the `main` branch of `SceneFX`. It supports rounded borders, blur, and shadows. However, it does not add rounded borders or shadows to Xwayland apps. That said, Xwayland apps can have shadows, and they might also support rounded borders, but I was never able to make it work. PRs are welcome! + + **IMPORTANT:** This patch requires you to build DWL with the following dependencies + + - **scenefx** + - libGL + +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/scenefx/scenefx.patch) + + **IMPORTANT:** This patch only works with the `2ec3505248e819191c37cb831197629f373326fb` commit on the `main` branch of `scenefx`, therefore, it does not support **blur**. + + **IMPORTANT:** This patch requires you to build DWL with the following dependencies + + - **scenefx** + - libGL + +
+ Preview +
+  
+  
+  
+
+ +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/6e3a57ffd16dafa31900b7e89e51672bd7bcc1e8/scenefx/scenefx.patch) + + **IMPORTANT:** This patch only works with the `de4ec10e1ff9347b5833f00f8615d760d9378c99` commit on the `wlr_scene_blur` branch of `scenefx`, as it adds support for **blur**. + + **IMPORTANT:** This patch requires you to build DWL with the dependencies of WLROOTS: + + - **scenefx** + - libGL + - libcap + - libinput + - libpng + - libxkbcommon + - mesa + - pixman + - seatd + - vulkan-loader + - wayland + - wayland-protocols + - xorg.libX11 + - xorg.xcbutilerrors + - xorg.xcbutilimage + - xorg.xcbutilrenderutil + - xorg.xcbutilwm + - xwayland (optional) + - ffmpeg + - hwdata + - libliftoff + - libdisplay-info + +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/7a5c3420822074c544fa102e030b7c30aa6b6be8/scenefx/scenefx.patch) + +### Authors + +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/scenefx/scenefx.patch b/dwl-bak/dwl-patches/patches/scenefx/scenefx.patch new file mode 100644 index 0000000..c556cb4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/scenefx/scenefx.patch @@ -0,0 +1,700 @@ +From d209aeaee541a045de9dba2154bbbd5475bba2ec Mon Sep 17 00:00:00 2001 +From: wochap +Date: Sun, 9 Mar 2025 17:26:07 -0500 +Subject: [PATCH] implement scenefx + +--- + Makefile | 2 +- + client.h | 18 +++ + config.def.h | 30 +++- + dwl.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 4 files changed, 457 insertions(+), 7 deletions(-) + +diff --git a/Makefile b/Makefile +index 3358bae..8dc79bd 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = scenefx-0.2 wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/client.h b/client.h +index 42f225f..4e8f016 100644 +--- a/client.h ++++ b/client.h +@@ -147,6 +147,13 @@ client_get_clip(Client *c, struct wlr_box *clip) + return; + #endif + ++ *clip = (struct wlr_box){ ++ .x = c->bw, ++ .y = c->bw, ++ .width = c->geom.width - c->bw * 2, ++ .height = c->geom.height - c->bw * 2, ++ }; ++ + wlr_xdg_surface_get_geometry(c->surface.xdg, &xdg_geom); + clip->x = xdg_geom.x; + clip->y = xdg_geom.y; +@@ -328,6 +335,17 @@ static inline void + client_set_border_color(Client *c, const float color[static 4]) + { + int i; ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius > 0) { ++ return; ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); + } +diff --git a/config.def.h b/config.def.h +index 22d2171..8c50932 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,35 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ ++ ++static const int opacity = 0; /* flag to enable opacity */ ++static const float opacity_inactive = 0.5; ++static const float opacity_active = 1.0; ++ ++static const int shadow = 1; /* flag to enable shadow */ ++static const int shadow_only_floating = 0; /* only apply shadow to floating windows */ ++static const float shadow_color[4] = COLOR(0x0000FFff); ++static const float shadow_color_focus[4] = COLOR(0xFF0000ff); ++static const int shadow_blur_sigma = 20; ++static const int shadow_blur_sigma_focus = 40; ++static const char *const shadow_ignore_list[] = { NULL }; /* list of app-id to ignore */ ++ ++static const int corner_radius = 8; /* 0 disables corner_radius */ ++static const int corner_radius_inner = 9; /* 0 disables corner_radius */ ++static const int corner_radius_only_floating = 0; /* only apply corner_radius and corner_radius_inner to floating windows */ ++ ++static const int blur = 1; /* flag to enable blur */ ++static const int blur_xray = 0; /* flag to make transparent fs and floating windows display your background */ ++static const int blur_ignore_transparent = 1; ++static const struct blur_data blur_data = { ++ .radius = 5, ++ .num_passes = 3, ++ .noise = (float)0.02, ++ .brightness = (float)0.9, ++ .contrast = (float)0.9, ++ .saturation = (float)1.1, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index 5bf995e..655e175 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -10,8 +10,14 @@ + #include + #include + #include ++#include ++#include ++#include ++#include ++#include + #include + #include ++#include + #include + #include + #include +@@ -43,7 +49,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -83,7 +88,7 @@ + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ +-enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { LyrBg, LyrBlur, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -141,6 +146,12 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ ++ float opacity; ++ int corner_radius; ++ struct wlr_scene_shadow *shadow; ++ int has_shadow_enabled; ++ struct wlr_scene_rect *round_border; + } Client; + + typedef struct { +@@ -208,6 +219,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ struct wlr_scene_optimized_blur *blur_layer; + }; + + typedef struct { +@@ -355,6 +367,18 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_blur(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_opacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_corner_radius(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void output_configure_scene(struct wlr_scene_node *node, Client *c); ++static int in_shadow_ignore_list(const char *str); ++static void client_set_shadow_blur_sigma(Client *c, int blur_sigma); ++static void update_client_corner_radius(Client *c); ++static void update_client_shadow_color(Client *c); ++static void update_client_focus_decorations(Client *c, int focused, int urgent); ++static void update_client_blur(Client *c); ++static void update_buffer_corner_radius(Client *c, struct wlr_scene_buffer *buffer); + + /* variables */ + static const char broken[] = "broken"; +@@ -413,6 +437,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static float transparent[4] = {0.1f, 0.1f, 0.1f, 0.0f}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -499,6 +525,10 @@ arrange(Monitor *m) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + ++ if (blur) { ++ wlr_scene_node_set_enabled(&m->blur_layer->node, 1); ++ } ++ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while +@@ -722,6 +752,11 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ ++ if (blur) { ++ wlr_scene_node_destroy(&m->blur_layer->node); ++ } ++ + free(m); + } + +@@ -785,6 +820,18 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data) + } + + arrangelayers(l->mon); ++ ++ if (blur) { ++ // Rerender the optimized blur on change ++ struct wlr_layer_surface_v1 *wlr_layer_surface = l->layer_surface; ++ if (wlr_layer_surface->current.layer == ++ ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ++ wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { ++ if (l->mon) { ++ wlr_scene_optimized_blur_mark_dirty(l->mon->blur_layer); ++ } ++ } ++ } + } + + void +@@ -1036,6 +1083,12 @@ createmon(struct wl_listener *listener, void *data) + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + ++ if (blur) { ++ m->blur_layer = wlr_scene_optimized_blur_create(&scene->tree, 0, 0); ++ wlr_scene_node_reparent(&m->blur_layer->node, layers[LyrBlur]); ++ wlr_scene_node_set_enabled(&m->blur_layer->node, 0); ++ } ++ + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the +@@ -1061,6 +1114,9 @@ createnotify(struct wl_listener *listener, void *data) + c->surface.xdg = toplevel->base; + c->bw = borderpx; + ++ c->opacity = opacity; ++ c->corner_radius = corner_radius; ++ + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); +@@ -1369,8 +1425,11 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ ++ update_client_focus_decorations(c, 1, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1389,6 +1448,8 @@ focusclient(Client *c, int lift) + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); + ++ update_client_focus_decorations(old_c, 0, 0); ++ + client_activate_surface(old, 0); + } + } +@@ -1718,6 +1779,38 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers, c); ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius > 0) { ++ c->round_border = wlr_scene_rect_create(c->scene, 0, 0, c->isurgent ? urgentcolor : bordercolor); ++ c->round_border->node.data = c; ++ /* Lower the border below the XDG scene tree */ ++ wlr_scene_node_lower_to_bottom(&c->round_border->node); ++ ++ /* hide original border */ ++ for (i = 0; i < 4; i++) { ++ wlr_scene_rect_set_color(c->border[i], transparent); ++ } ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (shadow) { ++ c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, c->corner_radius, shadow_blur_sigma, shadow_color); ++ /* Lower the shadow below the border */ ++ wlr_scene_node_lower_to_bottom(&c->shadow->node); ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1739,6 +1832,13 @@ mapnotify(struct wl_listener *listener, void *data) + } + printstatus(); + ++ /* TODO: shouldn't we call iter_xdg_scene_buffers_corner_radius? */ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { +@@ -2113,6 +2213,8 @@ rendermon(struct wl_listener *listener, void *data) + goto skip; + } + ++ output_configure_scene(&m->scene_output->scene->tree.node, NULL); ++ + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite +@@ -2207,6 +2309,21 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ if (corner_radius > 0 && c->round_border) { ++ wlr_scene_node_set_position(&c->round_border->node, 0, 0); ++ wlr_scene_rect_set_size(c->round_border, c->geom.width, c->geom.height); ++ wlr_scene_rect_set_clipped_region(c->round_border, (struct clipped_region) { ++ .corner_radius = c->corner_radius, ++ .corners = CORNER_LOCATION_ALL, ++ .area = { c->bw, c->bw, c->geom.width - c->bw * 2, c->geom.height - c->bw * 2 } ++ }); ++ } ++ ++ if (shadow && c->shadow) { ++ /* TODO: shouldn't we call wlr_scene_shadow_set_blur_sigma? */ ++ client_set_shadow_blur_sigma(c, (int)round(c->shadow->blur_sigma)); ++ } + } + + void +@@ -2307,6 +2424,13 @@ setfloating(Client *c, int floating) + { + Client *p = client_get_parent(c); + c->isfloating = floating; ++ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; +@@ -2336,6 +2460,13 @@ setfullscreen(Client *c, int fullscreen) + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } ++ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + arrange(c->mon); + printstatus(); + } +@@ -2457,11 +2588,15 @@ setup(void) + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + ++ if (blur) { ++ wlr_scene_set_blur_data(scene, blur_data); ++ } ++ + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ +- if (!(drw = wlr_renderer_autocreate(backend))) ++ if (!(drw = fx_renderer_create(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + +@@ -2870,6 +3005,10 @@ updatemons(struct wl_listener *listener, void *data) + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); ++ ++ if (blur) { ++ wlr_scene_optimized_blur_set_size(m->blur_layer, m->m.width, m->m.height); ++ } + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; +@@ -2940,8 +3079,11 @@ urgent(struct wl_listener *listener, void *data) + c->isurgent = 1; + printstatus(); + +- if (client_surface(c)->mapped) ++ if (client_surface(c)->mapped) { + client_set_border_color(c, urgentcolor); ++ ++ update_client_focus_decorations(c, 1, 1); ++ } + } + + void +@@ -3053,6 +3195,268 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ update_buffer_corner_radius(c, buffer); ++ ++ if (blur) { ++ int blur_optimized = !c->isfloating || blur_xray; ++ wlr_scene_buffer_set_backdrop_blur(buffer, 1); ++ wlr_scene_buffer_set_backdrop_blur_optimized(buffer, blur_optimized); ++ wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, blur_ignore_transparent); ++ } ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_blur(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ if (blur) { ++ int blur_optimized = !c->isfloating || blur_xray; ++ wlr_scene_buffer_set_backdrop_blur_optimized(buffer, blur_optimized); ++ } ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_opacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_corner_radius(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ update_buffer_corner_radius(c, buffer); ++ } ++} ++ ++void ++output_configure_scene(struct wlr_scene_node *node, Client *c) ++{ ++ Client *_c; ++ struct wlr_xdg_surface *xdg_surface; ++ struct wlr_scene_node *_node; ++ ++ if (!node->enabled) { ++ return; ++ } ++ ++ _c = node->data; ++ if (_c) { ++ c = _c; ++ } ++ ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); ++ ++ struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ update_buffer_corner_radius(c, buffer); ++ } ++ } ++ } else if (node->type == WLR_SCENE_NODE_TREE) { ++ struct wlr_scene_tree *tree = wl_container_of(node, tree, node); ++ wl_list_for_each(_node, &tree->children, link) { ++ output_configure_scene(_node, c); ++ } ++ } ++} ++ ++int ++in_shadow_ignore_list(const char *str) ++{ ++ for (int i = 0; shadow_ignore_list[i] != NULL; i++) { ++ if (strcmp(shadow_ignore_list[i], str) == 0) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++void ++client_set_shadow_blur_sigma(Client *c, int blur_sigma) ++{ ++ wlr_scene_shadow_set_blur_sigma(c->shadow, blur_sigma); ++ wlr_scene_node_set_position(&c->shadow->node, -blur_sigma, -blur_sigma); ++ wlr_scene_shadow_set_size(c->shadow, c->geom.width + blur_sigma * 2, c->geom.height + blur_sigma * 2); ++ wlr_scene_shadow_set_clipped_region(c->shadow, (struct clipped_region) { ++ .corner_radius = c->corner_radius + c->bw, ++ .corners = CORNER_LOCATION_ALL, ++ .area = { blur_sigma, blur_sigma, c->geom.width, c->geom.height } ++ }); ++} ++ ++void ++update_client_corner_radius(Client *c) ++{ ++ if (corner_radius && c->round_border) { ++ int radius = c->corner_radius + c->bw; ++ if ((corner_radius_only_floating && !c->isfloating) || c->isfullscreen) { ++ radius = 0; ++ } ++ wlr_scene_rect_set_corner_radius(c->round_border, radius, CORNER_LOCATION_ALL); ++ } ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius_inner > 0 && c->scene) { ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_corner_radius, c); ++ } ++#ifdef XWAYLAND ++ } ++#endif ++} ++ ++void ++update_client_blur(Client *c) ++{ ++ if (!blur) { ++ return; ++ } ++ ++ if (c->scene) { ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_blur, c); ++ } ++} ++ ++void ++update_buffer_corner_radius(Client *c, struct wlr_scene_buffer *buffer) ++{ ++ int radius; ++ ++#ifdef XWAYLAND ++ if (client_is_x11(c)) { ++ return; ++ } ++#endif ++ ++ if (!corner_radius_inner) { ++ return; ++ } ++ ++ radius = corner_radius_inner; ++ if ((corner_radius_only_floating && !c->isfloating) || c->isfullscreen) { ++ radius = 0; ++ } ++ wlr_scene_buffer_set_corner_radius(buffer, radius, CORNER_LOCATION_ALL); ++} ++ ++void ++update_client_shadow_color(Client *c) ++{ ++ int has_shadow_enabled = 1; ++ const float *color; ++ ++ if (!shadow || !c->shadow) { ++ return; ++ } ++ ++ color = focustop(c->mon) == c ? shadow_color_focus : shadow_color; ++ ++ if ((shadow_only_floating && !c->isfloating) || ++ in_shadow_ignore_list(client_get_appid(c)) || ++ c->isfullscreen) { ++ color = transparent; ++ has_shadow_enabled = 0; ++ } ++ ++ wlr_scene_shadow_set_color(c->shadow, color); ++ c->has_shadow_enabled = has_shadow_enabled; ++} ++ ++void ++update_client_focus_decorations(Client *c, int focused, int urgent) ++{ ++ if (corner_radius > 0 && c->round_border) { ++ wlr_scene_rect_set_color(c->round_border, urgent ? urgentcolor : (focused ? focuscolor : bordercolor)); ++ } ++ if (shadow && c->shadow) { ++ client_set_shadow_blur_sigma(c, (int)round(focused ? shadow_blur_sigma_focus : shadow_blur_sigma)); ++ if (c->has_shadow_enabled) { ++ wlr_scene_shadow_set_color(c->shadow, focused ? shadow_color_focus : shadow_color); ++ } ++ } ++ if (opacity) { ++ c->opacity = focused ? opacity_active : opacity_inactive; ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_opacity, c); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.47.2 + + diff --git a/dwl-bak/dwl-patches/patches/setrule/README.md b/dwl-bak/dwl-patches/patches/setrule/README.md new file mode 100644 index 0000000..bdb7408 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/setrule/README.md @@ -0,0 +1,46 @@ +### Description + +This patch adds an ability to add or change client rules at runtime. + +Sometimes it happens that some client rule I've set is not relevant in the +current circumstance and adds major disturbance and annoyance to my work +(e.g., `isfloating` is set or unset and I want the opposite). Changing the rule +is not an option because it will require recompilation and restarting dwl, which +is even worse. Having an option of always being able to change a rule solves +this issue. + +The patch only adds one keybinding (`Alt+Shift+R`) to toggle `isfloating` +option. Upon pressing it, dwl will try to find a matching rule for the focused +client and change its `isfloating` setting. If there's no such a rule, a new +rule will be added. The new rule will inherit an appid of the focused client. + +It's very easy to add support for more rule options from other patches as well. +You just need to define a new function similar to `setruleisfloating` and add a +new keybinding to `config.h`. For example, this is a function I created for my +build to toggle `noswallow` option from the [swallow][swallow] patch: + +```c +void +setrulenoswallow(const Arg *arg) +{ + Rule *r = getrule(focustop(selmon)); + if (!r) + return; + r->noswallow = !r->noswallow; +} +``` + +You can also try [menurule][menurule] patch if you want to be able to edit rules +with dmenu. + +[swallow]: /dwl/dwl-patches/src/branch/main/patches/swallow +[menurule]: /dwl/dwl-patches/src/branch/main/patches/menurule + +### Download + +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/setrule/setrule.patch) +- [2025-02-14 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/268bee3cee239e5bd52cceed88a52bfc21143cc3/patches/setrule/setrule.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/setrule/setrule.patch b/dwl-bak/dwl-patches/patches/setrule/setrule.patch new file mode 100644 index 0000000..6f9a1a3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/setrule/setrule.patch @@ -0,0 +1,155 @@ +From 3c78308f0d74ac6ef112804333f82c098e33bb40 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 21 Mar 2025 22:20:54 +0100 +Subject: [PATCH] setrule: add/change rules at runtime + +--- + config.def.h | 4 ++++ + dwl.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 70 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5b05e52 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,9 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Max amount of dynamically added rules */ ++#define RULES_MAX 100 ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +@@ -142,6 +145,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index def2562..8beac1f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -290,6 +290,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static Rule *getrule(Client *c); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -331,6 +332,7 @@ static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); ++static void setruleisfloating(const Arg *arg); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); +@@ -413,6 +415,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static Rule *drules; ++static size_t druleslen; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -466,7 +471,7 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + +- for (r = rules; r < END(rules); r++) { ++ for (r = drules; r < drules + druleslen; r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; +@@ -1472,6 +1477,53 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++Rule * ++getrule(Client *c) ++{ ++ Rule *r; ++ const Rule *e; ++ const char *appid, *title; ++ ++ if (!c) ++ return NULL; ++ ++ if (!(appid = client_get_appid(c))) ++ appid = broken; ++ if (!(title = client_get_title(c))) ++ title = broken; ++ ++ for (r = drules + druleslen - 1; r >= drules; r--) ++ if ((!r->title || strstr(title, r->title)) ++ && (!r->id || strstr(appid, r->id))) ++ goto found; ++ ++ if (druleslen >= LENGTH(rules) + RULES_MAX) ++ return NULL; /* No free slots left */ ++ ++ r = drules + druleslen++; ++ ++ /* Use [NULL,NULL] as the default rule if exists */ ++ for (e = rules; e < END(rules); e++) ++ if (!e->title && !e->id) { ++ *r = *e; ++ break; ++ } ++ ++ /* No default rule found, set reasoble defaults */ ++ if (e >= END(rules)) { ++ r->monitor = -1; ++ } ++ ++ /* Only set title if appid is unset */ ++ if (appid == broken) ++ r->title = strdup(title); ++ else ++ r->id = strdup(appid); ++ ++found: ++ return r; ++} ++ + void + gpureset(struct wl_listener *listener, void *data) + { +@@ -2417,6 +2469,15 @@ setpsel(struct wl_listener *listener, void *data) + wlr_seat_set_primary_selection(seat, event->source, event->serial); + } + ++void ++setruleisfloating(const Arg *arg) ++{ ++ Rule *r = getrule(focustop(selmon)); ++ if (!r) ++ return; ++ r->isfloating = !r->isfloating; ++} ++ + void + setsel(struct wl_listener *listener, void *data) + { +@@ -2645,6 +2706,10 @@ setup(void) + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } + #endif ++ ++ drules = ecalloc(LENGTH(rules) + RULES_MAX, sizeof(Rule)); ++ memcpy(drules, rules, sizeof(rules)); ++ druleslen = LENGTH(rules); + } + + void +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/setupenv/README.md b/dwl-bak/dwl-patches/patches/setupenv/README.md new file mode 100644 index 0000000..776c328 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/setupenv/README.md @@ -0,0 +1,8 @@ +### Description +Allow configuring environment variables in config.h + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/setupenv) +- [2024-03-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/setupenv/setupenv.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/setupenv/setupenv.patch b/dwl-bak/dwl-patches/patches/setupenv/setupenv.patch new file mode 100644 index 0000000..17d2ad4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/setupenv/setupenv.patch @@ -0,0 +1,54 @@ +From 11ee2fc23ef5728d1e132f338c08a7805c6109b2 Mon Sep 17 00:00:00 2001 +From: choc +Date: Tue, 26 Mar 2024 21:02:16 +0800 +Subject: [PATCH] implement setupenv + +--- + config.def.h | 5 +++++ + dwl.c | 7 +++++++ + 2 files changed, 12 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..b16189a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++static const Env envs[] = { ++ /* variable value */ ++ { "XDG_CURRENT_DESKTOP", "wlroots" }, ++}; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: +diff --git a/dwl.c b/dwl.c +index 5867b0c..b7d522b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -230,6 +230,11 @@ typedef struct { + int monitor; + } Rule; + ++typedef struct { ++ const char *variable; ++ const char *value; ++} Env; ++ + typedef struct { + struct wlr_scene_tree *scene; + +@@ -2066,6 +2071,8 @@ run(char *startup_cmd) + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); ++ for (size_t i = 0; i < LENGTH(envs); i++) ++ setenv(envs[i].variable, envs[i].value, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/shiftview/README.md b/dwl-bak/dwl-patches/patches/shiftview/README.md new file mode 100644 index 0000000..2d25820 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/shiftview/README.md @@ -0,0 +1,9 @@ +### Description +Add keybindings to cycle through tags with visible clients. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/shiftview) +- [2024-01-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shiftview/shiftview.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl-bak/dwl-patches/patches/shiftview/shiftview.patch b/dwl-bak/dwl-patches/patches/shiftview/shiftview.patch new file mode 100644 index 0000000..f556916 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/shiftview/shiftview.patch @@ -0,0 +1,76 @@ +From 40f9140742277d0298988990264f4b6a738f8122 Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Sat, 27 Jan 2024 22:43:29 +0100 +Subject: [PATCH] cycle through tags + +--- + config.def.h | 4 ++++ + shiftview.c | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + create mode 100644 shiftview.c + +diff --git a/config.def.h b/config.def.h +index 9009517..8d77ec0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -117,6 +117,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "bemenu-run", NULL }; + ++#include "shiftview.c" ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ +@@ -130,6 +132,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, ++ { MODKEY, XKB_KEY_a, shiftview, { .i = -1 } }, ++ { MODKEY, XKB_KEY_semicolon, shiftview, { .i = 1 } }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +diff --git a/shiftview.c b/shiftview.c +new file mode 100644 +index 0000000..fa53db0 +--- /dev/null ++++ b/shiftview.c +@@ -0,0 +1,34 @@ ++// "arg->i" stores the number of tags to shift right (positive value) ++// or left (negative value) ++void ++shiftview(const Arg *arg) ++{ ++ Arg a; ++ Client *c; ++ bool visible = false; ++ int i = arg->i; ++ int count = 0; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ ++ do { ++ if (i > 0) // left circular shift ++ nextseltags = (curseltags << i) | (curseltags >> (TAGCOUNT - i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-i) | (curseltags << (TAGCOUNT + i)); ++ ++ // Check if the tag is visible ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == selmon && nextseltags & c->tags) { ++ visible = true; ++ break; ++ } ++ } ++ ++ i += arg->i; ++ } while (!visible && ++count <= TAGCOUNT); ++ ++ if (count <= TAGCOUNT) { ++ a.i = nextseltags; ++ view(&a); ++ } ++} +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/simple_scratchpad/README.md b/dwl-bak/dwl-patches/patches/simple_scratchpad/README.md new file mode 100644 index 0000000..8076d96 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/simple_scratchpad/README.md @@ -0,0 +1,25 @@ +### Description + +# simple_scratchpad — A very simple scratchpad utility. + +Adds functions to add, toggle and remove clients to/from scratchpad client list. +![scratchpad in action](https://github.com/julmajustus/dwl-patches/blob/main/demos/simple_scratchpad_demo.gif?raw=true) + +--- + +1. **addscratchpad** + - Floats client and adds it to scratchpad clients list. + +2. **togglescratchpad** + - Shows or hides the clients in scratchpad client list. + +3. **removescratchpad** + - Removes client from scratchpad client list. + + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/simple_scratchpad) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simple_scratchpad/simple_scratchpad-v0.7.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl-bak/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch b/dwl-bak/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch new file mode 100644 index 0000000..a273559 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch @@ -0,0 +1,211 @@ +From a8d29e03f565b54a68c6c2cb2da103366c627825 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sat, 4 Jan 2025 13:22:12 +0200 +Subject: [PATCH] add simple_scratchpad + +--- + config.def.h | 3 ++ + dwl.c | 34 +++++++++++++++++++++-- + simple_scratchpad.c | 67 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 102 insertions(+), 2 deletions(-) + create mode 100644 simple_scratchpad.c + +diff --git a/config.def.h b/config.def.h +index 22d2171..83f19b3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -136,6 +136,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Z, addscratchpad, {0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, togglescratchpad, {0} }, ++ { MODKEY, XKB_KEY_z, removescratchpad, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..85f4a51 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wl_list link_temp; + } Client; + + typedef struct { +@@ -243,6 +244,7 @@ typedef struct { + } SessionLock; + + /* function declarations */ ++static void addscratchpad(const Arg *arg); + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); + static void arrange(Monitor *m); +@@ -317,6 +319,7 @@ static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); ++static void removescratchpad(const Arg *arg); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +@@ -340,6 +343,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglescratchpad(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +417,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct wl_list scratchpad_clients; ++static int scratchpad_visible = 1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -432,6 +439,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "simple_scratchpad.c" ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1259,10 +1268,20 @@ void + destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ +- Client *c = wl_container_of(listener, c, destroy); ++ Client *sc, *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* Check if destroyed client was part of scratchpad_clients ++ * and clean it from the list if so. */ ++ if (c && wl_list_length(&scratchpad_clients) > 0) { ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -2306,11 +2325,21 @@ setcursorshape(struct wl_listener *listener, void *data) + void + setfloating(Client *c, int floating) + { +- Client *p = client_get_parent(c); ++ Client *sc, *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; ++ /* Check if unfloated client was part of scratchpad_clients ++ * and remove it from scratchpad_clients list if so */ ++ if (!floating && wl_list_length(&scratchpad_clients) > 0) { ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); +@@ -2534,6 +2563,7 @@ setup(void) + */ + wl_list_init(&clients); + wl_list_init(&fstack); ++ wl_list_init(&scratchpad_clients); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); +diff --git a/simple_scratchpad.c b/simple_scratchpad.c +new file mode 100644 +index 0000000..381f4b5 +--- /dev/null ++++ b/simple_scratchpad.c +@@ -0,0 +1,68 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* simple_scratchpad.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/19 19:35:02 by jmakkone #+# #+# */ ++/* Updated: 2025/01/04 13:35:50 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++void ++addscratchpad(const Arg *arg) ++{ ++ Client *cc, *c = focustop(selmon); ++ ++ if (!c) ++ return; ++ /* Check if the added client is already a scratchpad client */ ++ wl_list_for_each(cc, &scratchpad_clients, link_temp) { ++ if (cc == c) ++ return; ++ } ++ if (!c->isfloating) { ++ setfloating(c, 1); ++ } ++ wl_list_insert(&scratchpad_clients, &c->link_temp); ++} ++ ++void ++togglescratchpad(const Arg *arg) ++{ ++ Client *c; ++ Monitor *m = selmon; ++ ++ scratchpad_visible = !scratchpad_visible; ++ if (scratchpad_visible) { ++ wl_list_for_each(c, &scratchpad_clients, link_temp) { ++ c->mon = m; ++ c->tags = m->tagset[m->seltags]; ++ arrange(m); ++ focusclient(c, 1); ++ } ++ } else { ++ wl_list_for_each(c, &scratchpad_clients, link_temp) { ++ c->tags = 0; ++ focusclient(focustop(m), 1); ++ arrange(m); ++ } ++ } ++} ++ ++void ++removescratchpad(const Arg *arg) ++{ ++ Client *sc, *c = focustop(selmon); ++ ++ if (c && wl_list_length(&scratchpad_clients) > 0) { ++ /* Check if c is in scratchpad_clients */ ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } ++} +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/simpleborders/README.md b/dwl-bak/dwl-patches/patches/simpleborders/README.md new file mode 100644 index 0000000..29dcf5e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/simpleborders/README.md @@ -0,0 +1,13 @@ +### Description + +Like smartborders. Don't put borders when there is only one window on the screen. + +The patch for tag v0.7 below appears to apply cleanly to the current HEAD of +upstream/main as at 2024-10-11. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/simpleborders) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simpleborders/simpleborders-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simpleborders/simpleborders-v0.6.patch) +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch b/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch new file mode 100644 index 0000000..a4797bb --- /dev/null +++ b/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch @@ -0,0 +1,73 @@ +From 681e520deaeee460647de36f5312af3cb0c31f4a Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] simpleborders + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 4d19357..900e651 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -245,6 +245,7 @@ static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -286,6 +287,7 @@ static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int needsborder(Client *c); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -739,6 +741,17 @@ commitnotify(struct wl_listener *listener, void *data) + c->resize = 0; + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -1697,6 +1710,14 @@ moveresize(const Arg *arg) + } + } + ++int ++needsborder(Client *c) { ++ return ((countclients(c->mon) > 1 ++ && c->mon->lt[c->mon->sellt]->arrange != monocle) ++ || c->isfloating) ++ && !c->isfullscreen; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -1930,6 +1951,7 @@ resize(Client *c, struct wlr_box geo, int interact) + struct wlr_box clip; + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = needsborder(c) ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch b/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch new file mode 100644 index 0000000..e782d84 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch @@ -0,0 +1,73 @@ +From 09759c3ef75158c366e9fc63814485fbb31a3ccf Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] simpleborders + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f67..415fe1a0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -259,6 +259,7 @@ static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void commitpopup(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -308,6 +309,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int needsborder(Client *c); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -849,6 +851,17 @@ commitpopup(struct wl_listener *listener, void *data) + wl_list_remove(&listener->link); + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -1927,6 +1940,14 @@ moveresize(const Arg *arg) + } + } + ++int ++needsborder(Client *c) { ++ return ((countclients(c->mon) > 1 ++ && c->mon->lt[c->mon->sellt]->arrange != monocle) ++ || c->isfloating) ++ && !c->isfullscreen; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2190,6 +2211,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = needsborder(c) ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/singlemaster/README.md b/dwl-bak/dwl-patches/patches/singlemaster/README.md new file mode 100644 index 0000000..99cb1e6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singlemaster/README.md @@ -0,0 +1,9 @@ +### Description +Restricts layout to only having one client in the master area. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/singlemaster) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singlemaster/singlemaster.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/singlemaster/singlemaster.patch b/dwl-bak/dwl-patches/patches/singlemaster/singlemaster.patch new file mode 100644 index 0000000..ffa6045 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singlemaster/singlemaster.patch @@ -0,0 +1,155 @@ +From 610362d2f38260ad2a06ffbd91d089ba738429e9 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 18 Jul 2024 11:03:46 +1000 +Subject: [PATCH] singlemaster + +--- + config.def.h | 8 +++----- + dwl.c | 49 ++++++++++++++++++++++--------------------------- + 2 files changed, 25 insertions(+), 32 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0ba9ddb8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -43,12 +43,12 @@ static const Layout layouts[] = { + */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ ++ /* name mfact scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { "eDP-1", 0.5f, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.55f, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + }; + + /* keyboard */ +@@ -129,8 +129,6 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd018..6a3c84a7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -70,7 +70,6 @@ + + /* macros */ + #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 LENGTH(X) (sizeof X / sizeof X[0]) +@@ -204,7 +203,6 @@ struct Monitor { + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; +- int nmaster; + char ltsymbol[16]; + int asleep; + }; +@@ -212,7 +210,6 @@ struct Monitor { + typedef struct { + const char *name; + float mfact; +- int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; +@@ -288,7 +285,6 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +-static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -955,7 +951,6 @@ createmon(struct wl_listener *listener, void *data) + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; +- m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); +@@ -1467,15 +1462,6 @@ handlesig(int signo) + } + } + +-void +-incnmaster(const Arg *arg) +-{ +- if (!arg || !selmon) +- return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); +- arrange(selmon); +-} +- + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2635,8 +2621,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; +- int i, n = 0; ++ unsigned int i, n = 0, mw, ty; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2645,21 +2630,31 @@ tile(Monitor *m) + if (n == 0) + return; + +- if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; +- else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = (n == 1) ? m->w.width : (int)roundf(m->w.width * m->mfact); ++ i = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ if (i == 0) { ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x, ++ .y = m->w.y, ++ .width = mw, ++ .height = (m->w.height) ++ }, ++ 0); + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x + mw, ++ .y = m->w.y + ty, ++ .width = m->w.width - mw, ++ .height = (m->w.height - ty) / (n - i) ++ }, ++ 0); + ty += c->geom.height; + } + i++; +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/singletagset-pertag/README.md b/dwl-bak/dwl-patches/patches/singletagset-pertag/README.md new file mode 100644 index 0000000..13ab951 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset-pertag/README.md @@ -0,0 +1,22 @@ +### Description +Pertag keeps layouts, mfact and nmaster per tag +instead of per output. + +This adapted version of pertag contains one version of +the rules per all outputs, instead of one per output. +This makes switching to tags from other monitors keep +the window layout. + +This patch expects [singletagset](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/singletagset) +patch to be already in your tree committed. It applies onto it. + +This patch is incompatible with [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag). + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset-pertag/singletagset-pertag.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset-pertag) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [wochap (maintainer of pertag patch)](https://codeberg.org/wochap) +- [Guido Cella (creator of pertag patch)](https://codeberg.org/guidocella) diff --git a/dwl-bak/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch b/dwl-bak/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch new file mode 100644 index 0000000..5fe4f47 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch @@ -0,0 +1,261 @@ +From 4f6bdd3ea1fcc83abd962e9a2dc7737519164a6b Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 19 Jul 2024 16:25:46 +0200 +Subject: [PATCH] singletagset-pertag + +--- + config.def.h | 12 +++++-- + dwl.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 90 insertions(+), 12 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..7feb04d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -43,12 +43,18 @@ static const Layout layouts[] = { + */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ ++ /* name scale rotate/reflect x y */ + /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { "eDP-1", 2, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 1, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++}; ++ ++static const TagRule tagrules[] = { ++ /* tag mfact nmaster layout */ ++ /* defaults */ ++ { 0, 0.55, 1, &layouts[0] } + }; + + /* keyboard */ +diff --git a/dwl.c b/dwl.c +index 40a6e48..465d9dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -104,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct Pertag Pertag; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -209,6 +210,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ unsigned int pertag[2]; /* the tag used for layout via pertag */ + }; + + typedef struct { +@@ -226,6 +228,13 @@ typedef struct { + struct wl_listener destroy; + } PointerConstraint; + ++typedef struct { ++ unsigned int tag; ++ float mfact; ++ int nmaster; ++ const Layout *lt; ++} TagRule; ++ + typedef struct { + const char *id; + const char *title; +@@ -245,6 +254,7 @@ typedef struct { + + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); ++static void applypertag(Monitor *m); + static void applyrules(Client *c); + static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -293,6 +303,7 @@ 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 unsigned int getpertagtag(unsigned int curtagset); + static size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -415,6 +426,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static Pertag pertag; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -435,6 +447,13 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++struct Pertag { ++ int nmasters[TAGCOUNT + 1]; /* number of windows in master area */ ++ float mfacts[TAGCOUNT + 1]; /* mfacts per tag */ ++ unsigned int sellts[TAGCOUNT + 1]; /* selected layouts */ ++ const Layout *ltidxs[TAGCOUNT + 1][2]; /* matrix of tags and layouts indexes */ ++}; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1023,6 +1042,33 @@ createlocksurface(struct wl_listener *listener, void *data) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); + } + ++unsigned int ++getpertagtag(unsigned int curtagset) ++{ ++ size_t i; ++ ++ if (curtagset == TAGMASK) { ++ return 0; ++ } ++ ++ if ((curtagset & TAGMASK) == 0) { ++ return 0; // What to do in this case? ++ } ++ ++ for (i = 0; !(curtagset & 1 << i); i++) ; ++ return i + 1; ++} ++ ++void ++applypertag(Monitor *m) ++{ ++ m->nmaster = pertag.nmasters[m->pertag[m->seltags]]; ++ m->mfact = pertag.mfacts[m->pertag[m->seltags]]; ++ m->sellt = pertag.sellts[m->pertag[m->seltags]]; ++ m->lt[m->sellt] = pertag.ltidxs[m->pertag[m->seltags]][m->sellt]; ++ m->lt[m->sellt^1] = pertag.ltidxs[m->pertag[m->seltags]][m->sellt^1]; ++} ++ + void + createmon(struct wl_listener *listener, void *data) + { +@@ -1046,14 +1092,12 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = (1<pertag[0] = m->pertag[1] = getpertagtag(m->tagset[0]); ++ applypertag(m); + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; +- m->mfact = r->mfact; +- m->nmaster = r->nmaster; +- m->lt[0] = r->lt; +- m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); +@@ -1597,7 +1641,7 @@ incnmaster(const Arg *arg) + { + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ selmon->nmaster = pertag.nmasters[selmon->pertag[selmon->seltags]] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); + } + +@@ -2433,9 +2477,9 @@ setlayout(const Arg *arg) + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +- selmon->sellt ^= 1; ++ selmon->sellt = pertag.sellts[selmon->pertag[selmon->seltags]] ^= 1; + if (arg && arg->v) +- selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ selmon->lt[selmon->sellt] = pertag.ltidxs[selmon->pertag[selmon->seltags]][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2452,7 +2496,7 @@ setmfact(const Arg *arg) + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; +- selmon->mfact = f; ++ selmon->mfact = pertag.mfacts[selmon->pertag[selmon->seltags]] = f; + arrange(selmon); + } + +@@ -2504,6 +2548,10 @@ setsel(struct wl_listener *listener, void *data) + void + setup(void) + { ++ const TagRule *r; ++ struct xkb_context *context; ++ struct xkb_keymap *keymap; ++ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); +@@ -2596,6 +2644,19 @@ setup(void) + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + ++ for (i = 0; i <= TAGCOUNT; i++) { ++ for (r = tagrules; r < END(tagrules); r++) { ++ if (!r->tag || r->tag == i) { ++ pertag.mfacts[i] = r->mfact; ++ pertag.nmasters[i] = r->nmaster; ++ pertag.sellts[i] = 0; ++ pertag.ltidxs[i][0] = r->lt; ++ pertag.ltidxs[i][1] = r->lt; ++ break; ++ } ++ } ++ } ++ + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); +@@ -2857,6 +2918,12 @@ toggleview(const Arg *arg) + if (m !=selmon && newtagset & m->tagset[m->seltags]) + return; + ++ // set new pertag tag only if the tag we were at was removed, or if all tags are shown. ++ if (!(newtagset & 1 << (selmon->pertag[selmon->seltags] - 1)) || newtagset == TAGMASK) { ++ selmon->pertag[selmon->seltags] = getpertagtag(newtagset); ++ } ++ ++ applypertag(selmon); + selmon->tagset[selmon->seltags] = newtagset; + attachclients(selmon); + focusclient(focustop(selmon), 1); +@@ -3072,6 +3139,8 @@ view(const Arg *arg) + } + m->seltags ^= 1; + m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ m->pertag[m->seltags] = origm->pertag[origm->seltags]; ++ applypertag(m); + attachclients(m); + /* Beware: this changes selmon */ + focusclient(focustop(m), 1); +@@ -3081,11 +3150,14 @@ view(const Arg *arg) + } + + origm->seltags ^= 1; /* toggle sel tagset */ +- if (arg->ui & TAGMASK) ++ if (arg->ui & TAGMASK) { + origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ origm->pertag[origm->seltags] = getpertagtag(arg->ui & TAGMASK); ++ } + + /* Change selmon back to orig mon */ + selmon = origm; ++ applypertag(origm); + attachclients(origm); + focusclient(focustop(origm), 1); + arrange(origm); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/singletagset-sticky/README.md b/dwl-bak/dwl-patches/patches/singletagset-sticky/README.md new file mode 100644 index 0000000..f1f9fd3 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset-sticky/README.md @@ -0,0 +1,13 @@ +### Description +Makes sticky work as expected with singletagset. The sticky window will +stay on original output until you explicitely put it to a different monitor. + +This patch expects both [singletagset](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/singletagset) and [sticky](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/sticky) patches to be already in +your tree committed. It applies onto them. + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset-sticky/singletagset-sticky.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset-sticky) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) diff --git a/dwl-bak/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch b/dwl-bak/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch new file mode 100644 index 0000000..ce6a40c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch @@ -0,0 +1,27 @@ +From a37d65c8601a7c5b03c53bb99956da8a24952628 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 19 Jul 2024 16:51:26 +0200 +Subject: [PATCH] singletagset-sticky + +--- + dwl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 59e56bd..fbbdff9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -73,8 +73,8 @@ + #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)->issticky) +-#define SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) ++#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((((C)->tags & (M)->tagset[(M)->seltags])) || (C)->issticky)) ++#define SVISIBLEON(C, M) ((M) && (C)->mon && (((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->issticky) || ((C)->issticky && (C)->mon == (M))) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/singletagset/README.md b/dwl-bak/dwl-patches/patches/singletagset/README.md new file mode 100644 index 0000000..920d8a4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset/README.md @@ -0,0 +1,19 @@ +### Description +Single set of tags shared between multiple monitors. + +This patch allows all the tags to be shared between both (or more) monitors. +So a single set of tags from 1 to 9 can be viewed on any monitor, as opposed to +having separate tag sets 1 to 9 on each monitor. + +Originally based on the dwm single_tagset patch: +https://dwm.suckless.org/patches/single_tagset/ + +### Download +- [git branch (v0.7)](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset) +- [git branch (v0.6)](https://codeberg.org/bencc/dwl/src/branch/singletagset) +- [2024-07-26 (v0.7)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset/singletagset-v0.7.patch) +- [2024-05-16 (v0.6)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset/singletagset-v0.6.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Rutherther](https://codeberg.org/Rutherther) diff --git a/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.6.patch b/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.6.patch new file mode 100644 index 0000000..708774b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.6.patch @@ -0,0 +1,293 @@ +From 140fe587bf34cf43e44acaf365b16ec3e385f742 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 16 May 2024 09:56:58 +1000 +Subject: [PATCH] singletagset + +--- + dwl.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 140 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index bf763dfc..5b1d594f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -72,6 +72,7 @@ + #define ROUND(X) ((int)((X < 0) ? (X - 0.5) : (X + 0.5))) + #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) ++#define SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +@@ -246,6 +247,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void attachclients(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -283,6 +285,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -470,7 +473,18 @@ applyrules(Client *c) + } + } + } ++ wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); ++ ++ wl_list_for_each(m, &mons, link) { ++ // tag with different monitor selected by rules ++ if (m->tagset[m->seltags] & newtags) { ++ mon = m; ++ break; ++ } ++ } ++ + setmon(c, mon, newtags); ++ attachclients(mon); + } + + void +@@ -558,6 +572,45 @@ arrangelayers(Monitor *m) + } + } + ++void ++attachclients(Monitor *m) ++{ ++ Monitor *tm; ++ unsigned int utags = 0; ++ Client *c; ++ int rmons = 0; ++ ++ if (m == NULL) { ++ return; ++ } ++ ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ utags |= tm->tagset[tm->seltags]; ++ } ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (SVISIBLEON(c, m)) { ++ /* if client is also visible on other tags that are displayed on ++ * other monitors, remove these tags */ ++ if (c->tags & utags) { ++ c->tags = c->tags & m->tagset[m->seltags]; ++ rmons = 1; ++ } ++ setmon(c, m, c->tags); ++ } ++ } ++ ++ if (rmons) { ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ arrange(tm); ++ } ++ } ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -881,7 +934,7 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ +- m->tagset[0] = m->tagset[1] = 1; ++ m->tagset[0] = m->tagset[1] = (1<name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -1354,7 +1407,7 @@ focustop(Monitor *m) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (SVISIBLEON(c, m)) + return c; + } + return NULL; +@@ -1367,6 +1420,29 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++size_t ++getunusedtag(void) ++{ ++ size_t i = 0; ++ Monitor *m; ++ if (wl_list_empty(&mons)) { ++ return i; ++ } ++ for (i=0; i < TAGCOUNT; i++) { ++ int is_used = 0; ++ wl_list_for_each(m, &mons, link) { ++ if ((m->tagset[m->seltags] & (1<mon != m) +- continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; +@@ -2551,6 +2625,7 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; +@@ -2558,15 +2633,25 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ wl_list_for_each(m, &mons, link) { ++ attachclients(m); ++ arrange(m); ++ } + printstatus(); + } + + void + tagmon(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); +- if (sel) ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ wl_list_for_each(m, &mons, link) { ++ arrange(m); ++ } ++ focusclient(focustop(sel->mon), 1); ++ } + } + + void +@@ -2623,12 +2708,18 @@ togglefullscreen(const Arg *arg) + void + toggletag(const Arg *arg) + { ++ Monitor *m; + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m != selmon && newtags & m->tagset[m->seltags]) ++ return; ++ + sel->tags = newtags; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2637,11 +2728,17 @@ toggletag(const Arg *arg) + void + toggleview(const Arg *arg) + { ++ Monitor *m; + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m !=selmon && newtagset & m->tagset[m->seltags]) ++ return; ++ + selmon->tagset[selmon->seltags] = newtagset; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2759,6 +2856,7 @@ updatemons(struct wl_listener *listener, void *data) + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + ++ attachclients(m); + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ +@@ -2825,13 +2923,45 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { +- if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) ++ Monitor *m, *origm = selmon; ++ unsigned int newtags; ++ ++ if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) { + return; +- selmon->seltags ^= 1; /* toggle sel tagset */ ++ } ++ ++ newtags = origm->tagset[origm->seltags ^ 1]; ++ ++ /* swap tags when trying to display a tag from another monitor */ ++ if (arg->ui & TAGMASK) { ++ newtags = arg->ui & TAGMASK; ++ } ++ wl_list_for_each(m, &mons, link) { ++ if (m != origm && newtags & m->tagset[m->seltags]) { ++ /* prevent displaying all tags (MODKEY-0) when multiple monitors ++ * are connected */ ++ if (newtags & origm->tagset[origm->seltags]) { ++ return; ++ } ++ m->seltags ^= 1; ++ m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ attachclients(m); ++ /* Beware: this changes selmon */ ++ focusclient(focustop(m), 1); ++ arrange(m); ++ break; ++ } ++ } ++ ++ origm->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) +- selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); +- arrange(selmon); ++ origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ ++ /* Change selmon back to orig mon */ ++ selmon = origm; ++ attachclients(origm); ++ focusclient(focustop(origm), 1); ++ arrange(origm); + printstatus(); + } + +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.7.patch b/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.7.patch new file mode 100644 index 0000000..9c2f98b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/singletagset/singletagset-v0.7.patch @@ -0,0 +1,312 @@ +From bd90ec504369a69dbefc34cb72265d15c7795944 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 26 Jul 2024 12:36:41 +0200 +Subject: [PATCH] singletagset + +--- + dwl.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 145 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5bf995e..40a6e48 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -74,6 +74,7 @@ + #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 SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +@@ -249,6 +250,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void attachclients(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -291,6 +293,7 @@ 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 size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -478,7 +481,17 @@ applyrules(Client *c) + } + } + } ++ ++ wl_list_for_each(m, &mons, link) { ++ // tag with different monitor selected by rules ++ if (m->tagset[m->seltags] & newtags) { ++ mon = m; ++ break; ++ } ++ } ++ + setmon(c, mon, newtags); ++ attachclients(mon); + } + + void +@@ -580,6 +593,45 @@ arrangelayers(Monitor *m) + } + } + ++void ++attachclients(Monitor *m) ++{ ++ Monitor *tm; ++ unsigned int utags = 0; ++ Client *c; ++ int rmons = 0; ++ ++ if (m == NULL) { ++ return; ++ } ++ ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ utags |= tm->tagset[tm->seltags]; ++ } ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (SVISIBLEON(c, m)) { ++ /* if client is also visible on other tags that are displayed on ++ * other monitors, remove these tags */ ++ if (c->tags & utags) { ++ c->tags = c->tags & m->tagset[m->seltags]; ++ rmons = 1; ++ } ++ setmon(c, m, c->tags); ++ } ++ } ++ ++ if (rmons) { ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ arrange(tm); ++ } ++ } ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -750,6 +802,9 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } ++ ++ m->tagset[0] = m->tagset[1] = 0; ++ + focusclient(focustop(selmon), 1); + printstatus(); + } +@@ -990,7 +1045,7 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ +- m->tagset[0] = m->tagset[1] = 1; ++ m->tagset[0] = m->tagset[1] = (1<name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -1456,7 +1511,7 @@ focustop(Monitor *m) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (SVISIBLEON(c, m)) + return c; + } + return NULL; +@@ -1469,6 +1524,29 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++size_t ++getunusedtag(void) ++{ ++ size_t i = 0; ++ Monitor *m; ++ if (wl_list_empty(&mons)) { ++ return i; ++ } ++ for (i=0; i < TAGCOUNT; i++) { ++ int is_used = 0; ++ wl_list_for_each(m, &mons, link) { ++ if ((m->tagset[m->seltags] & (1<mon != m) +- continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; +@@ -2669,6 +2745,7 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; +@@ -2676,15 +2753,25 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ wl_list_for_each(m, &mons, link) { ++ attachclients(m); ++ arrange(m); ++ } + printstatus(); + } + + void + tagmon(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); +- if (sel) ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ wl_list_for_each(m, &mons, link) { ++ arrange(m); ++ } ++ focusclient(focustop(sel->mon), 1); ++ } + } + + void +@@ -2741,12 +2828,18 @@ togglefullscreen(const Arg *arg) + void + toggletag(const Arg *arg) + { ++ Monitor *m; + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m != selmon && newtags & m->tagset[m->seltags]) ++ return; ++ + sel->tags = newtags; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2755,11 +2848,17 @@ toggletag(const Arg *arg) + void + toggleview(const Arg *arg) + { ++ Monitor *m; + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m !=selmon && newtagset & m->tagset[m->seltags]) ++ return; ++ + selmon->tagset[selmon->seltags] = newtagset; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2863,6 +2962,9 @@ updatemons(struct wl_listener *listener, void *data) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + ++ if ((m->tagset[0] & TAGMASK) == 0 && (m->tagset[1] & TAGMASK) == 0) ++ m->tagset[0] = m->tagset[1] = (1 << getunusedtag()) & TAGMASK; ++ + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; +@@ -2877,6 +2979,7 @@ updatemons(struct wl_listener *listener, void *data) + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + ++ attachclients(m); + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ +@@ -2947,13 +3050,45 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { +- if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) ++ Monitor *m, *origm = selmon; ++ unsigned int newtags; ++ ++ if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) { + return; +- selmon->seltags ^= 1; /* toggle sel tagset */ ++ } ++ ++ newtags = origm->tagset[origm->seltags ^ 1]; ++ ++ /* swap tags when trying to display a tag from another monitor */ ++ if (arg->ui & TAGMASK) { ++ newtags = arg->ui & TAGMASK; ++ } ++ wl_list_for_each(m, &mons, link) { ++ if (m != origm && newtags & m->tagset[m->seltags]) { ++ /* prevent displaying all tags (MODKEY-0) when multiple monitors ++ * are connected */ ++ if (newtags & origm->tagset[origm->seltags]) { ++ return; ++ } ++ m->seltags ^= 1; ++ m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ attachclients(m); ++ /* Beware: this changes selmon */ ++ focusclient(focustop(m), 1); ++ arrange(m); ++ break; ++ } ++ } ++ ++ origm->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) +- selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); +- arrange(selmon); ++ origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ ++ /* Change selmon back to orig mon */ ++ selmon = origm; ++ attachclients(origm); ++ focusclient(focustop(origm), 1); ++ arrange(origm); + printstatus(); + } + +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/skipfocus/README.md b/dwl-bak/dwl-patches/patches/skipfocus/README.md new file mode 100644 index 0000000..4fb6e8a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/skipfocus/README.md @@ -0,0 +1,18 @@ +### Description +Adds a rule-based ability to skip automatically focusing a window on creation. Expected use-case is for transient windows like notifications etc. The window can still be focused by mouse or keyboard movement. + +| `skipfocus` value | effect | +| ----------------- | ------------------ | +| 0 | usual | +| 1 | skipautofocus | +| 2 | skipfocus entirely | + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus-20240714.patch) +- [2024-01-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus-20240108.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/skipfocus) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) + diff --git a/dwl-bak/dwl-patches/patches/skipfocus/skipfocus-20240714.patch b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus-20240714.patch new file mode 100644 index 0000000..2715fb9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus-20240714.patch @@ -0,0 +1,81 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..9f6599c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 145fd01..ec9d1af 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -229,6 +229,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -465,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1323,6 +1325,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -1692,11 +1701,13 @@ mapnotify(struct wl_listener *listener, void *data) + printstatus(); + + unset_fullscreen: +- m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +- wl_list_for_each(w, &clients, link) { +- if (w != c && w->isfullscreen && m == w->mon && (w->tags & c->tags)) +- setfullscreen(w, 0); +- } ++ if (!c->skipfocus) { ++ m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); ++ wl_list_for_each(w, &clients, link) { ++ if (w != c && w->isfullscreen && m == w->mon && (w->tags & c->tags)) ++ setfullscreen(w, 0); ++ } ++ } + } + + void diff --git a/dwl-bak/dwl-patches/patches/skipfocus/skipfocus.patch b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus.patch new file mode 100644 index 0000000..ed445ec --- /dev/null +++ b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus.patch @@ -0,0 +1,81 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..9f6599c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0c861..b6a4cbb 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,7 +141,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +231,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -466,6 +467,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1343,6 +1345,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -1738,11 +1747,13 @@ mapnotify(struct wl_listener *listener, void *data) + printstatus(); + + unset_fullscreen: +- m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +- wl_list_for_each(w, &clients, link) { +- if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) +- setfullscreen(w, 0); +- } ++ if (!c->skipfocus) { ++ m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); ++ wl_list_for_each(w, &clients, link) { ++ if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) ++ setfullscreen(w, 0); ++ } ++ } + } + + void diff --git a/dwl-bak/dwl-patches/patches/skipfocus/skipfocus20240108.patch b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus20240108.patch new file mode 100644 index 0000000..10d5f4f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/skipfocus/skipfocus20240108.patch @@ -0,0 +1,62 @@ +diff --git a/config.def.h b/config.def.h +index a784eb4..5d3a4f9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,10 +21,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 6f041a0..90ac57b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -137,7 +137,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -228,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -464,6 +465,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1307,6 +1309,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); diff --git a/dwl-bak/dwl-patches/patches/smartborders/README.md b/dwl-bak/dwl-patches/patches/smartborders/README.md new file mode 100644 index 0000000..6fe0e59 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/smartborders/README.md @@ -0,0 +1,14 @@ +### Description +The borders of a window aren't drawn when the window is the only tiling window +in its tag OR if the window is in a monocle layout. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/smartborders) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/smartborders/smartborders.patch) +- [smartborders-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/smartborders/smartborders-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [fauxmight](https://codeberg.org/fauxmight) +- [Piotr Marendowski](https://github.com/piotr-marendowski) +- Andrey Proskurin diff --git a/dwl-bak/dwl-patches/patches/smartborders/smartborders-0.7.patch b/dwl-bak/dwl-patches/patches/smartborders/smartborders-0.7.patch new file mode 100644 index 0000000..732f06f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/smartborders/smartborders-0.7.patch @@ -0,0 +1,204 @@ +From b10e044d95072994d9b00b31ea70051ab02a026b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 16 Aug 2022 15:28:00 -0500 +Subject: [PATCH] don't draw borders if there is only one window +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: A Frederick Christensen +Co-authored-by: Andrey Proskurin +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 47 +++++++++++++++++++++++++++++++---------------- + 2 files changed, 32 insertions(+), 16 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..632f151f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartborders = 1; + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index a2711f67..647ce38c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -320,7 +320,7 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resize(Client *c, struct wlr_box geo, int interact, int draw_borders); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -746,7 +746,7 @@ closemon(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +- .width = c->geom.width, .height = c->geom.height}, 0); ++ .width = c->geom.width, .height = c->geom.height}, 0, 1); + if (c->mon == m) + setmon(c, selmon, c->tags); + } +@@ -814,8 +814,11 @@ commitnotify(struct wl_listener *listener, void *data) + return; + } + +- if (client_surface(c)->mapped && c->mon) +- resize(c, c->geom, (c->isfloating && !c->isfullscreen)); ++ if (client_surface(c)->mapped && c->mon && c->mon->lt[c->mon->sellt]->arrange ++ && !c->isfullscreen && !c->isfloating) ++ c->mon->lt[c->mon->sellt]->arrange(c->mon); ++ else ++ resize(c, c->geom, (c->isfloating && !c->isfullscreen), (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) +@@ -1775,7 +1778,7 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ resize(c, m->w, 0, !smartborders); + n++; + } + if (n) +@@ -1867,11 +1870,11 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); ++ .width = grabc->geom.width, .height = grabc->geom.height}, 1, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1, 1); + return; + } + +@@ -2178,7 +2181,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resize(Client *c, struct wlr_box geo, int interact, int draw_borders) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2190,6 +2193,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +@@ -2314,6 +2318,8 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); ++ if (c->isfloating && !c->bw) ++ resize(c, c->mon->m, 0, 1); + arrange(c->mon); + printstatus(); + } +@@ -2331,11 +2337,11 @@ setfullscreen(Client *c, int fullscreen) + + if (fullscreen) { + c->prev = c->geom; +- resize(c, c->mon->m, 0); ++ resize(c, c->mon->m, 0, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ +- resize(c, c->prev, 0); ++ resize(c, c->prev, 0, 1); + } + arrange(c->mon); + printstatus(); +@@ -2362,6 +2368,12 @@ setlayout(const Arg *arg) + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ if (!selmon->lt[selmon->sellt]->arrange) { ++ /* floating layout, draw borders around all clients */ ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ resize(c, c->mon->m, 0, 1); ++ } + arrange(selmon); + printstatus(); + } +@@ -2396,7 +2408,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ +- resize(c, c->geom, 0); ++ resize(c, c->geom, 0, 1); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); +@@ -2691,7 +2703,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, draw_borders = 1; + int i, n = 0; + Client *c; + +@@ -2701,6 +2713,9 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (n == smartborders) ++ draw_borders = 0; ++ + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else +@@ -2711,11 +2726,11 @@ tile(Monitor *m) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0, draw_borders); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0, draw_borders); + ty += c->geom.height; + } + i++; +@@ -2884,7 +2899,7 @@ updatemons(struct wl_listener *listener, void *data) + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) +- resize(c, m->m, 0); ++ resize(c, m->m, 0, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ +@@ -3087,7 +3102,7 @@ configurex11(struct wl_listener *listener, void *data) + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, +- .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); ++ .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0, 1); + else + arrange(c->mon); + } +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/smartborders/smartborders.patch b/dwl-bak/dwl-patches/patches/smartborders/smartborders.patch new file mode 100644 index 0000000..5eac3ee --- /dev/null +++ b/dwl-bak/dwl-patches/patches/smartborders/smartborders.patch @@ -0,0 +1,203 @@ +From 38010bdecf63bdb8acfc584825b398838310eed1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 16 Aug 2022 15:28:00 -0500 +Subject: [PATCH] don't draw borders if there is only one window +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: A Frederick Christensen +Co-authored-by: Andrey Proskurin +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 46 +++++++++++++++++++++++++++++++--------------- + 2 files changed, 32 insertions(+), 15 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..632f151f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartborders = 1; + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index ad21e1ba..0c56431a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -317,7 +317,7 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resize(Client *c, struct wlr_box geo, int interact, int draw_borders); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -803,7 +803,7 @@ closemon(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +- .width = c->geom.width, .height = c->geom.height}, 0); ++ .width = c->geom.width, .height = c->geom.height}, 0, 1); + if (c->mon == m) + setmon(c, selmon, c->tags); + } +@@ -872,7 +872,11 @@ commitnotify(struct wl_listener *listener, void *data) + return; + } + +- resize(c, c->geom, (c->isfloating && !c->isfullscreen)); ++ if (client_surface(c)->mapped && c->mon && c->mon->lt[c->mon->sellt]->arrange ++ && !c->isfullscreen && !c->isfloating) ++ c->mon->lt[c->mon->sellt]->arrange(c->mon); ++ else ++ resize(c, c->geom, (c->isfloating && !c->isfullscreen), (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) +@@ -1819,7 +1823,7 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ resize(c, m->w, 0, !smartborders); + n++; + } + if (n) +@@ -1911,11 +1915,11 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); ++ .width = grabc->geom.width, .height = grabc->geom.height}, 1, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1, 1); + return; + } + +@@ -2194,7 +2198,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resize(Client *c, struct wlr_box geo, int interact, int draw_borders) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2206,6 +2210,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +@@ -2330,6 +2335,8 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); ++ if (c->isfloating && !c->bw) ++ resize(c, c->mon->m, 0, 1); + arrange(c->mon); + printstatus(); + } +@@ -2347,11 +2354,11 @@ setfullscreen(Client *c, int fullscreen) + + if (fullscreen) { + c->prev = c->geom; +- resize(c, c->mon->m, 0); ++ resize(c, c->mon->m, 0, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ +- resize(c, c->prev, 0); ++ resize(c, c->prev, 0, 1); + } + arrange(c->mon); + printstatus(); +@@ -2367,6 +2374,12 @@ setlayout(const Arg *arg) + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ if (!selmon->lt[selmon->sellt]->arrange) { ++ /* floating layout, draw borders around all clients */ ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ resize(c, c->mon->m, 0, 1); ++ } + arrange(selmon); + printstatus(); + } +@@ -2401,7 +2414,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ +- resize(c, c->geom, 0); ++ resize(c, c->geom, 0, 1); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); +@@ -2701,7 +2714,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, draw_borders = 1; + int i, n = 0; + Client *c; + +@@ -2711,6 +2724,9 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (n == smartborders) ++ draw_borders = 0; ++ + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else +@@ -2721,11 +2737,11 @@ tile(Monitor *m) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0, draw_borders); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0, draw_borders); + ty += c->geom.height; + } + i++; +@@ -2894,7 +2910,7 @@ updatemons(struct wl_listener *listener, void *data) + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) +- resize(c, m->m, 0); ++ resize(c, m->m, 0, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ +@@ -3103,7 +3119,7 @@ configurex11(struct wl_listener *listener, void *data) + if ((c->isfloating && c != grabc) || !c->mon->lt[c->mon->sellt]->arrange) { + resize(c, (struct wlr_box){.x = event->x - c->bw, + .y = event->y - c->bw, .width = event->width + c->bw * 2, +- .height = event->height + c->bw * 2}, 0); ++ .height = event->height + c->bw * 2}, 0, 1); + } else { + arrange(c->mon); + } +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/snail-gaps/README.md b/dwl-bak/dwl-patches/patches/snail-gaps/README.md new file mode 100644 index 0000000..ba26f13 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/snail-gaps/README.md @@ -0,0 +1,15 @@ +### Description + +Adds support for the [gaps patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) +to the [snail layout patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/snail). + +Install the [gaps patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) +and the [snail patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/snail) first. + +### Download + +- [main 2024-08-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail-gaps/snail-gaps.patch) + +### Authors + +- [JoaoCostaIFG](https://codeberg.org/JoaoCostaIFG) ([joaocosta@posteo.net](mailto:joaocosta@posteo.net)) diff --git a/dwl-bak/dwl-patches/patches/snail-gaps/snail-gaps.patch b/dwl-bak/dwl-patches/patches/snail-gaps/snail-gaps.patch new file mode 100644 index 0000000..51d2812 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/snail-gaps/snail-gaps.patch @@ -0,0 +1,160 @@ +From 804e69ca4bf586bcec46e018630f94c1c4e0b7e7 Mon Sep 17 00:00:00 2001 +From: JoaoCostaIFG +Date: Thu, 8 Aug 2024 00:05:27 +0100 +Subject: [PATCH 2/2] Add gaps support to snail layout + +--- + dwl.c | 105 +++++++++++++++++++++++++++++++++------------------------- + 1 file changed, 59 insertions(+), 46 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 46bdca1..a158dd6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2659,8 +2659,8 @@ void + snail(Monitor *m) + { + int i = 0, n = 0; +- unsigned int mw = m->w.width; +- Client *c, *prev; ++ unsigned int mw = m->w.width, e = m->gaps, w = 0, h = 0, egappx = 0; ++ Client *c, *prev = NULL; + enum wlr_direction dir = WLR_DIRECTION_RIGHT; + + wl_list_for_each(c, &clients, link) +@@ -2668,9 +2668,12 @@ snail(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; ++ egappx = e * gappx; + + if (n > m->nmaster) +- mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (unsigned int)round((m->w.width + egappx) * m->mfact) : 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) +@@ -2681,8 +2684,8 @@ snail(Monitor *m) + * master area with this window + */ + if (mw > 0 && i == 0) { +- c->geom = (struct wlr_box){.x = m->w.x, .y = m->w.y, +- .width = mw, .height = m->w.height}; ++ c->geom = (struct wlr_box){.x = m->w.x + egappx, .y = m->w.y + egappx, ++ .width = mw - 2*egappx, .height = m->w.height - 2*egappx}; + /* + * If the first window in the master area is wide, split it + * horizontally and put next one on its right; otherwise, split it +@@ -2694,55 +2697,65 @@ snail(Monitor *m) + * m->nmaster-th window + */ + } else if (i == m->nmaster) { +- c->geom = (struct wlr_box){.x = m->w.x + mw, .y = m->w.y, +- .width = m->w.width - mw, .height = m->w.height}; ++ c->geom = (struct wlr_box){.x = m->w.x + mw + egappx, .y = m->w.y + egappx, ++ .width = m->w.width - mw - 2*egappx, .height = m->w.height - 2*egappx}; + /* + * If the first window in the stack is wide, split it horizontally + * and put next one on its right; otherwise, split it vertically and + * put the next one below it + */ + dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; +- /* +- * Split the previous horizontally and put the current window on the right +- */ +- } else if (dir == WLR_DIRECTION_RIGHT) { +- c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ } else if (prev) { + /* +- * If it's a stack window or the first narrow window in the master +- * area, put the next one below it ++ * Split the previous horizontally and put the current window on the right + */ +- if (i >= m->nmaster || c->geom.width < m->w.height) +- dir = WLR_DIRECTION_DOWN; +- /* +- * Split the previous vertically and put the current window below it +- */ +- } else if (dir == WLR_DIRECTION_DOWN) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- dir = WLR_DIRECTION_LEFT; +- /* +- * Split the previous horizontally and put the current window on the left +- */ +- } else if (dir == WLR_DIRECTION_LEFT) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- dir = WLR_DIRECTION_UP; +- /* +- * Split the previous vertically and put the current window above it +- */ +- } else { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- dir = WLR_DIRECTION_RIGHT; ++ if (dir == WLR_DIRECTION_RIGHT) { ++ w = prev->geom.width / 2 - egappx; ++ h = prev->geom.height; ++ c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2 + egappx, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ /* ++ * If it's a stack window or the first narrow window in the master ++ * area, put the next one below it ++ */ ++ if (i >= m->nmaster || c->geom.width < m->w.height) ++ dir = WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous vertically and put the current window below it ++ */ ++ } else if (dir == WLR_DIRECTION_DOWN) { ++ w = prev->geom.width; ++ h = prev->geom.height / 2 - egappx; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2 + egappx, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_LEFT; ++ /* ++ * Split the previous horizontally and put the current window on the left ++ */ ++ } else if (dir == WLR_DIRECTION_LEFT) { ++ w = prev->geom.width / 2 - egappx; ++ h = prev->geom.height; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2 + egappx, .y = prev->geom.y, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_UP; ++ /* ++ * Split the previous vertically and put the current window above it ++ */ ++ } else { ++ w = prev->geom.width; ++ h = prev->geom.height / 2 - egappx; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2 + egappx, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_RIGHT; ++ } + } + i++; + prev = c; +-- +2.46.0 + + diff --git a/dwl-bak/dwl-patches/patches/snail/README.md b/dwl-bak/dwl-patches/patches/snail/README.md new file mode 100644 index 0000000..0c94b79 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/snail/README.md @@ -0,0 +1,9 @@ +### Description +Adds a spiral-inspired layout for wide screens. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail/snail.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () +- [Nikita Ivanov](https://github.com/NikitaIvanovV) (fix for flickering) diff --git a/dwl-bak/dwl-patches/patches/snail/snail.patch b/dwl-bak/dwl-patches/patches/snail/snail.patch new file mode 100644 index 0000000..061030a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/snail/snail.patch @@ -0,0 +1,250 @@ +From 39b6da6082db641191699be906e485f8c8fbc4e6 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sun, 11 Feb 2024 09:09:16 +0200 +Subject: [PATCH] add the snail layout +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This layout is a scalable alternative to the "tile" and "spiral" layouts, optimized for wide monitors. Both the master area and the stack are "spirals", but windows in the master area are split horizontally as long as the master area has enough horizontal space, and the first window in the stack is split vertically unless the stack is wide. + +With one window in the master area and mfact = 0.5, it behaves like the spiral layout: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ ├───┬───┬────────┤ +│ │ │ │ │ +│ ├───┴───┤ │ +│ │ │ │ +│ │ │ │ +└───────────────┴───────┴────────┘ + +With 2 windows in the master area and 2 in the stack: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───────────────┼────────────────┤ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +└───────────────┴────────────────┘ + +With 3 windows in the master area and 2 in the stack: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───────┬───────┼────────────────┤ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +└───────┴───────┴────────────────┘ + +With many windows in both areas: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───┬───┬───────┼───┬───┬────────┤ +│ │ │ │ │ │ │ +├───┴───┤ ├───┴───┤ │ +│ │ │ │ │ +│ │ │ │ │ +└───────┴───────┴───────┴────────┘ + +With 2 windows in the master area, many windows in the stack and high mfact: + +┌──────────┬──────────┬──────────┐ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ master ├──┬stack──┤ +│ │ │ │ │ │ +│ │ ├──┴──┤ │ +│ │ │ │ │ +│ │ │ │ │ +└──────────┴──────────┴─────┴────┘ + +With 2 windows in the master area, many windows in the stack and low mfact: + +┌──────────┬──────────┬──────────┐ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +├──master──┤ stack┬──┬────┤ +│ │ │ │ │ │ +│ │ ├──┴──┤ │ +│ │ │ │ │ +│ │ │ │ │ +└──────────┴──────────┴─────┴────┘ +--- + config.def.h | 4 +- + dwl.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 105 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..abf9644 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "@|@", snail }, + }; + + /* monitors */ +@@ -48,7 +49,7 @@ static const MonitorRule monrules[] = { + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.64f, 1, 1, &layouts[3], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + }; + + /* keyboard */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 5bf995e..45ac5ad 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -333,6 +333,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void snail(Monitor *m); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -2644,6 +2645,107 @@ setup(void) + #endif + } + ++void ++snail(Monitor *m) ++{ ++ int i = 0, n = 0; ++ unsigned int mw = m->w.width; ++ Client *c, *prev; ++ enum wlr_direction dir = WLR_DIRECTION_RIGHT; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? (int)round(m->w.width * m->mfact) : 0; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ /* ++ * If the master area exists and this is the first window, fill the ++ * master area with this window ++ */ ++ if (mw > 0 && i == 0) { ++ c->geom = (struct wlr_box){.x = m->w.x, .y = m->w.y, ++ .width = mw, .height = m->w.height}; ++ /* ++ * If the first window in the master area is wide, split it ++ * horizontally and put next one on its right; otherwise, split it ++ * vertically and put the next one below it ++ */ ++ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; ++ /* ++ * If the master area is full or doesn't exist, fill the stack with the ++ * m->nmaster-th window ++ */ ++ } else if (i == m->nmaster) { ++ c->geom = (struct wlr_box){.x = m->w.x + mw, .y = m->w.y, ++ .width = m->w.width - mw, .height = m->w.height}; ++ /* ++ * If the first window in the stack is wide, split it horizontally ++ * and put next one on its right; otherwise, split it vertically and ++ * put the next one below it ++ */ ++ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous horizontally and put the current window on the right ++ */ ++ } else if (dir == WLR_DIRECTION_RIGHT) { ++ c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ /* ++ * If it's a stack window or the first narrow window in the master ++ * area, put the next one below it ++ */ ++ if (i >= m->nmaster || c->geom.width < m->w.height) ++ dir = WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous vertically and put the current window below it ++ */ ++ } else if (dir == WLR_DIRECTION_DOWN) { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ dir = WLR_DIRECTION_LEFT; ++ /* ++ * Split the previous horizontally and put the current window on the left ++ */ ++ } else if (dir == WLR_DIRECTION_LEFT) { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ dir = WLR_DIRECTION_UP; ++ /* ++ * Split the previous vertically and put the current window above it ++ */ ++ } else { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ dir = WLR_DIRECTION_RIGHT; ++ } ++ i++; ++ prev = c; ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ resize(c, c->geom, 0); ++ } ++} ++ + void + spawn(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/spawninfo/README.md b/dwl-bak/dwl-patches/patches/spawninfo/README.md new file mode 100644 index 0000000..5ee345d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/spawninfo/README.md @@ -0,0 +1,41 @@ +### Description + +This patch adds `spawninfo` function that is very similar to `spawn`, except it +also passes some information about the focused client via stdin. + +The info is passed in this format: + + PID + TITLE + APPID + TAGS + X,Y WIDTHxHEIGHT + +I use it for 2 things: grabbing a screenshot of a focused window and adjusting +volume of audio produced by a focused window (so much simpler than having to +open pulsemixer every time). If you want to have the same functionality, you +need to put these scripts into your PATH and make them executable: + +[screenshotwin](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/screenshotwin) +for taking a screenshot (`grim` is required): + +```c + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_S, spawninfo, SHCMD("screenshotwin") }, +``` + +[pamixerc](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/pamixerc) +for adjusting volume (`pactl` is required): + +```c + { MODKEY, XKB_KEY_XF86AudioRaiseVolume,spawninfo,SHCMD("pamixerc -- -i 5") }, + { MODKEY, XKB_KEY_XF86AudioLowerVolume,spawninfo,SHCMD("pamixerc -- -d 5") }, + { MODKEY, XKB_KEY_XF86AudioMute, spawninfo, SHCMD("pamixerc -- -t") }, +``` + +### Download + +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/spawninfo.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl-bak/dwl-patches/patches/spawninfo/pamixerc b/dwl-bak/dwl-patches/patches/spawninfo/pamixerc new file mode 100755 index 0000000..91a41c4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/spawninfo/pamixerc @@ -0,0 +1,134 @@ +#!/usr/bin/awk -f + +function get_parent(pid, cmd, ppid, arr) { + cmd = sprintf("/proc/%d/stat", pid) + ppid = "" + if ((getline line < cmd) > 0) { + split(line, arr) + ppid = arr[4] + } + close(cmd) + return ppid +} + +function proc_clients(line, arr) { + if (match(line, /^Client #(.*)/, arr)) { + store_client() + client_index = arr[1] + return + } + else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) { + process_id = arr[1] + return + } + else if (match(line, /^\s*application\.name = "(.*)"/, arr)) { + application_name = arr[1] + return + } +} + +function store_client() { + if (client_index == "") + return + + clients[client_index] = 1 + clients[client_index, "pid"] = process_id + clients[client_index, "parent_pid"] = get_parent(process_id) + clients[client_index, "name"] = application_name + client_index = "" + process_id = "" +} + +function proc_sink_inputs(line, arr) { + if (match(line, /^Sink Input #(.*)/, arr)) { + apply_sink_input() + sink_index = arr[1] + return + } + else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) { + process_id = arr[1] + return + } + else if (match(line, /^\s*Client: (.*)/, arr)) { + client_index = arr[1] + return + } + else if (match(line, /^\s*Mute: (.*)/, arr)) { + muted = (arr[1] == "yes") + return + } + else if (match(line, /^\s*Volume:.* ([0-9]+)%/, arr)) { + volume = strtonum(arr[1]) + return + } +} + +function apply_sink_input( cmd, header, msg, inc) { + if (sink_index == "") + return + + # Do stuff if PID matches + if (PID == clients[client_index, "pid"] || PID == clients[client_index, "parent_pid"]) { + switch (ACTION) { + case "-i": + case "-d": + inc = strtonum(ARG) + if (ACTION == "-i") + volume += inc + else + volume -= inc + volume = volume > 100 ? 100 : volume + volume = volume < 0 ? 0 : volume + cmd = sprintf("pactl set-sink-input-volume '%d' '%d'%", sink_index, volume) + system(cmd) + print cmd + break + case "-t": + muted = !muted + cmd = sprintf("pactl set-sink-input-mute '%d' '%s'", sink_index, muted ? "true" : "false") + system(cmd) + print cmd + break + } + # Display a "popup" with new volume + header = sprintf("Client Volume: %d%%%s", volume, muted ? " MUTED" : "") + msg = clients[client_index, "name"] + system(sprintf("notify-send -r 101 -u low -h 'int:value:%d' '%s' '%s'", volume, header, msg)) + } + + sink_index = "" +} + +BEGIN { + # Get arguments + # ACTION: -d, -i or -t (like pamixer) + # ARG: num arg for -d or -i + ACTION = ARGV[1] + ARG = ARGV[2] + for (i = 1; i < ARGC; i++) + delete ARGV[i] + + # Get client info + getline PID + getline TITLE # Not used + getline APPID # Not used + getline TAGS # Not used + getline GEOMETRY # Not used + + if (PID == "") + exit 1 + + # Process PulseAudio clients list and store PIDs for each + cmd = "pactl list clients" + while ((cmd | getline line) > 0) + proc_clients(line) + close(cmd) + store_client() + + # Process PulseAudio sink inputs list and run apply_sink_input() for each + cmd = "pactl list sink-inputs" + while ((cmd | getline line) > 0) + proc_sink_inputs(line) + close(cmd) + apply_sink_input() +} diff --git a/dwl-bak/dwl-patches/patches/spawninfo/screenshotwin b/dwl-bak/dwl-patches/patches/spawninfo/screenshotwin new file mode 100755 index 0000000..c8a3ebf --- /dev/null +++ b/dwl-bak/dwl-patches/patches/spawninfo/screenshotwin @@ -0,0 +1,21 @@ +#!/bin/sh + +# Get client info +read -r PID +read -r TITLE +read -r APPID +read -r TAGS +read -r GEOMETRY + +[ -n "$GEOMETRY" ] || exit 1 + +tempdir="/tmp/screenshots" +mkdir -p "$tempdir" +file="$(mktemp -p "$tempdir" "XXXXXX.png")" + +# Grab the screenshot! Very conviniently, GEOMETRY format matches the one +# expected by grim +grim -g "$GEOMETRY" "$file" || exit + +wl-copy -t image/png < "$file" +notify-send -i "$file" "Screenshot taken!" "Image copied to clipboard and saved to $file" diff --git a/dwl-bak/dwl-patches/patches/spawninfo/spawninfo.patch b/dwl-bak/dwl-patches/patches/spawninfo/spawninfo.patch new file mode 100644 index 0000000..e452a4e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/spawninfo/spawninfo.patch @@ -0,0 +1,110 @@ +From 83b8dc03f5ea40f472e90d452671f8e55faf2c4c Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sun, 9 Feb 2025 23:27:48 +0100 +Subject: [PATCH] spawninfo: spawn but pass client info via stdin + +--- + client.h | 12 ++++++++++++ + dwl.c | 42 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 54 insertions(+) + +diff --git a/client.h b/client.h +index 42f225f..bc9cad2 100644 +--- a/client.h ++++ b/client.h +@@ -131,6 +131,18 @@ client_get_appid(Client *c) + return c->surface.xdg->toplevel->app_id; + } + ++static inline int ++client_get_pid(Client *c) ++{ ++ pid_t pid; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) ++ return c->surface.xwayland->pid; ++#endif ++ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); ++ return pid; ++} ++ + static inline void + client_get_clip(Client *c, struct wlr_box *clip) + { +diff --git a/dwl.c b/dwl.c +index def2562..859514c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ pid_t pid; + } Client; + + typedef struct { +@@ -334,6 +335,7 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void spawninfo(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -466,6 +468,8 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ c->pid = client_get_pid(c); ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { +@@ -2658,6 +2662,44 @@ spawn(const Arg *arg) + } + } + ++void ++spawninfo(const Arg *arg) ++{ ++ int fd[2]; ++ pid_t pid; ++ const char *title, *appid; ++ Client *c = focustop(selmon); ++ ++ if (pipe(fd) == -1) ++ return; ++ if ((pid = fork()) == -1) ++ return; ++ if (pid == 0) { ++ dup2(fd[0], STDIN_FILENO); ++ close(fd[0]); ++ close(fd[1]); ++ dup2(STDERR_FILENO, STDOUT_FILENO); ++ setsid(); ++ execvp(((char **)arg->v)[0], (char **)arg->v); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[0]); ++ } ++ ++ close(fd[0]); ++ ++ if (c) { ++ if (!(title = client_get_title(c))) ++ title = ""; ++ if (!(appid = client_get_appid(c))) ++ appid = ""; ++ dprintf(fd[1], "%d\n%s\n%s\n%"PRIu32"\n%d,%d %dx%d\n", c->pid, ++ title, appid, c->tags, ++ c->geom.x + c->bw, c->geom.y + c->bw, ++ c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); ++ } ++ ++ close(fd[1]); ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/stacker/README.md b/dwl-bak/dwl-patches/patches/stacker/README.md new file mode 100644 index 0000000..c7778a0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/stacker/README.md @@ -0,0 +1,28 @@ +### Description +Stacker is a patch that allows moving around the stack more freely. With only +one keybinding, quickly move, swap and jump around the window stack. + +1. Focus any window of the stack with a single key binding. +2. Swap the currently focused windows with any other window in the stack. +3. Move the selected window in the stack with `relativeswap`. + +This patch is heavily inspired by the original [stacker](https://dwm.suckless.org/patches/stacker/) dwm patch. + +### Keybinding + +- `MODKEY` + {`q`, `w`, `e`, `r`}: jump to the first, second, third and last + window of the stack +- `MODKEY` + `SHIFT` + {`Q`, `W`, `E`, `R`}: swap the selected with the first, second, third and last + window of the stack +- `MODKEY` + `SHIFT` + {`J`, `K`}: move the selected window up & down the stack + +### Missing feature + +Jumping to the last selected window is not yet implemented. + +### Download +- [git branch](https://codeberg.org/jeromecst/dwl/src/branch/stacker) +- [2024-05-17](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/stacker/stacker.patch) + +### Authors +- [jeromecst](https://codeberg.org/jeromecst) diff --git a/dwl-bak/dwl-patches/patches/stacker/stacker.patch b/dwl-bak/dwl-patches/patches/stacker/stacker.patch new file mode 100644 index 0000000..dba1cb0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/stacker/stacker.patch @@ -0,0 +1,212 @@ +From b8f39c0710eb3cfdf4c619f532222d356ec58140 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?J=C3=A9r=C3=B4me?= +Date: Fri, 17 May 2024 23:06:19 +0200 +Subject: [PATCH] stacker + +--- + config.def.h | 10 +++++ + dwl.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 114 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8f498d2..44cbcd8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -120,10 +120,20 @@ static const char *menucmd[] = { "wmenu-run", NULL }; + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ ++ { MODKEY, XKB_KEY_q, focusto, {.i = 0} }, ++ { MODKEY, XKB_KEY_w, focusto, {.i = 1} }, ++ { MODKEY, XKB_KEY_e, focusto, {.i = 2} }, ++ { MODKEY, XKB_KEY_r, focusto, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, swapstack, {.i = 0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_W, swapstack, {.i = 1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, swapstack, {.i = 2} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, swapstack, {.i = -1} }, + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, relativeswap, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, relativeswap, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index bf763df..be88c6d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -279,6 +279,7 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); ++static void focusto(const Arg *arg); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); +@@ -308,6 +309,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); + static void quit(const Arg *arg); ++static void relativeswap(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -327,6 +329,8 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static void swapstack(const Arg *arg); ++static void relativeswap(const Arg *arg); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); +@@ -343,9 +347,11 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static Client *nextvisible(int i, struct wl_list *from, Monitor *m); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); ++static void wl_list_swap(struct wl_list *a, struct wl_list *b); + static void zoom(const Arg *arg); + + /* variables */ +@@ -1308,6 +1314,21 @@ focusclient(Client *c, int lift) + client_activate_surface(client_surface(c), 1); + } + ++void ++focusto(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ int i; ++ if (!sel || (sel->isfullscreen && !client_has_children(sel))) ++ return; ++ ++ i = arg->i > -1 ? arg->i + 1 : arg->i; ++ ++ c = nextvisible(i, &clients, selmon); ++ if (c) ++ focusclient(c , 1); ++} ++ + void + focusmon(const Arg *arg) + { +@@ -1951,6 +1972,24 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++relativeswap(const Arg *arg) ++{ ++ Client *trgt, *sel = focustop(selmon); ++ ++ if (!sel || !selmon) ++ return; ++ ++ trgt = nextvisible(arg->i, &sel->link, selmon); ++ if (!trgt || trgt == sel) ++ return; ++ ++ wl_list_swap(&sel->link, &trgt->link); ++ ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +@@ -2548,6 +2587,26 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++void ++swapstack(const Arg *arg) ++{ ++ Client *trgt, *sel = focustop(selmon); ++ int i; ++ ++ if (!sel || !selmon) ++ return; ++ ++ i = arg->i > -1 ? arg->i + 1 : arg->i; ++ trgt = nextvisible(i, &clients, selmon); ++ if (!trgt || trgt == sel) ++ return; ++ ++ wl_list_swap(&sel->link, &trgt->link); ++ ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ + void + tag(const Arg *arg) + { +@@ -2858,6 +2917,28 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++Client * ++nextvisible(int i, struct wl_list *from, Monitor *m) ++{ ++ Client *c; ++ if (i >= 0){ ++ wl_list_for_each(c, from, link) { ++ // if (VISIBLEON(c , m) && &c->link != from && i--) ++ if (VISIBLEON(c , m)) { ++ if (--i == 0) ++ return c; ++ } ++ } ++ } else if (i < 0) { ++ wl_list_for_each_reverse(c, from, link) { ++ if (VISIBLEON(c , m)) ++ if (++i == 0) ++ return c; ++ } ++ } ++ return NULL; ++} ++ + Monitor * + xytomon(double x, double y) + { +@@ -2865,6 +2946,29 @@ xytomon(double x, double y) + return o ? o->data : NULL; + } + ++void ++wl_list_swap(struct wl_list *a, struct wl_list *b) ++{ ++ struct wl_list *prev_a = a->prev; ++ struct wl_list *prev_b = b->prev; ++ ++ if (prev_b == a) { ++ wl_list_remove(a); ++ wl_list_insert(b, a); ++ return; ++ } ++ if (prev_a == b) { ++ wl_list_remove(b); ++ wl_list_insert(a, b); ++ return; ++ } ++ wl_list_remove(a); ++ wl_list_insert(prev_b, a); ++ ++ wl_list_remove(b); ++ wl_list_insert(prev_a, b); ++} ++ + void + xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/stairs/README.md b/dwl-bak/dwl-patches/patches/stairs/README.md new file mode 100644 index 0000000..403a276 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/stairs/README.md @@ -0,0 +1,42 @@ +### Description +Port of the [stairs](https://dwm.suckless.org/patches/stairs/) patch from dwm. + +The remaining text is from the description on the dwm site: + +This layout can be modified using the 3 variables provided in config.h: stairpx, stairdirection and stairsamesize. + +``` ++-------------+--------+----------------+ +| | | | +| | +--| S1 | +| | |S2| | +| | +--| | | +| | |S3| | | +| M |--| | +----------------+ +| |S4| | | | +| | | +-----------------+ | +| | | | | +| | +------------------+ | +| | | | ++-------------+-------------------+-----+ + stairsamesize = 1 + ++-------------+--------+----------------+ +| | | | +| | +--| S1 | +| | |S2| | +| | +--| | | +| | |S3| | | +| M |--| | | | +| |S4| | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | ++-------------+--+--+--+----------------+ + stairsamesize = 0 +``` +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/stairs/stairs.patch) +### Authors +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl-bak/dwl-patches/patches/stairs/stairs.patch b/dwl-bak/dwl-patches/patches/stairs/stairs.patch new file mode 100644 index 0000000..d39932e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/stairs/stairs.patch @@ -0,0 +1,136 @@ +From d5bbe6d54d044fa11c76e2e20b96a44897319424 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 20:51:06 +0100 +Subject: [PATCH 1/2] Fix crash disabling monitor with locked surface + +(cherry picked from commit d1880b44223701c91b51b319fc69a0f63044f861) +--- + dwl.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dwl.c b/dwl.c +index cf3ef70..c717c1d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -716,6 +716,8 @@ cleanupmon(struct wl_listener *listener, void *data) + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); ++ if (m->lock_surface) ++ destroylocksurface(&m->destroy_lock_surface, NULL); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +-- +2.50.1 + + +From b6e96ab6281eddacdda26c39e6446b72ce05de39 Mon Sep 17 00:00:00 2001 +From: JustinWayland +Date: Fri, 6 Jun 2025 22:14:31 -0400 +Subject: [PATCH 2/2] Add stairs layout. + +--- + config.def.h | 5 +++++ + dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 53 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..e81568c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,9 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int stairpx = 20; /* depth of the stairs layout */ ++static const int stairdirection = 1; /* 0: left-aligned, 1: right-aligned */ ++static const int stairsamesize = 1; /* 1 means shrink all the staired windows to the same size */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -34,6 +37,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "[S]", stairs }, + }; + + /* monitors */ +@@ -139,6 +143,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index cf3ef70..4debf15 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -334,6 +334,7 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void stairs(Monitor* m); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -2657,6 +2658,53 @@ spawn(const Arg *arg) + } + } + ++void ++stairs(Monitor *m) ++{ ++ int i, n, h, mw, my; ++ int ox, oy, ow, oh; ++ Client *c; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; ++ else ++ mw = m->w.width; ++ ++ i = my = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c,m) || c->isfloating || c->fullscreen) ++ continue; ++ if (i < m->nmaster) { ++ h = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box){ ++ .x=m->w.x,.y=m->w.y + my,.width=mw,.height=h ++ }, 0); ++ my += c->geom.height; ++ } else { ++ oy = i - m->nmaster; ++ ox = stairdirection ? n - i - 1 : (stairsamesize ? i - m->nmaster: 0); ++ ow = stairsamesize ? n - m->nmaster - 1 : n - i - 1; ++ oh = stairsamesize ? ow : i - m->nmaster; ++ resize(c, (struct wlr_box){ ++ .x=m->w.x + mw + (ox * stairpx), ++ .y=m->w.y + (oy * stairpx), ++ .width=m->w.width - mw - (ow * stairpx), ++ .height=m->w.height - (oh * stairpx) ++ }, 0); ++ if (c == focustop(selmon)) ++ wlr_scene_node_raise_to_top(&c->scene->node); ++ } ++ i++; ++ } ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +-- +2.50.1 + diff --git a/dwl-bak/dwl-patches/patches/startargv/README.md b/dwl-bak/dwl-patches/patches/startargv/README.md new file mode 100644 index 0000000..bc15225 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/startargv/README.md @@ -0,0 +1,13 @@ +### Description +allow passing startup command on argv + +e.g. `dwl -s foot -s` launches `foot -s` + +put `sh -c` right after `dwl -s` to emulate normal behaviour + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/startargv) +- [2024-07-03](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/startargv/startargv.patch) + +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/startargv/startargv.patch b/dwl-bak/dwl-patches/patches/startargv/startargv.patch new file mode 100644 index 0000000..5697b9c --- /dev/null +++ b/dwl-bak/dwl-patches/patches/startargv/startargv.patch @@ -0,0 +1,58 @@ +From a823f7923f8d2006eedc013ca1c1ba7a496543d5 Mon Sep 17 00:00:00 2001 +From: choc +Date: Wed, 3 Jul 2024 11:21:33 +0800 +Subject: [PATCH] startargv: allow passing startup command on argv + +--- + dwl.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 9fb50a7..43b1ac1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -320,7 +320,7 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); +-static void run(char *startup_cmd); ++static void run(char **startup_cmd); + 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); +@@ -2158,7 +2158,7 @@ resize(Client *c, struct wlr_box geo, int interact) + } + + void +-run(char *startup_cmd) ++run(char **startup_cmd) + { + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); +@@ -2183,7 +2183,7 @@ run(char *startup_cmd) + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); + close(piperw[1]); +- execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); ++ execvp(startup_cmd[0], startup_cmd); + die("startup: execl:"); + } + dup2(piperw[1], STDOUT_FILENO); +@@ -3124,12 +3124,12 @@ xwaylandready(struct wl_listener *listener, void *data) + int + main(int argc, char *argv[]) + { +- char *startup_cmd = NULL; ++ char **startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') +- startup_cmd = optarg; ++ *(startup_cmd = &argv[optind-1]) = optarg, argc = 0; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/sticky/README.md b/dwl-bak/dwl-patches/patches/sticky/README.md new file mode 100644 index 0000000..af0e81f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/sticky/README.md @@ -0,0 +1,13 @@ +### Description +Adds a toggleable function that makes a sticky client that is visible on all tags. + +Originally based on [dwm sticky patch](https://dwm.suckless.org/patches/sticky). + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch) +- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-sticky.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/sticky) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [Dmitry Zakharchenko](https://github.com/dm1tz) diff --git a/dwl-bak/dwl-patches/patches/sticky/sticky.patch b/dwl-bak/dwl-patches/patches/sticky/sticky.patch new file mode 100644 index 0000000..ece8308 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/sticky/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/dwl-bak/dwl-patches/patches/swallow/README.md b/dwl-bak/dwl-patches/patches/swallow/README.md new file mode 100644 index 0000000..6c16162 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/swallow/README.md @@ -0,0 +1,53 @@ +### Description + +This patch adds "window swallowing" to dwl. + +If a user runs a graphical program from the terminal (e.g., `mpv`), the terminal +will be hidden and only a window of the newly spawned graphical program will +be visible. The terminal stays hidden until the graphical program is closed. +This patch helps users spawning a lot of graphical programs from their command +line by avoiding cluttering the screen with many unusable terminals. + +`foot` is the terminal by default, you can change it in client rules in config.h. + +In `2025-03-03 v0.7` version and above, the patch had been rewritten from +scratch to make it more robust and add a few more features: + +- "dynamically swallow" windows by pressing `Alt+a` (a focused window will + swallow/unswallow the previously focused one) +- toggle automatic swallowing by `Alt+Shift+a` +- if a window has swallowed another, it get thicker borders + +### Download + +#### swallow.patch + +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow.patch) +- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/2e5748edfe1129f95c7bb1bf9dd590a897f55f57/patches/swallow/swallow.patch) (added "dynamic swallowing" support) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow.patch) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f64d701bab2f9f52d3637edd091684f920407d87/patches/swallow/swallow.patch) +- [2024-05-02](https://codeberg.org/dwl/dwl-patches/raw/commit/9c5d5d85f3ac780e7a14d5d0535e3349ce8b8f53/patches/swallow/swallow.patch) +- [2024-04-03](https://codeberg.org/dwl/dwl-patches/raw/commit/3c9a8e3232a8531871924484cef1ef0938730e15/swallow/swallow.patch) +- [2024-01-01](https://codeberg.org/dwl/dwl-patches/raw/commit/8a352a1b27a64821ba9fbfda52fe82463ac84c66/swallow/swallow.patch) +- [2023-10-26](https://github.com/djpohly/dwl/compare/main...youbitchoc:swallow.patch) +- [2023-08-16](https://github.com/djpohly/dwl/compare/main...mewkl:swallowx.patch) (added XWayland support) +- [2023-07-15](https://github.com/djpohly/dwl/compare/main...NikitaIvanovV:swallow.patch) +- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-swallow.patch) +- [2021-12-03](https://github.com/djpohly/dwl/compare/main...dm1tz:swallow.patch) + +#### swallow-freebsd.patch + +Apply this patch on top of the swallow.patch if you use FreeBSD. + +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow-freebsd.patch) +- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/2e5748edfe1129f95c7bb1bf9dd590a897f55f57/patches/swallow/swallow-freebsd.patch) (added "dynamic swallowing" support) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow-freebsd.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [Dmitry Zakharchenko](https://github.com/dm1tz) +- [Palanix](https://codeberg.org/Palanix) +- [Connor Worrell](https://github.com/ConnorWorrell) +- [Mewkl](https://github.com/mewkl) +- [Choc](https://codeberg.org/notchoc) diff --git a/dwl-bak/dwl-patches/patches/swallow/swallow-freebsd.patch b/dwl-bak/dwl-patches/patches/swallow/swallow-freebsd.patch new file mode 100644 index 0000000..795a5e1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/swallow/swallow-freebsd.patch @@ -0,0 +1,54 @@ +From 301e1b368d92a32a7bdcd4bd1f0ede0295977e3e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Mon, 3 Mar 2025 19:49:07 +0100 +Subject: [PATCH] swallow: add FreeBSD support + +--- + dwl.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 71d500a..52fdd9c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -67,6 +67,14 @@ + #include + #endif + ++#ifdef __FreeBSD__ ++#define __BSD_VISIBLE ++#include ++#include ++#include ++#include ++#endif ++ + #include "util.h" + + /* macros */ +@@ -2032,6 +2040,7 @@ outputmgrtest(struct wl_listener *listener, void *data) + pid_t + parentpid(pid_t pid) + { ++#ifdef __linux__ + unsigned int v = 0; + FILE *f; + char buf[256]; +@@ -2041,6 +2050,14 @@ parentpid(pid_t pid) + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); + return (pid_t)v; ++#elif defined(__FreeBSD__) ++ struct kinfo_proc kip; ++ size_t len = sizeof(struct kinfo_proc); ++ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, p }; ++ if (sysctl(mib, 4, &kip, &len, NULL, 0) < 0 || len == 0) ++ return 0; ++ return kip.ki_ppid; ++#endif + } + + void +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/swallow/swallow.patch b/dwl-bak/dwl-patches/patches/swallow/swallow.patch new file mode 100644 index 0000000..ee1a1c1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/swallow/swallow.patch @@ -0,0 +1,351 @@ +From a220e1ed4b04a66c837dfc8e3363d3e696cbf541 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Wed, 5 Feb 2025 02:34:39 +0100 +Subject: [PATCH] Swallow: hide the terminal when it spawns a client + +--- + client.h | 12 ++++ + config.def.h | 11 +++- + dwl.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 168 insertions(+), 7 deletions(-) + +diff --git a/client.h b/client.h +index 42f225f..bc9cad2 100644 +--- a/client.h ++++ b/client.h +@@ -131,6 +131,18 @@ client_get_appid(Client *c) + return c->surface.xdg->toplevel->app_id; + } + ++static inline int ++client_get_pid(Client *c) ++{ ++ pid_t pid; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) ++ return c->surface.xwayland->pid; ++#endif ++ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); ++ return pid; ++} ++ + static inline void + client_get_clip(Client *c, struct wlr_box *clip) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..42342f1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,8 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static int enableautoswallow = 1; /* enables autoswallowing newly spawned clients */ ++static float swallowborder = 1.0f; /* add this multiplied by borderpx to border when a client is swallowed */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +24,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating isterm noswallow monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "foot", NULL, 0, 0, 1, 1, -1 }, ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, 0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +@@ -142,6 +145,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_a, toggleswallow, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, toggleautoswallow,{0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index def2562..71d500a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -73,12 +73,13 @@ + #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)->swallowedby) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define BORDERPX(C) (borderpx + ((C)->swallowing ? (int)ceilf(swallowborder * (C)->swallowing->bw) : 0)) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -104,7 +105,8 @@ typedef struct { + } Button; + + typedef struct Monitor Monitor; +-typedef struct { ++typedef struct Client Client; ++struct Client { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ +@@ -140,8 +142,12 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int isterm, noswallow; + uint32_t resize; /* configure serial of a pending resize */ +-} Client; ++ pid_t pid; ++ Client *swallowing; /* client being hidden */ ++ Client *swallowedby; ++}; + + typedef struct { + uint32_t mod; +@@ -230,6 +236,8 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int isterm; ++ int noswallow; + int monitor; + } Rule; + +@@ -311,6 +319,7 @@ static void moveresize(const Arg *arg); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); ++static pid_t parentpid(pid_t pid); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); +@@ -335,11 +344,15 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static void swallow(Client *c, Client *toswallow); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static Client *termforwin(Client *c); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void toggleswallow(const Arg *arg); ++static void toggleautoswallow(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -466,11 +479,15 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ c->pid = client_get_pid(c); ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; ++ c->isterm = r->isterm; ++ c->noswallow = r->noswallow; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) +@@ -478,6 +495,12 @@ applyrules(Client *c) + } + } + } ++ if (enableautoswallow && !c->noswallow && !c->isfloating && ++ !c->surface.xdg->initial_commit) { ++ Client *p = termforwin(c); ++ if (p) ++ swallow(c, p); ++ } + setmon(c, mon, newtags); + } + +@@ -2006,6 +2029,20 @@ outputmgrtest(struct wl_listener *listener, void *data) + outputmgrapplyortest(config, 1); + } + ++pid_t ++parentpid(pid_t pid) ++{ ++ unsigned int v = 0; ++ FILE *f; ++ char buf[256]; ++ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)pid); ++ if (!(f = fopen(buf, "r"))) ++ return 0; ++ fscanf(f, "%*u %*s %*c %u", &v); ++ fclose(f); ++ return (pid_t)v; ++} ++ + void + pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +@@ -2326,7 +2363,7 @@ setfullscreen(Client *c, int fullscreen) + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; +- c->bw = fullscreen ? 0 : borderpx; ++ c->bw = fullscreen ? 0 : BORDERPX(c); + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); +@@ -2404,6 +2441,9 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); ++ ++ if (c->swallowing) ++ setmon(c->swallowing, m, newtags); + } + + void +@@ -2669,6 +2709,44 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++void ++swallow(Client *c, Client *toswallow) ++{ ++ /* Do not allow a client to swallow itself */ ++ if (c == toswallow) ++ return; ++ ++ /* Swallow */ ++ if (toswallow && !c->swallowing) { ++ c->swallowing = toswallow; ++ toswallow->swallowedby = c; ++ toswallow->mon = c->mon; ++ toswallow->mon = c->mon; ++ wl_list_remove(&c->link); ++ wl_list_insert(&c->swallowing->link, &c->link); ++ wl_list_remove(&c->flink); ++ wl_list_insert(&c->swallowing->flink, &c->flink); ++ c->bw = BORDERPX(c); ++ c->tags = toswallow->tags; ++ c->isfloating = toswallow->isfloating; ++ c->geom = toswallow->geom; ++ setfullscreen(toswallow, 0); ++ } ++ ++ /* Unswallow */ ++ else if (c->swallowing) { ++ wl_list_remove(&c->swallowing->link); ++ wl_list_insert(&c->link, &c->swallowing->link); ++ wl_list_remove(&c->swallowing->flink); ++ wl_list_insert(&c->flink, &c->swallowing->flink); ++ c->swallowing->tags = c->tags; ++ c->swallowing->swallowedby = NULL; ++ c->swallowing = NULL; ++ c->bw = BORDERPX(c); ++ setfullscreen(c, 0); ++ } ++} ++ + void + tag(const Arg *arg) + { +@@ -2690,6 +2768,40 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++Client * ++termforwin(Client *c) ++{ ++ Client *p; ++ pid_t pid; ++ pid_t pids[32]; ++ size_t i, pids_len; ++ ++ if (!c->pid || c->isterm) ++ return NULL; ++ ++ /* Get all parent pids */ ++ pids_len = 0; ++ pid = c->pid; ++ while (pids_len < LENGTH(pids)) { ++ pid = parentpid(pid); ++ if (!pid) ++ break; ++ pids[pids_len++] = pid; ++ } ++ ++ /* Find closest parent */ ++ for (i = 0; i < pids_len; i++) { ++ wl_list_for_each(p, &clients, link) { ++ if (!p->pid || !p->isterm || p->swallowedby) ++ continue; ++ if (pids[i] == p->pid) ++ return p; ++ } ++ } ++ ++ return NULL; ++} ++ + void + tile(Monitor *m) + { +@@ -2741,6 +2853,32 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++toggleswallow(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ if (!sel) ++ return; ++ ++ if (sel->swallowing) { ++ swallow(sel, NULL); ++ } else { ++ wl_list_for_each(c, &sel->flink, flink) { ++ if (&c->flink == &fstack) ++ continue; /* wrap past the sentinel node */ ++ if (VISIBLEON(c, selmon)) ++ break; /* found it */ ++ } ++ swallow(sel, c); ++ } ++} ++ ++void ++toggleautoswallow(const Arg *arg) ++{ ++ enableautoswallow = !enableautoswallow; ++} ++ + void + toggletag(const Arg *arg) + { +@@ -2801,6 +2939,12 @@ unmapnotify(struct wl_listener *listener, void *data) + grabc = NULL; + } + ++ if (c->swallowing) { ++ swallow(c, NULL); ++ } else if (c->swallowedby) { ++ swallow(c->swallowedby, NULL); ++ } ++ + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/swapandfocusdir/README.md b/dwl-bak/dwl-patches/patches/swapandfocusdir/README.md new file mode 100644 index 0000000..2861a07 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/swapandfocusdir/README.md @@ -0,0 +1,14 @@ +### Description +Focus the window (floating or no) to the left, right, above, or below the current focused window. + +Swap the focused window with the window (no floating) to the left, right, above, or below. + +**NOTE:** this patch uses the same algorithm that River uses to select the window in the given direction. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/swapandfocusdir) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/swapandfocusdir/swapandfocusdir.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swapandfocusdir/swapandfocusdir.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl-bak/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch b/dwl-bak/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch new file mode 100644 index 0000000..3636387 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch @@ -0,0 +1,221 @@ +From 285470897406b653e77d732a77356aaf9a70b799 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 12:37:39 -0500 +Subject: [PATCH] implement swapandfocusdir + +--- + config.def.h | 8 +++ + dwl.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 172 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..724e15e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,14 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, focusdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, focusdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, focusdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, focusdir, {.ui = 3} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapdir, {.ui = 3} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index dc0437e..844c1f5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -241,6 +241,11 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ int x; ++ int y; ++} Vector; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -285,6 +290,8 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); ++static void focusdir(const Arg *arg); ++static void swapdir(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -1425,6 +1432,163 @@ focusstack(const Arg *arg) + focusclient(c, 1); + } + ++Vector ++position_of_box(const struct wlr_box *box) ++{ ++ return (Vector){ ++ .x = box->x + box->width / 2, ++ .y = box->y + box->height / 2, ++ }; ++} ++ ++Vector ++diff_of_vectors(Vector *a, Vector *b) ++{ ++ return (Vector){ ++ .x = b->x - a->x, ++ .y = b->y - a->y, ++ }; ++} ++ ++const char * ++direction_of_vector(Vector *vector) ++{ ++ // A zero length vector has no direction ++ if (vector->x == 0 && vector->y == 0) return ""; ++ ++ if (abs(vector->y) > abs(vector->x)) { ++ // Careful: We are operating in a Y-inverted coordinate system. ++ return (vector->y > 0) ? "bottom" : "top"; ++ } else { ++ return (vector->x > 0) ? "right" : "left"; ++ } ++} ++ ++uint32_t ++vector_length(Vector *vector) ++{ ++ // Euclidean distance formula ++ return (uint32_t)sqrt(vector->x * vector->x + vector->y * vector->y); ++} ++ ++// Spatial direction, based on focused client position. ++Client * ++client_in_direction(const char *direction, const int *skipfloat) ++{ ++ Client *cfocused = focustop(selmon); ++ Vector cfocusedposition; ++ Client *ctarget = NULL; ++ double targetdistance = INFINITY; ++ Client *c; ++ ++ if (!cfocused || cfocused->isfullscreen || (skipfloat && cfocused->isfloating)) ++ return NULL; ++ ++ cfocusedposition = position_of_box(&cfocused->geom); ++ ++ wl_list_for_each(c, &clients, link) { ++ Vector cposition; ++ Vector positiondiff; ++ uint32_t distance; ++ ++ if (c == cfocused) ++ continue; ++ ++ if (skipfloat && c->isfloating) ++ continue; ++ ++ if (!VISIBLEON(c, selmon)) ++ continue; ++ ++ cposition = position_of_box(&c->geom); ++ positiondiff = diff_of_vectors(&cfocusedposition, &cposition); ++ ++ if (strcmp(direction, direction_of_vector(&positiondiff)) != 0) ++ continue; ++ ++ distance = vector_length(&positiondiff); ++ ++ if (distance < targetdistance) { ++ ctarget = c; ++ targetdistance = distance; ++ } ++ } ++ ++ return ctarget; ++} ++ ++void ++focusdir(const Arg *arg) ++{ ++ Client *c = NULL; ++ ++ if (arg->ui == 0) ++ c = client_in_direction("left", (int *)0); ++ if (arg->ui == 1) ++ c = client_in_direction("right", (int *)0); ++ if (arg->ui == 2) ++ c = client_in_direction("top", (int *)0); ++ if (arg->ui == 3) ++ c = client_in_direction("bottom", (int *)0); ++ ++ if (c != NULL) ++ focusclient(c, 1); ++} ++ ++void ++wl_list_swap(struct wl_list *list1, struct wl_list *list2) ++{ ++ struct wl_list *prev1, *next1, *prev2, *next2; ++ struct wl_list temp; ++ ++ if (list1 == list2) { ++ // No need to swap the same list ++ return; ++ } ++ ++ // Get the lists before and after list1 ++ prev1 = list1->prev; ++ next1 = list1->next; ++ ++ // Get the lists before and after list2 ++ prev2 = list2->prev; ++ next2 = list2->next; ++ ++ // Update the next and previous pointers of adjacent lists ++ prev1->next = list2; ++ next1->prev = list2; ++ prev2->next = list1; ++ next2->prev = list1; ++ ++ // Swap the next and previous pointers of the lists to actually swap them ++ temp = *list1; ++ *list1 = *list2; ++ *list2 = temp; ++} ++ ++void ++swapdir(const Arg *arg) ++{ ++ Client *c = NULL; ++ Client *cfocused; ++ ++ if (arg->ui == 0) ++ c = client_in_direction("left", (int *)1); ++ if (arg->ui == 1) ++ c = client_in_direction("right", (int *)1); ++ if (arg->ui == 2) ++ c = client_in_direction("top", (int *)1); ++ if (arg->ui == 3) ++ c = client_in_direction("bottom", (int *)1); ++ ++ if (c == NULL) ++ return; ++ ++ cfocused = focustop(selmon); ++ wl_list_swap(&cfocused->link, &c->link); ++ arrange(selmon); ++} ++ + /* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/switchtotag/README.md b/dwl-bak/dwl-patches/patches/switchtotag/README.md new file mode 100644 index 0000000..55175d1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/switchtotag/README.md @@ -0,0 +1,9 @@ +### Description +Add a rule option to switch to the configured tag when a window opens, then switch back when it closes. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/switchtotag) +- [2024-09-30](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/switchtotag/switchtotag.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl-bak/dwl-patches/patches/switchtotag/switchtotag.patch b/dwl-bak/dwl-patches/patches/switchtotag/switchtotag.patch new file mode 100644 index 0000000..bbe1638 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/switchtotag/switchtotag.patch @@ -0,0 +1,116 @@ +From 08cfc2eceb34316cfd7eaf591a8a631e3d58ff3a Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Mon, 30 Sep 2024 08:40:25 +0200 +Subject: [PATCH] allow switching to the configured tag when a window opens + +Add a rule option to switch to the configured tag when a window opens, +then switch back when it closes. +--- + config.def.h | 6 +++--- + dwl.c | 23 +++++++++++++++++++---- + 2 files changed, 22 insertions(+), 7 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..52ea128 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask switchtotag isfloating monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 1, 0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0c861..621b614 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -142,6 +142,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int switchtotag; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -230,6 +231,7 @@ typedef struct { + const char *id; + const char *title; + uint32_t tags; ++ bool switchtotag; + int isfloating; + int monitor; + } Rule; +@@ -245,7 +247,7 @@ typedef struct { + + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); +-static void applyrules(Client *c); ++static void applyrules(Client *c, bool map); + static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +@@ -449,7 +451,7 @@ applybounds(Client *c, struct wlr_box *bbox) + } + + void +-applyrules(Client *c) ++applyrules(Client *c, bool map) + { + /* rule matching */ + const char *appid, *title; +@@ -472,6 +474,11 @@ applyrules(Client *c) + if (r->monitor == i++) + mon = m; + } ++ if (r->switchtotag && map) { ++ c->switchtotag = selmon->tagset[selmon->seltags]; ++ mon->seltags ^= 1; ++ mon->tagset[selmon->seltags] = r->tags & TAGMASK; ++ } + } + } + setmon(c, mon, newtags); +@@ -795,7 +802,7 @@ commitnotify(struct wl_listener *listener, void *data) + * a different monitor based on its title this will likely select + * a wrong monitor. + */ +- applyrules(c); ++ applyrules(c, false); + if (c->mon) { + client_set_scale(client_surface(c), c->mon->wlr_output->scale); + } +@@ -1733,7 +1740,7 @@ mapnotify(struct wl_listener *listener, void *data) + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { +- applyrules(c); ++ applyrules(c, true); + } + printstatus(); + +@@ -2769,6 +2776,14 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (c->switchtotag) { ++ Arg a = { .ui = c->switchtotag }; ++ // Call view() -> arrange() -> checkidleinhibitor() before ++ // wlr_scene_node_destroy() to prevent a rare use after free of ++ // tree->node. ++ view(&a); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/systemd/README.md b/dwl-bak/dwl-patches/patches/systemd/README.md new file mode 100644 index 0000000..a861b5e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/systemd/README.md @@ -0,0 +1,33 @@ +### Description +This is a simple patch that runs `systemctl --user import-environment WAYLAND_DISPLAY DISPLAY`, and `systemctl --user start dwl-session.target` after Dwl initializes, and then `systemctl --user stop dwl-session.target` when Dwl quits. This allows you to handle graceful startup and shutdown of your graphical systemd services, with the proper environment variables set. This is similar to [uwsm](https://github.com/Vladimir-csp/uwsm?tab=readme-ov-file), but it integrates natively with your existing systemd user services, and doesn't have uwsm's runtime overhead. + +You must have a `dwl-session.target` created that binds to `graphical-session.target`. You can then set the WantedBy of your systemd services to either `graphical-session.target`, or `dwl-session.target`, depending on whether you want them to start for just Dwl, or for every graphical session. + +Below is the Nix home-manager configuration I use to generate `dwl-session.target`, and its resulting output. +``` +systemd.user.targets.dwl-session.Unit = { + Description = "dwl compositor session"; + Documentation = [ "man:systemd.special(7)" ]; + BindsTo = [ "graphical-session.target" ]; + Wants = [ "graphical-session-pre.target" ]; + After = [ "graphical-session-pre.target" ]; +}; +``` + +``` +[Unit] +After=graphical-session-pre.target +BindsTo=graphical-session.target +Description=dwl compositor session +Documentation=man:systemd.special(7) +Wants=graphical-session-pre.target +``` + +### Download +- [git branch](https://github.com/Shringe/dwl/tree/systemd) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/systemd/systemd-0.7.patch) + +### Authors +- [Shringe](https://codeberg.org/Shringe) +- shringe_ at [dwl Discord](https://discord.gg/jJxZnrGPWN) + diff --git a/dwl-bak/dwl-patches/patches/systemd/systemd-0.7.patch b/dwl-bak/dwl-patches/patches/systemd/systemd-0.7.patch new file mode 100644 index 0000000..f911997 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/systemd/systemd-0.7.patch @@ -0,0 +1,64 @@ +From 43b3026b0744d54287eada71c84ea8be174950c3 Mon Sep 17 00:00:00 2001 +From: Shringe +Date: Sat, 12 Jul 2025 01:00:46 -0500 +Subject: [PATCH] basic implementation + +--- + dwl.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/dwl.c b/dwl.c +index c717c1d..4ba134d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -680,6 +680,15 @@ cleanup(void) + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif ++ /* Stop systemd target */ ++ if (fork() == 0) { ++ setsid(); ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "stop", "dwl-session.target", NULL ++ }); ++ exit(1); ++ } ++ + wl_display_destroy_clients(dpy); + if (child_pid > 0) { + kill(-child_pid, SIGTERM); +@@ -2222,6 +2231,31 @@ run(char *startup_cmd) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + ++ /* Import environment variables then start systemd target */ ++ if (fork() == 0) { ++ setsid(); ++ ++ /* First: import environment variables */ ++ pid_t import_pid = fork(); ++ if (import_pid == 0) { ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "import-environment", ++ "DISPLAY", "WAYLAND_DISPLAY", NULL ++ }); ++ exit(1); ++ } ++ ++ /* Wait for import to complete */ ++ waitpid(import_pid, NULL, 0); ++ ++ /* Second: start target */ ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "start", "dwl-session.target", NULL ++ }); ++ ++ exit(1); ++ } ++ + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/tab/README.md b/dwl-bak/dwl-patches/patches/tab/README.md new file mode 100644 index 0000000..9f08765 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tab/README.md @@ -0,0 +1,24 @@ +### Description +Add tabs or a title bar to the top or bottom of windows. + +Much of the code was taken from the [bar patch](/dwl/dwl-patches/src/branch/main/patches/bar). + +**This is the new version using `drwl`. The [old version](/dwl/dwl-patches/src/branch/main/_STALE_PATCHES/tab-pango) is deprecated.** + +### Dependencies +- tllist (build dependency, required & pulled automatically by fcft) +- fcft +- pixman + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tab/tab-0.7.patch) +- [0.7 + bar](/dwl/dwl-patches/raw/branch/main/patches/tab/tab-0.7-bar.patch) +- [main 2024-11-15](/dwl/dwl-patches/raw/branch/main/patches/tab/tab.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) + +### Credits +- [sewn](https://codeberg.org/sewn) +- [MadcowOG](https://codeberg.org/MadcowOG) +- [kolumni](https://codeberg.org/kolumni) diff --git a/dwl-bak/dwl-patches/patches/tab/tab-0.7-bar.patch b/dwl-bak/dwl-patches/patches/tab/tab-0.7-bar.patch new file mode 100644 index 0000000..4a79bbc --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tab/tab-0.7-bar.patch @@ -0,0 +1,767 @@ +From 3944cc81d0683700405cd106edf965293abbb87c Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:27:19 -0500 +Subject: [PATCH] Add tab support + +--- + config.def.h | 25 +++- + dwl.c | 392 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 386 insertions(+), 31 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..52fcfee 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,13 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -20,6 +27,13 @@ static uint32_t colors[][3] = { + [SchemeUrg] = { 0, 0, 0x770000ff }, + }; + ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +@@ -34,12 +48,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/dwl.c b/dwl.c +index 1e199f3..e7754d3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -90,6 +91,7 @@ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -110,6 +112,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -119,6 +129,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + union { +@@ -146,7 +161,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -190,17 +205,11 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +-typedef struct { +- struct wlr_buffer base; +- struct wl_listener release; +- bool busy; +- Img *image; +- uint32_t data[]; +-} Buffer; +- + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; +@@ -275,10 +284,11 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +-static void bufdestroy(struct wlr_buffer *buffer); +-static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +-static void bufdataend(struct wlr_buffer *buffer); ++static void bufdataend(struct wlr_buffer *wlr_buffer); + static Buffer *bufmon(Monitor *m); + static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); +@@ -316,12 +326,15 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void drawbar(Monitor *m); + static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -529,14 +542,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -561,6 +576,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -650,6 +666,36 @@ baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) + return true; + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ + void + bufdestroy(struct wlr_buffer *wlr_buffer) + { +@@ -827,6 +873,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -1230,6 +1291,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1579,6 +1644,166 @@ drawbars(void) + drawbar(m); + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1635,6 +1860,12 @@ focusclient(Client *c, int lift) + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ + drawbars(); + + if (!c) { +@@ -1705,6 +1936,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1764,6 +2009,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1932,6 +2178,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1988,6 +2235,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2012,18 +2273,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2384,32 +2655,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2505,6 +2781,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2528,6 +2806,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + drawbars(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2553,6 +2832,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2568,6 +2848,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2898,14 +3179,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2979,6 +3268,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2992,6 +3283,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -3022,6 +3315,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -3038,6 +3334,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -3058,9 +3368,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -3124,6 +3437,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -3195,6 +3525,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -3208,6 +3539,7 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + drawbars(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +@@ -3322,8 +3654,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3375,6 +3709,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.47.0 + diff --git a/dwl-bak/dwl-patches/patches/tab/tab-0.7.patch b/dwl-bak/dwl-patches/patches/tab/tab-0.7.patch new file mode 100644 index 0000000..b7edd4f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tab/tab-0.7.patch @@ -0,0 +1,1252 @@ +From 419fa4d6212806d9e4a3439568f8a103a57de912 Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:19:42 -0500 +Subject: [PATCH] Add tab support + +--- + Makefile | 2 +- + config.def.h | 37 ++++- + drwl.h | 320 ++++++++++++++++++++++++++++++++++++ + dwl.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 777 insertions(+), 31 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 3358bae..9bc67db 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d33d89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,13 +7,31 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; ++ ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -28,12 +46,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..02eb8e9 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,320 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static unsigned int ++drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n) ++{ ++ unsigned int tmp = 0; ++ if (drwl && drwl->font && text && n) ++ tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n); ++ return tmp < n ? tmp : n; ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index def2562..893b1f1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -12,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +60,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -68,6 +71,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -81,9 +85,11 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -103,6 +109,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -112,6 +126,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + union { +@@ -139,7 +158,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -183,6 +202,8 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +@@ -250,6 +271,12 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *wlr_buffer); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -285,10 +312,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -413,6 +443,12 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -485,14 +521,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -517,6 +555,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -595,6 +634,74 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { +@@ -676,6 +783,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -697,6 +819,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -1063,6 +1187,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1338,6 +1466,166 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1373,13 +1661,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1390,11 +1678,18 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ ++ + printstatus(); + + if (!c) { +@@ -1465,6 +1760,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1524,6 +1833,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1692,6 +2002,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1717,7 +2028,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1740,6 +2051,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ + printstatus(); + + unset_fullscreen: +@@ -1748,6 +2060,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -1772,18 +2098,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2184,32 +2520,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2318,6 +2659,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2341,6 +2684,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2366,6 +2710,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2381,6 +2726,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2627,6 +2973,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2680,14 +3028,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2753,6 +3109,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2766,6 +3124,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2796,6 +3156,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -2812,6 +3175,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2832,9 +3209,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -2898,6 +3278,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -2929,6 +3326,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2942,9 +3340,10 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + printstatus(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -3052,8 +3451,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3105,6 +3506,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +@@ -3150,7 +3555,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.47.0 + diff --git a/dwl-bak/dwl-patches/patches/tab/tab.patch b/dwl-bak/dwl-patches/patches/tab/tab.patch new file mode 100644 index 0000000..49276ad --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tab/tab.patch @@ -0,0 +1,1252 @@ +From d722eef53d488c9e9ed04991112376035e8c9b25 Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:22:14 -0500 +Subject: [PATCH] Add tab support + +--- + Makefile | 2 +- + config.def.h | 37 ++++- + drwl.h | 320 ++++++++++++++++++++++++++++++++++++ + dwl.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 777 insertions(+), 31 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d33d89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,13 +7,31 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; ++ ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -28,12 +46,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..02eb8e9 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,320 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static unsigned int ++drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n) ++{ ++ unsigned int tmp = 0; ++ if (drwl && drwl->font && text && n) ++ tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n); ++ return tmp < n ? tmp : n; ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 9acb898..bde4009 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -12,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +61,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +72,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -82,9 +86,11 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -104,6 +110,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -113,6 +127,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + struct wlr_box geom; /* layout-relative, includes border */ +@@ -141,7 +160,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -184,6 +203,8 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +@@ -251,6 +272,12 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *wlr_buffer); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -286,10 +313,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -411,6 +441,12 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -481,14 +517,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -513,6 +551,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -589,6 +628,74 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { +@@ -670,6 +777,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -691,6 +813,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -1057,6 +1181,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1332,6 +1460,166 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1366,13 +1654,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1383,11 +1671,18 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ ++ + printstatus(); + + if (!c) { +@@ -1458,6 +1753,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1517,6 +1826,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1685,6 +1995,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1712,7 +2023,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1735,6 +2046,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ + printstatus(); + + unset_fullscreen: +@@ -1743,6 +2055,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -1767,18 +2093,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2150,32 +2486,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2284,6 +2625,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2307,6 +2650,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2321,6 +2665,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2336,6 +2681,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2585,6 +2931,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2638,14 +2986,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2711,6 +3067,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2724,6 +3082,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2754,6 +3114,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -2770,6 +3133,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2790,9 +3167,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -2856,6 +3236,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -2887,6 +3284,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2900,9 +3298,10 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + printstatus(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -3010,8 +3409,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3070,6 +3471,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +@@ -3115,7 +3520,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.47.0 + diff --git a/dwl-bak/dwl-patches/patches/tablet-input/README.md b/dwl-bak/dwl-patches/patches/tablet-input/README.md new file mode 100644 index 0000000..3b3be09 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tablet-input/README.md @@ -0,0 +1,25 @@ +### Description +implements wlr-tablet-v2 for drawing tablets and supports cursor emulation + +inspired by @guyuming76's [branch](https://codeberg.org/guyuming76/dwl/commits/branch/graphic_tablet), with coding help from @Palanix and testing by @Thanatos + +**Please note:** wlroots modified the order of arguments to the function `wlr_surface_accepts_tablet_v2` +In versions less than or equal to 0.18.1 (against which, dwl 0.7 is typically built), the order is `tablet, surface`. +In the master branch and (anticipated) in versions later than 0.18.1, the order is `surface, tablet`. +The git branch for this patch uses the `surface, tablet` order. + +If you are attempting to use this codebase to make your own modifications to the patch, there is only one call to the `wlr_surface_accepts_tablet_v2` function, but you will need to make the argument order change if you expect to build against wlroots 0.18.1 or earlier. + +The `0.7` patch linked here accounts for the change and uses the `tablet, surface` order, but the patch is orphaned from a codebase. + +There was discussion of this matter in [Issue #141](https://codeberg.org/dwl/dwl-patches/issues/141). + +### Download +- [git branch](https://codeberg.org/fauxmight/dwl/src/branch/tablet-input) +- [main 2025-05-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-input/tablet-input.patch) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-input/tablet-input-0.7.patch) + +### Authors +- [fauxmight](https://codeberg.org/fauxmight) +- [notchoc](https://codeberg.org/notchoc) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/tablet-input/tablet-input-0.7.patch b/dwl-bak/dwl-patches/patches/tablet-input/tablet-input-0.7.patch new file mode 100644 index 0000000..37fdf8a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tablet-input/tablet-input-0.7.patch @@ -0,0 +1,355 @@ +From e504dc0fccfc3994962f03dc824d8907c6afc64f Mon Sep 17 00:00:00 2001 +From: choc +Date: Sat, 4 May 2024 01:16:12 +0800 +Subject: [PATCH] implement wlr-tablet-v2 + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 230 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index f955e7b..ce1b556 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,8 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ tablet-v2-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tablet-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..3ad98ef 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -4,6 +4,7 @@ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } + /* appearance */ ++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +diff --git a/dwl.c b/dwl.c +index ac9c36b..b8d129f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -50,6 +50,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -270,6 +273,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtablet(struct wlr_input_device *device); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -284,6 +288,9 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroytablet(struct wl_listener *listener, void *data); ++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); ++static void destroytablettool(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -337,6 +344,11 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy); ++static void tablettoolproximity(struct wl_listener *listener, void *data); ++static void tablettoolaxis(struct wl_listener *listener, void *data); ++static void tablettoolbutton(struct wl_listener *listener, void *data); ++static void tablettooltip(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -396,6 +408,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; + ++static struct wlr_tablet_manager_v2 *tablet_mgr; ++static struct wlr_tablet_v2_tablet *tablet = NULL; ++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; ++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; ++static struct wlr_surface *tablet_curr_surface = NULL; ++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify}; ++ + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; +@@ -1133,6 +1152,28 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtablet(struct wlr_input_device *device) ++{ ++ if (!tablet) { ++ struct libinput_device *device_handle = NULL; ++ if (!wlr_input_device_is_libinput(device) || ++ !(device_handle = wlr_libinput_get_device_handle(device))) ++ return; ++ ++ tablet = wlr_tablet_create(tablet_mgr, seat, device); ++ LISTEN_STATIC(&tablet->wlr_device->events.destroy, destroytablet); ++ if (libinput_device_config_send_events_get_modes(device_handle)) { ++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode); ++ wlr_cursor_attach_input_device(cursor, device); ++ } ++ } else if (device == tablet->wlr_device) { ++ wlr_log(WLR_ERROR, "createtablet: duplicate device"); ++ } else { ++ wlr_log(WLR_ERROR, "createtablet: already have one tablet"); ++ } ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1321,6 +1362,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void ++destroytablet(struct wl_listener *listener, void *data) ++{ ++ tablet = NULL; ++} ++ ++void ++destroytabletsurfacenotify(struct wl_listener *listener, void *data) ++{ ++ if (tablet_curr_surface) ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ tablet_curr_surface = NULL; ++} ++ ++void ++destroytablettool(struct wl_listener *listener, void *data) ++{ ++ destroytabletsurfacenotify(NULL, NULL); ++ tablet_tool = NULL; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1540,6 +1602,12 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TABLET: ++ createtablet(device); ++ break; ++ case WLR_INPUT_DEVICE_TABLET_PAD: ++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -2567,6 +2635,8 @@ setup(void) + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + ++ tablet_mgr = wlr_tablet_v2_create(dpy); ++ + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. +@@ -2596,6 +2666,10 @@ setup(void) + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); ++ LISTEN_STATIC(&cursor->events.tablet_tool_proximity, tablettoolproximity); ++ LISTEN_STATIC(&cursor->events.tablet_tool_axis, tablettoolaxis); ++ LISTEN_STATIC(&cursor->events.tablet_tool_button, tablettoolbutton); ++ LISTEN_STATIC(&cursor->events.tablet_tool_tip, tablettooltip); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); +@@ -2689,6 +2763,156 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tabletapplymap(double x, double y, struct wlr_input_device *dev) ++{ ++ Client *p; ++ struct wlr_box geom = {0}; ++ if (tabletmaptosurface && tablet_curr_surface) { ++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); ++ if (p) { ++ for (; client_get_parent(p); p = client_get_parent(p)); ++ geom.x = p->geom.x + p->bw; ++ geom.y = p->geom.y + p->bw; ++ geom.width = p->geom.width - 2 * p->bw; ++ geom.height = p->geom.height - 2 * p->bw; ++ } ++ } ++ wlr_cursor_map_input_to_region(cursor, dev, &geom); ++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); ++} ++ ++void ++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, ++ double x, double y, double dx, double dy) ++{ ++ struct wlr_surface *surface = NULL; ++ double sx, sy; ++ ++ if (!change_x && !change_y) ++ return; ++ ++ tabletapplymap(x, y, tablet->wlr_device); ++ ++ // TODO: apply constraints ++ switch (tablet_tool->wlr_tool->type) { ++ case WLR_TABLET_TOOL_TYPE_LENS: ++ case WLR_TABLET_TOOL_TYPE_MOUSE: ++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); ++ break; ++ default: ++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN); ++ break; ++ } ++ ++ motionnotify(0, NULL, 0, 0, 0, 0); ++ ++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); ++ if (surface && !wlr_surface_accepts_tablet_v2(tablet, surface)) ++ surface = NULL; ++ ++ if (surface != tablet_curr_surface) { ++ if (tablet_curr_surface) { ++ // TODO: wait until all buttons released before leaving ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface); ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ } ++ if (surface) { ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface); ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface); ++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener); ++ } ++ tablet_curr_surface = surface; ++ } ++ ++ if (surface) ++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); ++} ++ ++void ++tablettoolproximity(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_proximity_event *event = data; ++ struct wlr_tablet_tool *tool = event->tool; ++ ++ if (!tablet_tool) { ++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); ++ LISTEN_STATIC(&tablet_tool->wlr_tool->events.destroy, destroytablettool); ++ LISTEN_STATIC(&tablet_tool->events.set_cursor, setcursor); ++ } ++ ++ switch (event->state) { ++ case WLR_TABLET_TOOL_PROXIMITY_OUT: ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ destroytabletsurfacenotify(NULL, NULL); ++ break; ++ case WLR_TABLET_TOOL_PROXIMITY_IN: ++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); ++ break; ++ } ++} ++ ++void ++tablettoolaxis(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_axis_event *event = data; ++ ++ tablettoolmotion(tablet_tool, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, ++ event->x, event->y, event->dx, event->dy); ++ ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) ++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) ++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); ++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) ++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, event->tilt_y); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) ++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) ++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) ++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0); ++} ++ ++void ++tablettoolbutton(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_button_event *event = data; ++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button, ++ (enum zwp_tablet_pad_v2_button_state)event->state); ++} ++ ++void ++tablettooltip(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_tip_event *event = data; ++ ++ if (!tablet_curr_surface) { ++ struct wlr_pointer_button_event fakeptrbtnevent = { ++ .button = BTN_LEFT, ++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ? ++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED, ++ .time_msec = event->time_msec, ++ }; ++ buttonpress(NULL, (void *)&fakeptrbtnevent); ++ } ++ ++ if (event->state == WLR_TABLET_TOOL_TIP_UP) { ++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); ++ return; ++ } ++ ++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); ++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); ++} ++ + void + tile(Monitor *m) + { +-- +2.43.0 + diff --git a/dwl-bak/dwl-patches/patches/tablet-input/tablet-input.patch b/dwl-bak/dwl-patches/patches/tablet-input/tablet-input.patch new file mode 100644 index 0000000..e7d1211 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tablet-input/tablet-input.patch @@ -0,0 +1,372 @@ +From 0659663b7eb9cafbd4f86779084765aa838e29cd Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Sat, 17 May 2025 23:26:15 -0500 +Subject: [PATCH] Add tablet input + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 237 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index 578194f..e0d1835 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,8 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ tablet-v2-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tablet-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..3ad98ef 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -4,6 +4,7 @@ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } + /* appearance */ ++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +diff --git a/dwl.c b/dwl.c +index 4816159..e8d36ac 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -268,6 +271,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtablet(struct wlr_input_device *device); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -281,6 +285,9 @@ static void destroynotify(struct wl_listener *listener, void *data); + static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroytablet(struct wl_listener *listener, void *data); ++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); ++static void destroytablettool(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -333,6 +340,11 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy); ++static void tablettoolproximity(struct wl_listener *listener, void *data); ++static void tablettoolaxis(struct wl_listener *listener, void *data); ++static void tablettoolbutton(struct wl_listener *listener, void *data); ++static void tablettooltip(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -390,6 +402,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; + ++static struct wlr_tablet_manager_v2 *tablet_mgr; ++static struct wlr_tablet_v2_tablet *tablet = NULL; ++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; ++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; ++static struct wlr_surface *tablet_curr_surface = NULL; ++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify}; ++ + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; +@@ -412,6 +431,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress}; + static struct wl_listener cursor_frame = {.notify = cursorframe}; + static struct wl_listener cursor_motion = {.notify = motionrelative}; + static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; ++static struct wl_listener tablet_device_destroy = {.notify = destroytablet}; ++static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; ++static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; ++static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool}; ++static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity}; ++static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; + static struct wl_listener gpu_reset = {.notify = gpureset}; + static struct wl_listener layout_change = {.notify = updatemons}; + static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +@@ -1196,6 +1221,28 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtablet(struct wlr_input_device *device) ++{ ++ if (!tablet) { ++ struct libinput_device *device_handle = NULL; ++ if (!wlr_input_device_is_libinput(device) || ++ !(device_handle = wlr_libinput_get_device_handle(device))) ++ return; ++ ++ tablet = wlr_tablet_create(tablet_mgr, seat, device); ++ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy); ++ if (libinput_device_config_send_events_get_modes(device_handle)) { ++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode); ++ wlr_cursor_attach_input_device(cursor, device); ++ } ++ } else if (device == tablet->wlr_device) { ++ wlr_log(WLR_ERROR, "createtablet: duplicate device"); ++ } else { ++ wlr_log(WLR_ERROR, "createtablet: already have one tablet"); ++ } ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1380,6 +1427,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void ++destroytablet(struct wl_listener *listener, void *data) ++{ ++ tablet = NULL; ++} ++ ++void ++destroytabletsurfacenotify(struct wl_listener *listener, void *data) ++{ ++ if (tablet_curr_surface) ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ tablet_curr_surface = NULL; ++} ++ ++void ++destroytablettool(struct wl_listener *listener, void *data) ++{ ++ destroytabletsurfacenotify(NULL, NULL); ++ tablet_tool = NULL; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1587,6 +1655,12 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TABLET: ++ createtablet(device); ++ break; ++ case WLR_INPUT_DEVICE_TABLET_PAD: ++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -2580,6 +2654,8 @@ setup(void) + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + ++ tablet_mgr = wlr_tablet_v2_create(dpy); ++ + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. +@@ -2609,6 +2685,11 @@ setup(void) + wl_signal_add(&cursor->events.button, &cursor_button); + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); ++ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity); ++ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); ++ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); ++ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); ++ + + 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); +@@ -2704,6 +2785,159 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tabletapplymap(double x, double y, struct wlr_input_device *dev) ++{ ++ Client *p; ++ struct wlr_box geom = {0}; ++ if (tabletmaptosurface && tablet_curr_surface) { ++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); ++ if (p) { ++ for (; client_get_parent(p); p = client_get_parent(p)); ++ geom.x = p->geom.x + p->bw; ++ geom.y = p->geom.y + p->bw; ++ geom.width = p->geom.width - 2 * p->bw; ++ geom.height = p->geom.height - 2 * p->bw; ++ } ++ } ++ wlr_cursor_map_input_to_region(cursor, dev, &geom); ++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); ++} ++ ++void ++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, ++ double x, double y, double dx, double dy) ++{ ++ struct wlr_surface *surface = NULL; ++ double sx, sy; ++ ++ if (!change_x && !change_y) ++ return; ++ ++ tabletapplymap(x, y, tablet->wlr_device); ++ ++ // TODO: apply constraints ++ switch (tablet_tool->wlr_tool->type) { ++ case WLR_TABLET_TOOL_TYPE_LENS: ++ case WLR_TABLET_TOOL_TYPE_MOUSE: ++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); ++ break; ++ default: ++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN); ++ break; ++ } ++ ++ motionnotify(0, NULL, 0, 0, 0, 0); ++ ++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); ++ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) ++ surface = NULL; ++ ++ if (surface != tablet_curr_surface) { ++ if (tablet_curr_surface) { ++ // TODO: wait until all buttons released before leaving ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface); ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ } ++ if (surface) { ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface); ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface); ++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener); ++ } ++ tablet_curr_surface = surface; ++ } ++ ++ if (surface) ++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); ++} ++ ++void ++tablettoolproximity(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_proximity_event *event = data; ++ struct wlr_tablet_tool *tool = event->tool; ++ ++ if (!tablet_tool) { ++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); ++ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy); ++ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor); ++ } ++ ++ switch (event->state) { ++ case WLR_TABLET_TOOL_PROXIMITY_OUT: ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ destroytabletsurfacenotify(NULL, NULL); ++ break; ++ case WLR_TABLET_TOOL_PROXIMITY_IN: ++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); ++ break; ++ } ++} ++ ++void ++tablettoolaxis(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_axis_event *event = data; ++ ++ tablettoolmotion(tablet_tool, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, ++ event->x, event->y, event->dx, event->dy); ++ ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) ++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) ++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); ++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) ++ { ++ printf("DEBUGGING: In axis event handling\n"); ++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, event->tilt_y); ++ } ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) ++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) ++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) ++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0); ++} ++ ++void ++tablettoolbutton(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_button_event *event = data; ++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button, ++ (enum zwp_tablet_pad_v2_button_state)event->state); ++} ++ ++void ++tablettooltip(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_tip_event *event = data; ++ ++ if (!tablet_curr_surface) { ++ struct wlr_pointer_button_event fakeptrbtnevent = { ++ .button = BTN_LEFT, ++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ? ++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED, ++ .time_msec = event->time_msec, ++ }; ++ buttonpress(NULL, (void *)&fakeptrbtnevent); ++ } ++ ++ if (event->state == WLR_TABLET_TOOL_TIP_UP) { ++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); ++ return; ++ } ++ ++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); ++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); ++} ++ + void + tile(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/tagshift/README.md b/dwl-bak/dwl-patches/patches/tagshift/README.md new file mode 100644 index 0000000..e3d7a03 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tagshift/README.md @@ -0,0 +1,10 @@ +### Description +Port of the [tagshift](https://dwm.suckless.org/patches/tagshift/) patch into dwl. + +Allows a user to change his view and/or focused client into the next or previous tag. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tagshift/tagshift.patch) +### Authors +- [h3nc4](https://codeberg.org/h3nc4) + me@h3nc4.com diff --git a/dwl-bak/dwl-patches/patches/tagshift/tagshift.patch b/dwl-bak/dwl-patches/patches/tagshift/tagshift.patch new file mode 100644 index 0000000..fac6033 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tagshift/tagshift.patch @@ -0,0 +1,83 @@ +From 938c63ad0a8a706fba0b4db1c66397e9defdcb92 Mon Sep 17 00:00:00 2001 +From: h3nc4 +Date: Mon, 17 Mar 2025 17:38:22 -0300 +Subject: [PATCH] port the tagshift patch from dwm + +--- + config.def.h | 4 ++++ + dwl.c | 37 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..72dbaa1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -131,6 +131,10 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY, XKB_KEY_Left, shiftview, {.i = -1 } }, ++ { MODKEY, XKB_KEY_Right, shiftview, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, shifttag, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, shifttag, {.i = +1 } }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index cf3ef70..be1e89e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -333,6 +333,8 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void shiftview(const Arg *arg); ++static void shifttag(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -2646,6 +2648,41 @@ setup(void) + #endif + } + ++void ++shiftview(const Arg *arg) ++{ ++ Arg a; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ if (arg->i > 0) // left circular shift ++ nextseltags = (curseltags << arg->i) | (curseltags >> (TAGCOUNT - arg->i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-arg->i) | (curseltags << (TAGCOUNT + arg->i)); ++ ++ a.i = nextseltags; // Change view to the new tag ++ view(&a); ++} ++ ++void ++shifttag(const Arg *arg) ++{ ++ Arg a; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ Client *sel = focustop(selmon); ++ if (!sel) ++ return; ++ if (arg->i > 0) // left circular shift ++ nextseltags = (curseltags << arg->i) | (curseltags >> (TAGCOUNT - arg->i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-arg->i) | (curseltags << (TAGCOUNT + arg->i)); ++ ++ sel->tags = nextseltags & TAGMASK;// Apply new tag to the client ++ a.i = nextseltags; // Change view to the new tag ++ view(&a); ++ ++ arrange(selmon); ++ printstatus(); // change to 'drawbars();' if using "bars" patch ++} ++ + void + spawn(const Arg *arg) + { +-- +2.47.2 + diff --git a/dwl-bak/dwl-patches/patches/tearing/README.md b/dwl-bak/dwl-patches/patches/tearing/README.md new file mode 100644 index 0000000..45efbf9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tearing/README.md @@ -0,0 +1,17 @@ +### Description +This patch adds support for tearing protocol. To get it working `export WLR_DRM_NO_ATOMIC=1` is probably required. +Some apps would send ASYNC hint and tearing will "just work", otherwise it's possible to force specified clients to tear with a rule. + +Set rules in the config.h (exact string match): +``` +static const ForceTearingRule force_tearing[] = { + {.title = "", .appid = "hl_linux"}, + {.title = "Warcraft III", .appid = ""}, + {.title = "", .appid = "gamescope"}, +}; +``` +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/tearing) +- [2025-04-22](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tearing/tearing.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl-bak/dwl-patches/patches/tearing/tearing.patch b/dwl-bak/dwl-patches/patches/tearing/tearing.patch new file mode 100644 index 0000000..feea1fd --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tearing/tearing.patch @@ -0,0 +1,362 @@ +From c5216442cfb22eed7c2d4196f46c9ad8db3a5425 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Sun, 2 Feb 2025 13:44:41 +0200 +Subject: [PATCH] implement tearing protocol + +--- + Makefile | 5 +- + config.def.h | 8 +++ + dwl.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++---- + 3 files changed, 174 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index 578194f..7d24970 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,7 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h tearing-control-v1-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +45,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tearing-control-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/staging/tearing-control/tearing-control-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..52d38d3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -28,6 +28,14 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++/* tearing */ ++static int tearing_allowed = 1; ++static const ForceTearingRule force_tearing[] = { ++ {.title = "", .appid = "hl_linux"}, ++ {.title = "Warcraft III", .appid = ""}, ++ {.title = "", .appid = "gamescope"}, ++}; ++ + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ +diff --git a/dwl.c b/dwl.c +index 4816159..747e16f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -86,6 +87,11 @@ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + ++typedef struct ForceTearingRule { ++ const char* title; ++ const char* appid; ++} ForceTearingRule; ++ + typedef union { + int i; + uint32_t ui; +@@ -139,6 +145,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ enum wp_tearing_control_v1_presentation_hint tearing_hint; + } Client; + + typedef struct { +@@ -239,6 +246,17 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct TearingController { ++ struct wlr_tearing_control_v1 *tearing_control; ++ struct wl_listener set_hint; ++ struct wl_listener destroy; ++} TearingController; ++ ++typedef struct SendFrameDoneData { ++ struct timespec when; ++ struct Monitor *mon; ++} SendFrameDoneData; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -305,6 +323,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int moncantear(Monitor* m); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -319,6 +338,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data); + 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); +@@ -333,6 +353,9 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tearingcontrollersethint(struct wl_listener *listener, void *data); ++static void tearingcontrollerdestroy(struct wl_listener *listener, void *data); ++static void tearingnewhint(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -395,6 +418,8 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + ++static struct wlr_tearing_control_manager_v1 *tearing_control_v1; ++ + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; +@@ -435,6 +460,7 @@ static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; + static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; ++static struct wl_listener tearing_control_new_object = {.notify = tearingnewhint}; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -779,6 +805,7 @@ cleanuplisteners(void) + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); + wl_list_remove(&new_session_lock.link); ++ wl_list_remove(&tearing_control_new_object.link); + #ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); +@@ -1563,6 +1590,63 @@ handlesig(int signo) + quit(NULL); + } + ++void ++tearingcontrollersethint(struct wl_listener *listener, void *data) ++{ ++ Client *c = NULL, *i = NULL; ++ TearingController *controller = wl_container_of(listener, controller, set_hint); ++ ++ struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#endif ++ ++ wl_list_for_each(i, &fstack, flink) { ++ if (i->surface.xdg == surface ++#ifdef XWAYLAND ++ || i->surface.xwayland == xsurface ++#endif ++ ) { ++ c = i; ++ break; ++ } ++ } ++ ++ if (c) { ++ enum wp_tearing_control_v1_presentation_hint hint = controller->tearing_control->current; ++ fprintf( ++ stderr, "TEARING: found surface: %p(appid: '%s', title: '%s'), hint: %d(%s)\n", ++ (void*)c, client_get_appid(c), client_get_title(c), hint, hint ? "ASYNC" : "VSYNC" ++ ); ++ c->tearing_hint = controller->tearing_control->current; ++ } ++} ++ ++void ++tearingcontrollerdestroy(struct wl_listener *listener, void *data) ++{ ++ TearingController *controller = wl_container_of(listener, controller, destroy); ++ ++ wl_list_remove(&controller->set_hint.link); ++ wl_list_remove(&controller->destroy.link); ++ free(controller); ++} ++ ++void ++tearingnewhint(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tearing_control_v1 *tearing_control = data; ++ TearingController *controller = ecalloc(1, sizeof(*controller)); ++ ++ controller->tearing_control = tearing_control; ++ ++ controller->set_hint.notify = tearingcontrollersethint; ++ wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); ++ ++ controller->destroy.notify = tearingcontrollerdestroy; ++ wl_signal_add(&tearing_control->events.destroy, &controller->destroy); ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1730,6 +1814,35 @@ locksession(struct wl_listener *listener, void *data) + wlr_session_lock_v1_send_locked(session_lock); + } + ++static inline void ++forcetearingrule(Client *c) ++{ ++ int success = 0; ++ const char* appid = client_get_appid(c); ++ const char* title = client_get_title(c); ++ ++ for (unsigned i = 0; i < LENGTH(force_tearing); ++i) { ++ if (appid) { ++ if (strcmp(force_tearing[i].appid, appid) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ ++ if (title) { ++ if (strcmp(force_tearing[i].title, title) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ } ++ ++ if (success) { ++ c->tearing_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; ++ fprintf(stderr, "Forcing tearing for appid: '%s', title: '%s'\n", appid, title); ++ } ++} ++ + void + mapnotify(struct wl_listener *listener, void *data) + { +@@ -1739,6 +1852,8 @@ mapnotify(struct wl_listener *listener, void *data) + Monitor *m; + int i; + ++ forcetearingrule(c); ++ + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ +@@ -1977,6 +2092,13 @@ moveresize(const Arg *arg) + } + } + ++int ++moncantear(Monitor* m) ++{ ++ Client *c = focustop(m); ++ return (c && c->isfullscreen && c->tearing_hint); /* 1 == ASYNC */ ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2147,27 +2269,40 @@ quit(const Arg *arg) + void + rendermon(struct wl_listener *listener, void *data) + { +- /* This function is called every time an output is ready to display a frame, +- * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); +- Client *c; ++ struct wlr_scene_output *scene_output = m->scene_output; + struct wlr_output_state pending = {0}; +- struct timespec now; ++ SendFrameDoneData frame_done_data = {0}; + +- /* Render if no XDG clients have an outstanding resize and are visible on +- * this monitor. */ +- wl_list_for_each(c, &clients, link) { +- if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) +- goto skip; ++ m->wlr_output->frame_pending = false; ++ ++ if (!wlr_scene_output_needs_frame(scene_output)) { ++ goto skip; + } + +- wlr_scene_output_commit(m->scene_output, NULL); ++ wlr_output_state_init(&pending); ++ if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) { ++ goto skip; ++ } ++ ++ if (tearing_allowed && moncantear(m)) { ++ pending.tearing_page_flip = true; ++ ++ if (!wlr_output_test_state(m->wlr_output, &pending)) { ++ fprintf(stderr, "Output test failed on '%s', retrying without tearing page-flip\n", m->wlr_output->name); ++ pending.tearing_page_flip = false; ++ } ++ } ++ ++ if (!wlr_output_commit_state(m->wlr_output, &pending)) ++ fprintf(stderr, "Page-flip failed on output %s", m->wlr_output->name); + +-skip: +- /* Let clients know a frame has been rendered */ +- clock_gettime(CLOCK_MONOTONIC, &now); +- wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); ++ ++skip: ++ clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when); ++ frame_done_data.mon = m; ++ wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, &frame_done_data); + } + + void +@@ -2291,6 +2426,16 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) ++{ ++ SendFrameDoneData *data = user_data; ++ if (buffer->primary_output != data->mon->scene_output) ++ return; ++ ++ wlr_scene_buffer_send_frame_done(buffer, &data->when); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2641,6 +2786,9 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1); ++ wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/titleurgent/README.md b/dwl-bak/dwl-patches/patches/titleurgent/README.md new file mode 100644 index 0000000..aa4c99d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/titleurgent/README.md @@ -0,0 +1,11 @@ +### Description +Whenever a client title changes set the client's urgent flag. + +Hacky solution I use to deal with qutebrowser not setting urgent flag when a new tab is opened. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/titleurgent) +- [2024-01-02](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/titleurgent/titleurgent.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/titleurgent/titleurgent.patch b/dwl-bak/dwl-patches/patches/titleurgent/titleurgent.patch new file mode 100644 index 0000000..9a73f66 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/titleurgent/titleurgent.patch @@ -0,0 +1,80 @@ +From ab002204c8bb7c8c36828aeb89ef3cf9b0447ea6 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Fri, 29 Dec 2023 19:02:11 +1000 +Subject: [PATCH] titleurgent + +--- + config.def.h | 8 +++++--- + dwl.c | 8 ++++++++ + 2 files changed, 13 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..a5000901 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,10 +21,12 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating titleurgent monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "org.qutebrowser.qutebrowser_EXAMPLE", ++ NULL, 0, 0, 1, -1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 6f041a0d..9486c435 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,6 +138,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int titleurgent; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -228,6 +229,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int titleurgent; + int monitor; + } Rule; + +@@ -455,6 +457,7 @@ applyrules(Client *c) + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); ++ c->titleurgent = 0; + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) +@@ -464,6 +467,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->titleurgent = r->titleurgent; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -2821,6 +2825,10 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ else if (c->titleurgent) { ++ c->isurgent = 1; ++ printstatus(); ++ } + } + + void +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/tmux-borders/README.md b/dwl-bak/dwl-patches/patches/tmux-borders/README.md new file mode 100644 index 0000000..e203e76 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tmux-borders/README.md @@ -0,0 +1,15 @@ +### Description +This patch replaces the window borders of tiled windows with borders that are similar to those found in tmux. The result is that there are no more unnecessary borders along the monitor edges in tiled mode. Borders of floating windows are not affected. + +### Preview +![two clients](screenshot1.png) + +![three clients](screenshot2.png) + +### Download +- [git branch](/kerberoge/dwl/src/branch/tmux-borders) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tmux-borders/tmux-borders-0.7.patch) + +### Authors +- [kerberoge](https://codeberg.org/kerberoge)\ + kerberoge at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl-bak/dwl-patches/patches/tmux-borders/screenshot1.png b/dwl-bak/dwl-patches/patches/tmux-borders/screenshot1.png new file mode 100644 index 0000000..ed12cc5 Binary files /dev/null and b/dwl-bak/dwl-patches/patches/tmux-borders/screenshot1.png differ diff --git a/dwl-bak/dwl-patches/patches/tmux-borders/screenshot2.png b/dwl-bak/dwl-patches/patches/tmux-borders/screenshot2.png new file mode 100644 index 0000000..83ae2e2 Binary files /dev/null and b/dwl-bak/dwl-patches/patches/tmux-borders/screenshot2.png differ diff --git a/dwl-bak/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch b/dwl-bak/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch new file mode 100644 index 0000000..f6839a8 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch @@ -0,0 +1,489 @@ +From 464259603cb3b7346670cc33fd9afa0f8fe0cd49 Mon Sep 17 00:00:00 2001 +From: kerberoge +Date: Sun, 1 Jun 2025 17:03:09 +0200 +Subject: [PATCH 1/1] Created tmux-borders patch for stock dwl + +--- + client.h | 6 ++ + dwl.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 280 insertions(+), 24 deletions(-) + +diff --git a/client.h b/client.h +index 42f225f..007ab7d 100644 +--- a/client.h ++++ b/client.h +@@ -291,6 +291,12 @@ client_is_unmanaged(Client *c) + return 0; + } + ++static inline int ++client_needs_borders(Client *c) ++{ ++ return c->isfloating || !c->mon->lt[c->mon->sellt]->arrange; ++} ++ + static inline void + client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) + { +diff --git a/dwl.c b/dwl.c +index a2711f6..c092c0e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -191,6 +191,7 @@ struct Monitor { + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ ++ struct wlr_scene_tree *borders, *fborders, *uborders; + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; +@@ -255,10 +256,12 @@ static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); + static void cleanupmon(struct wl_listener *listener, void *data); ++static int clientindex(Monitor *m, Client *c); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void commitpopup(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -273,6 +276,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void destroyborders(struct wlr_scene_tree *t); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -285,6 +289,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawclientborders(struct wlr_scene_tree *t, Client *c, ++ int cidx, int n, const float color[static 4]); ++static void drawrect(struct wlr_scene_tree *t, ++ int x, int y, int w, int h, const float color[static 4]); ++static void drawborders(Monitor *m); ++static void drawfborders(Monitor *m); ++static void drawuborders(Monitor *m); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -517,6 +528,11 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ ++ drawborders(m); ++ drawuborders(m); ++ drawfborders(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -721,10 +737,34 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_scene_output_destroy(m->scene_output); + + closemon(m); ++ wlr_scene_node_destroy(&m->borders->node); ++ wlr_scene_node_destroy(&m->fborders->node); ++ wlr_scene_node_destroy(&m->uborders->node); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); + } + ++int ++clientindex(Monitor *m, Client *c) ++{ ++ unsigned int i = 0; ++ Client *ci; ++ ++ if (!c || m->lt[m->sellt]->arrange != tile) ++ return -1; ++ ++ wl_list_for_each(ci, &clients, link) { ++ if (VISIBLEON(ci, m) && !ci->isfloating && !ci->isfullscreen) { ++ if (ci == c) ++ return i; ++ else ++ i++; ++ } ++ } ++ ++ return -1; ++} ++ + void + closemon(Monitor *m) + { +@@ -743,6 +783,10 @@ closemon(Monitor *m) + selmon = NULL; + } + ++ destroyborders(m->borders); ++ destroyborders(m->fborders); ++ destroyborders(m->uborders); ++ + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +@@ -849,6 +893,19 @@ commitpopup(struct wl_listener *listener, void *data) + wl_list_remove(&listener->link); + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -985,6 +1042,9 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->borders = wlr_scene_tree_create(layers[LyrTile]); ++ m->fborders = wlr_scene_tree_create(layers[LyrTile]); ++ m->uborders = wlr_scene_tree_create(layers[LyrTile]); + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -1171,6 +1231,15 @@ cursorwarptohint(void) + } + } + ++void ++destroyborders(struct wlr_scene_tree *t) ++{ ++ struct wlr_scene_node *node, *tmp; ++ ++ wl_list_for_each_safe(node, tmp, &t->children, link) ++ wlr_scene_node_destroy(node); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1336,6 +1405,154 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawclientborders(struct wlr_scene_tree *t, Client *c, int cidx, int n, const float color[static 4]) ++{ ++ Monitor *m; ++ int mw, x, y, w, h; ++ ++ m = c->mon; ++ mw = (int)round(m->w.width * m->mfact - 0.5 * borderpx); ++ ++ if (m->nmaster == 1 && n == 2) { ++ /* Half vertical center line */ ++ y = m->w.y + (cidx == 1 ? m->w.height / 2 : 0); ++ h = (int)round(0.5 * m->w.height); ++ drawrect(t, m->w.x + mw, y, borderpx, h, color); ++ } else if (m->nmaster != 1 && n == 2) { ++ /* Half horizontal center line */ ++ x = m->w.x + (cidx == 1 ? m->w.width / 2 : 0); ++ y = m->w.y + (int)round(0.5 * m->w.height - 0.5 * borderpx); ++ w = (int)round(0.5 * m->w.width); ++ drawrect(t, x, y, w, borderpx, color); ++ } else { ++ if (m->nmaster && n > m->nmaster) ++ /* Vertical line next to client */ ++ drawrect(t, m->w.x + mw, c->geom.y, borderpx, c->geom.height, color); ++ ++ if (n > m->nmaster && cidx < m->nmaster) { ++ /* Left half */ ++ x = m->w.x; ++ w = mw + borderpx; ++ } else if (m->nmaster && cidx >= m->nmaster) { ++ /* Right half */ ++ x = m->w.x + mw; ++ w = m->w.width - mw; ++ } else { ++ /* Full width */ ++ x = m->w.x; ++ w = m->w.width; ++ } ++ ++ if ((cidx > 0 && cidx < m->nmaster) || (cidx > m->nmaster)) ++ /* Line above client */ ++ drawrect(t, x, c->geom.y - borderpx, w, borderpx, color); ++ ++ if ((cidx < m->nmaster - 1) || (cidx >= m->nmaster && cidx < n - 1)) ++ /* Line below client */ ++ drawrect(t, x, c->geom.y + c->geom.height, w, borderpx, color); ++ } ++} ++ ++void ++drawrect(struct wlr_scene_tree *t, int x, int y, int w, int h, const float color[static 4]) ++{ ++ struct wlr_scene_rect *r; ++ ++ r = wlr_scene_rect_create(t, w, h, color); ++ wlr_scene_node_set_position(&r->node, x, y); ++} ++ ++void ++drawborders(Monitor *m) ++{ ++ int n, i; ++ int mw, tw, my = 0, ty = 0; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->borders); ++ n = countclients(m); ++ ++ if (n <= 1 || m->lt[m->sellt]->arrange != tile) ++ return; ++ ++ if (m->nmaster > 0 && n > m->nmaster) ++ mw = (int)round(m->w.width * m->mfact - 0.5 * borderpx); ++ else if (n <= m->nmaster) ++ mw = m->w.width; ++ else ++ mw = 0; ++ ++ if (mw > 0) ++ tw = m->w.width - mw - borderpx; ++ else ++ tw = m->w.width; ++ ++ /* Vertical center line */ ++ if (mw > 0 && mw < m->w.width) ++ drawrect(m->borders, m->w.x + mw, m->w.y, borderpx, m->w.height, bordercolor); ++ ++ /* Lines between master clients */ ++ for (i = 0; i < MIN(n, m->nmaster) - 1; i++) { ++ my += (m->w.height - my - borderpx * (MIN(n, m->nmaster) - i - 1)) ++ / (MIN(n, m->nmaster) - i); ++ drawrect(m->borders, m->w.x, m->w.y + my, mw, borderpx, bordercolor); ++ my += borderpx; ++ } ++ ++ /* Lines between clients on the stack */ ++ for (i = m->nmaster; i < n - 1; i++) { ++ ty += (m->w.height - ty - borderpx * (n - i - 1)) / (n - i); ++ drawrect(m->borders, m->m.x + (mw ? mw + borderpx : 0), m->w.y + ty, ++ tw, borderpx, bordercolor); ++ ty += borderpx; ++ } ++} ++ ++void ++drawfborders(Monitor *m) ++{ ++ Client *fc; ++ int n, cidx; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->fborders); ++ n = countclients(m); ++ fc = focustop(m); ++ cidx = clientindex(m, fc); ++ ++ if (n <= 1 || cidx == -1) ++ return; ++ ++ drawclientborders(m->fborders, fc, cidx, n, focuscolor); ++} ++ ++void ++drawuborders(Monitor *m) ++{ ++ Client *c; ++ int n, cidx; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->uborders); ++ n = countclients(m); ++ ++ if (n <= 1) ++ return; ++ ++ wl_list_for_each(c, &clients, link) { ++ cidx = clientindex(m, c); ++ if (cidx != -1 && c->isurgent) ++ drawclientborders(m->uborders, c, cidx, n, urgentcolor); ++ } ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1366,12 +1583,15 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ drawuborders(c->mon); + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ drawfborders(c->mon); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1389,7 +1609,8 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ if (old_c->mon && (!c || c->mon != old_c->mon)) ++ destroyborders(old_c->mon->fborders); + client_activate_surface(old, 0); + } + } +@@ -2193,15 +2414,20 @@ resize(Client *c, struct wlr_box geo, int interact) + applybounds(c, bbox); + + /* Update scene-graph, including borders */ ++ c->bw = client_needs_borders(c) ? borderpx : 0; + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ for (int i = 0; i < 4; i++) ++ wlr_scene_node_set_enabled(&c->border[i]->node, c->bw); ++ if (c->bw) { ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ } + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2359,8 +2585,13 @@ setlayout(const Arg *arg) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; ++ else ++ return; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ if (selmon->lt[selmon->sellt ^ 1]->arrange == tile && !selmon->lt[selmon->sellt]->arrange) ++ /* Tiled -> floating, remove monitor borders and enable client borders */ ++ tile(selmon); + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2692,31 +2923,46 @@ void + tile(Monitor *m) + { + unsigned int mw, my, ty; +- int i, n = 0; ++ int i, n; + Client *c; ++ struct wlr_box wb; ++ int borders = m->lt[m->sellt]->arrange == tile ? 1 : 0; + +- wl_list_for_each(c, &clients, link) +- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) +- n++; ++ n = countclients(m); + if (n == 0) + return; + +- if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; +- else ++ if (n > m->nmaster && m->nmaster) { ++ mw = (int)round(m->w.width * m->mfact - (borders ? 0.5 * borderpx : 0)); ++ } else if (m->nmaster) + mw = m->w.width; ++ else ++ mw = 0; + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ wb.x = m->w.x; ++ wb.y = m->w.y + my; ++ wb.width = mw; ++ if (borders) ++ wb.height = (m->w.height - my - borderpx * (MIN(n, m->nmaster) - i - 1)) ++ / (MIN(n, m->nmaster) - i); ++ else ++ wb.height = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, wb, 0); ++ my += wb.height + (borders ? borderpx : 0); + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ wb.x = m->w.x + mw + (mw && borders ? borderpx : 0); ++ wb.y = m->w.y + ty; ++ wb.width = m->w.width - mw - (mw && borders ? borderpx : 0); ++ if (borders) ++ wb.height = (m->w.height - ty - borderpx * (n - i - 1)) / (n - i); ++ else ++ wb.height = (m->w.height - ty) / (n - i); ++ resize(c, wb, 0); ++ ty += wb.height + (borders ? borderpx : 0); + } + i++; + } +@@ -2941,8 +3187,10 @@ urgent(struct wl_listener *listener, void *data) + c->isurgent = 1; + printstatus(); + +- if (client_surface(c)->mapped) ++ if (client_surface(c)->mapped) { + client_set_border_color(c, urgentcolor); ++ drawuborders(c->mon); ++ } + } + + void +@@ -3147,8 +3395,10 @@ sethints(struct wl_listener *listener, void *data) + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + printstatus(); + +- if (c->isurgent && surface && surface->mapped) ++ if (c->isurgent && surface && surface->mapped) { + client_set_border_color(c, urgentcolor); ++ drawuborders(c->mon); ++ } + } + + void +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/toggle_constraints/README.md b/dwl-bak/dwl-patches/patches/toggle_constraints/README.md new file mode 100644 index 0000000..7f2be77 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/toggle_constraints/README.md @@ -0,0 +1,17 @@ +### Description +Adds a function called togglepointerconstraints to turn pointer constraint enforcement on and off with a keybind. + +### Usage +Add a binding for the togglepointerconstraints function in the keys[] array of config.h. The function does not take any argument. Pointer constraints default to enabled, and can be toggled on and off with the function from there. + +Example: +``` +{ MODKEY, XKB_KEY_c, togglepointerconstraints, {0}}, +``` + +### Download +- [Git branch](https://codeberg.org/thanatos/dwl/src/branch/toggle_constraints) +- [2024-03-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/toggle_constraints/toggle_constraints) + +### Authors +- [thanatos](https://codeberg.org/thanatos) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/toggle_constraints/toggle_constraints.patch b/dwl-bak/dwl-patches/patches/toggle_constraints/toggle_constraints.patch new file mode 100644 index 0000000..e823377 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/toggle_constraints/toggle_constraints.patch @@ -0,0 +1,86 @@ +From 13bb9d86a0da2ebc82c20ee721b3eeb28592b0a0 Mon Sep 17 00:00:00 2001 +From: David Donahue +Date: Tue, 26 Mar 2024 17:45:26 -0600 +Subject: [PATCH] Added function to toggle pointer constraints via a keybind + +--- + dwl.c | 41 ++++++++++++++++++++++++++--------------- + 1 file changed, 26 insertions(+), 15 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..04d377b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -332,6 +332,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointerconstraints(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -400,6 +401,8 @@ static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ + ++static int enable_constraints = 1; ++ + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; +@@ -1700,22 +1703,24 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + +- wl_list_for_each(constraint, &pointer_constraints->constraints, link) +- cursorconstrain(constraint); +- +- if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { +- toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); +- if (c && active_constraint->surface == seat->pointer_state.focused_surface) { +- sx = cursor->x - c->geom.x - c->bw; +- sy = cursor->y - c->geom.y - c->bw; +- if (wlr_region_confine(&active_constraint->region, sx, sy, +- sx + dx, sy + dy, &sx_confined, &sy_confined)) { +- dx = sx_confined - sx; +- dy = sy_confined - sy; ++ if (enable_constraints){ ++ wl_list_for_each(constraint, &pointer_constraints->constraints, link) ++ cursorconstrain(constraint); ++ ++ if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { ++ toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); ++ if (c && active_constraint->surface == seat->pointer_state.focused_surface) { ++ sx = cursor->x - c->geom.x - c->bw; ++ sy = cursor->y - c->geom.y - c->bw; ++ if (wlr_region_confine(&active_constraint->region, sx, sy, ++ sx + dx, sy + dy, &sx_confined, &sy_confined)) { ++ dx = sx_confined - sx; ++ dy = sy_confined - sy; ++ } ++ ++ if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) ++ return; + } +- +- if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) +- return; + } + } + +@@ -2620,6 +2625,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointerconstraints(const Arg *arg) ++{ ++ enable_constraints = !enable_constraints; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.43.2 + diff --git a/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/README.md b/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/README.md new file mode 100644 index 0000000..883dd6d --- /dev/null +++ b/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/README.md @@ -0,0 +1,9 @@ +### Description +Switch between multiple keyboard layouts, variants, and options at runtime. + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch) + +### Authors +- [LaKato](https://codeberg.org/LaKato) +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch b/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch new file mode 100644 index 0000000..dc7fd81 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch @@ -0,0 +1,160 @@ +From 9ee76adc7c6567650e7b34273f953fed03191c05 Mon Sep 17 00:00:00 2001 +From: LaKato +Date: Sat, 8 Mar 2025 12:14:41 -0500 +Subject: [PATCH] Update for v0.7 + +Additional changes: +- incxkbrules now uses its argument +- A new setxkbrules function has been added +- Keyboard groups are no longer looped over because only one is created +--- + config.def.h | 17 +++++++++++------ + dwl.c | 44 +++++++++++++++++++++++++++++++++----------- + 2 files changed, 44 insertions(+), 17 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..b3b4699 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -52,12 +52,15 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ +- /* example: +- .options = "ctrl:nocaps", +- */ +- .options = NULL, ++static const struct xkb_rule_names xkb_rules[] = { ++ { ++ .layout = "us", ++ }, ++ /*{ ++ .layout = "us", ++ .variant = "dvp", ++ .options = "compose:102,numpad:shift3,kpdl:semi,keypad:atm,caps:super" ++ }*/ + }; + + static const int repeat_rate = 25; +@@ -148,6 +151,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, incxkbrules, {.i = +1} }, ++ /*{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, setxkbrules, {.i = +1} },*/ + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..a2413e9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -249,6 +249,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void assignkeymap(struct wlr_keyboard *keyboard); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -293,6 +294,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incxkbrules(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -333,6 +335,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setxkbrules(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; ++static unsigned int kblayout = 0; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ +@@ -580,6 +584,20 @@ arrangelayers(Monitor *m) + } + } + ++void ++assignkeymap(struct wlr_keyboard *keyboard) { ++ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &xkb_rules[kblayout], ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ ++ if (!keymap) ++ die("failed to compile keymap"); ++ ++ wlr_keyboard_set_keymap(keyboard, keymap); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -885,21 +903,11 @@ KeyboardGroup * + createkeyboardgroup(void) + { + KeyboardGroup *group = ecalloc(1, sizeof(*group)); +- struct xkb_context *context; +- struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + +- /* Prepare an XKB keymap and assign it to the keyboard group. */ +- context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); +- if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, +- XKB_KEYMAP_COMPILE_NO_FLAGS))) +- die("failed to compile keymap"); +- +- wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); +- xkb_keymap_unref(keymap); +- xkb_context_unref(context); ++ assignkeymap(&group->wlr_group->keyboard); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + +@@ -1524,6 +1532,13 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incxkbrules(const Arg *arg) ++{ ++ kblayout = (kblayout + arg->i) % LENGTH(xkb_rules); ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2645,6 +2660,13 @@ setup(void) + #endif + } + ++void ++setxkbrules(const Arg *arg) ++{ ++ kblayout = arg->i; ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + spawn(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl-bak/dwl-patches/patches/touch-input/README.md b/dwl-bak/dwl-patches/patches/touch-input/README.md new file mode 100644 index 0000000..3d37fc2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/touch-input/README.md @@ -0,0 +1,20 @@ +### Description +Adds touchscreen functionality. + +KNOWN BUGS: +- Sometimes, the pointer moves to where the screen is pressed, but the button press doesn't occur until the screen is touched AGAIN. This means that if you touch to click button 'Q' on the screen (for instance), nothing happens; then you touch elsewhere on the screen and THEN button 'Q' registers a click. This is annoying, doesn't always happen, and I don't yet know how to fix it. + +### Download +- [git branch](https://codeberg.org/fauxmight/dwl/src/branch/touch-input) +- [2025-05-17](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/touch-input/touch-input.patch) + +### Authors +- [fauxmight](https://codeberg.org/fauxmight) +- [minego](https://codeberg.org/minego) +- [Unprex](https://github.com/Unprex) + +### Changelog +- 2025-01-01 @fauxmight took over maintenance. Previous maintainer @minego notes [lack of available time](https://codeberg.org/dwl/dwl-patches/pulls/102#issuecomment-2557944) +- 2024-02-11 Corrected issue where motion events where not sending notifications for unfocused clients such as an on screen keyboard +- 2024-03-26 Rebased, and removed #ifdef's for the pointer constraints patch which has been merged into upstream +- 2024-03-28 Removed debug diff --git a/dwl-bak/dwl-patches/patches/touch-input/touch-input.patch b/dwl-bak/dwl-patches/patches/touch-input/touch-input.patch new file mode 100644 index 0000000..bfd33e7 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/touch-input/touch-input.patch @@ -0,0 +1,260 @@ +From f133af7120e28f3d8bda4d4e14b3bfd477b46426 Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Sat, 17 May 2025 21:59:37 -0500 +Subject: [PATCH] Add support for touchscreen input devices + +and send the appropriate events to clients +--- + dwl.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 158 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 4816159..3a378f9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -161,6 +162,12 @@ typedef struct { + struct wl_listener destroy; + } KeyboardGroup; + ++typedef struct TouchGroup { ++ struct wl_list link; ++ struct wlr_touch *touch; ++ Monitor *m; ++} TouchGroup; ++ + typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ +@@ -268,7 +275,9 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtouch(struct wlr_touch *touch); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); ++static void createtouch(struct wlr_touch *touch); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); + static void destroydecoration(struct wl_listener *listener, void *data); +@@ -338,6 +347,10 @@ static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); ++static void touchdown(struct wl_listener *listener, void *data); ++static void touchup(struct wl_listener *listener, void *data); ++static void touchframe(struct wl_listener *listener, void *data); ++static void touchmotion(struct wl_listener *listener, void *data); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); +@@ -405,6 +418,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static struct wl_list touches; + + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; +@@ -434,6 +448,10 @@ static struct wl_listener request_set_sel = {.notify = setsel}; + static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; + static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; ++static struct wl_listener touch_down = {.notify = touchdown}; ++static struct wl_listener touch_frame = {.notify = touchframe}; ++static struct wl_listener touch_motion = {.notify = touchmotion}; ++static struct wl_listener touch_up = {.notify = touchup}; + static struct wl_listener new_session_lock = {.notify = locksession}; + + #ifdef XWAYLAND +@@ -778,6 +796,10 @@ cleanuplisteners(void) + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); ++ wl_list_remove(&touch_down.link); ++ wl_list_remove(&touch_frame.link); ++ wl_list_remove(&touch_motion.link); ++ wl_list_remove(&touch_up.link); + wl_list_remove(&new_session_lock.link); + #ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); +@@ -1196,6 +1218,16 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtouch(struct wlr_touch *wlr_touch) ++{ ++ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); ++ ++ touch->touch = wlr_touch; ++ wl_list_insert(&touches, &touch->link); ++ wlr_cursor_attach_input_device(cursor, &wlr_touch->base); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1587,6 +1619,9 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TOUCH: ++ createtouch(wlr_touch_from_input_device(device)); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -1599,6 +1634,8 @@ inputdevice(struct wl_listener *listener, void *data) + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ if (!wl_list_empty(&touches)) ++ caps |= WL_SEAT_CAPABILITY_TOUCH; + wlr_seat_set_capabilities(seat, caps); + } + +@@ -2610,6 +2647,13 @@ setup(void) + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); + ++ wl_list_init(&touches); ++ ++ wl_signal_add(&cursor->events.touch_down, &touch_down); ++ wl_signal_add(&cursor->events.touch_frame, &touch_frame); ++ wl_signal_add(&cursor->events.touch_motion, &touch_motion); ++ wl_signal_add(&cursor->events.touch_up, &touch_up); ++ + 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); + +@@ -2782,6 +2826,120 @@ toggleview(const Arg *arg) + printstatus(); + } + ++void ++touchdown(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_down_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ uint32_t serial = 0; ++ Monitor *m; ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ ++ // Map the input to the appropriate output, to ensure that rotation is ++ // handled. ++ wl_list_for_each(m, &mons, link) { ++ if (m == NULL || m->wlr_output == NULL) { ++ continue; ++ } ++ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { ++ continue; ++ } ++ ++ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output); ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ ++ /* Find the client under the pointer and send the event along. */ ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ if (sloppyfocus) ++ focusclient(c, 0); ++ ++ if (surface != NULL) { ++ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } ++ ++ if (serial && wlr_seat_touch_num_points(seat) == 1) { ++ /* Emulate a mouse click if the touch event wasn't handled */ ++ struct wlr_pointer_button_event *button_event = data; ++ struct wlr_pointer_motion_absolute_event *motion_event = data; ++ double dx, dy; ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly); ++ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); ++ dx = lx - cursor->x; ++ dy = ly - cursor->y; ++ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy); ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; ++ buttonpress(listener, button_event); ++ } ++} ++ ++void ++touchup(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_up_event *event = data; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ if (wlr_seat_touch_num_points(seat) == 1) { ++ struct wlr_pointer_button_event *button_event = data; ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; ++ buttonpress(listener, button_event); ++ } ++ ++ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchframe(struct wl_listener *listener, void *data) ++{ ++ wlr_seat_touch_notify_frame(seat); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchmotion(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_motion_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ ++ if (c != NULL && surface != NULL) { ++ if (sloppyfocus) ++ focusclient(c, 0); ++ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } else { ++ if (sloppyfocus) ++ focusclient(NULL, 0); ++ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); ++ } ++ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy); ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++ + void + unlocksession(struct wl_listener *listener, void *data) + { +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/unclutter/README.md b/dwl-bak/dwl-patches/patches/unclutter/README.md new file mode 100644 index 0000000..48bf85a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/unclutter/README.md @@ -0,0 +1,11 @@ +### Description +Hide the mouse cursor if it isn't being used for a certain period of time. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/unclutter) +- [2025-06-09](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/unclutter/unclutter.patch) + +### Authors +- [Guido Cella](https://github.com/guidocella) +- [dm1tz](https://github.com/dm1tz) +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/unclutter/unclutter.patch b/dwl-bak/dwl-patches/patches/unclutter/unclutter.patch new file mode 100644 index 0000000..4c9aede --- /dev/null +++ b/dwl-bak/dwl-patches/patches/unclutter/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 + diff --git a/dwl-bak/dwl-patches/patches/ungroup-keyboards/README.md b/dwl-bak/dwl-patches/patches/ungroup-keyboards/README.md new file mode 100644 index 0000000..e56f94b --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ungroup-keyboards/README.md @@ -0,0 +1,15 @@ +### Description +Ungroup keyboard input devices based on device name. + +I wrote this patch was because keyboard device grouping breaks the behaviour of +the ydotool virtual device. This patch fixes my issue #558 in the codeberg +issue tracker. + +See the inputdevicerules patch for a more generalised version of this idea. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/ungroup-keyboards) +- [2024-06-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/ungroup-keyboards/ungroup-keyboards.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch b/dwl-bak/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch new file mode 100644 index 0000000..b3249b6 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch @@ -0,0 +1,97 @@ +From d9b9797680ae58bdb910e3bc1f71408f6b67c0d5 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] ungroup-keyboards + +Ungroup keyboards based on device name. My use case is keeping the +ydotool virtual keyboard from from being grouped with other keyboards. +--- + config.def.h | 7 +++++++ + dwl.c | 29 ++++++++++++++++++++++++++++- + 2 files changed, 35 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..9ad1c256 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,13 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* keyboard input devices - used to ungroup named keyboard devices */ ++static const KBInputRule kbinputrules[] = { ++ /* name kbcreate */ ++ { "ydotoold virtual device", createungroupedkeyboard }, ++ { NULL, createkeyboard }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index 5a31aeef..41db830b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,11 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++} KBInputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -266,6 +271,7 @@ static void createmon(struct wl_listener *listener, void *data); + static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -1089,6 +1095,20 @@ createpointerconstraint(struct wl_listener *listener, void *data) + &pointer_constraint->destroy, destroypointerconstraint); + } + ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1464,10 +1484,17 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const KBInputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = kbinputrules; r < END(kbinputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/vanitygaps/README.md b/dwl-bak/dwl-patches/patches/vanitygaps/README.md new file mode 100644 index 0000000..cbdf6b4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/vanitygaps/README.md @@ -0,0 +1,12 @@ +### Description +Adds (inner) gaps between client windows and (outer) gaps between windows and +the screen edge in a flexible manner. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/vanitygaps) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/vanitygaps/vanitygaps.patch) +- [vanitygaps-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/vanitygaps/vanitygaps-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Bonicgamer](https://github.com/Bonicgamer) diff --git a/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch b/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch new file mode 100644 index 0000000..622b444 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch @@ -0,0 +1,358 @@ +From 3c95b58bc2b87ebd9b8481b3b16e49d99883f0a7 Mon Sep 17 00:00:00 2001 +From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com> +Date: Mon, 17 Aug 2020 14:48:24 -0400 +Subject: [PATCH 1/2] Implement vanitygaps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 21 ++++++++ + dwl.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 161 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..39e528b1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,12 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +138,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index a2711f67..d749728a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -200,6 +200,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -273,6 +277,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +298,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -327,6 +339,7 @@ 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +353,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +427,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -989,6 +1005,11 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1171,6 +1192,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1524,6 +1551,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2352,6 +2456,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2691,7 +2805,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps; + int i, n = 0; + Client *c; + +@@ -2701,22 +2815,31 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (smartgaps == n) { ++ oe = 0; // outer gaps disabled ++ } ++ + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0; + else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie; ++ i = 0; ++ my = ty = m->gappoh*oe; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my, ++ .width = mw - m->gappiv*ie, .height = h}, 0); ++ my += c->geom.height + m->gappih*ie; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ r = n - i; ++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0); ++ ty += c->geom.height + m->gappih*ie; + } + i++; + } +@@ -2739,6 +2862,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.46.0 + + +From 8b9db986eddeade22d92fb15a8c836912869be29 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Jul 2022 00:15:32 -0500 +Subject: [PATCH 2/2] allow gaps in monocle layout if requested +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 39e528b1..f4d4095d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int gappih = 10; /* horiz inner gap between windows */ + static const unsigned int gappiv = 10; /* vert inner gap between windows */ +diff --git a/dwl.c b/dwl.c +index d749728a..dbc66ef8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1879,8 +1879,12 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); + n++; ++ if (!monoclegaps) ++ resize(c, m->w, 0); ++ else ++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov, ++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0); + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps.patch b/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps.patch new file mode 100644 index 0000000..db1ddf4 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/vanitygaps/vanitygaps.patch @@ -0,0 +1,357 @@ +From 8d29d5cace06c97917fbc26c673fd37731ac4984 Mon Sep 17 00:00:00 2001 +From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com> +Date: Mon, 17 Aug 2020 14:48:24 -0400 +Subject: [PATCH 1/2] Implement vanitygaps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 21 ++++++++ + dwl.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 160 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..39e528b1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,12 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +138,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index ad21e1ba..fa823957 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -197,6 +197,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -271,6 +275,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -290,6 +295,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -323,6 +335,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 setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -336,6 +349,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -405,6 +419,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ + + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; +@@ -1048,6 +1063,11 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1230,6 +1250,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1566,6 +1592,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2357,6 +2460,16 @@ setfullscreen(Client *c, int fullscreen) + printstatus(); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2701,7 +2814,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps; + int i, n = 0; + Client *c; + +@@ -2711,22 +2824,31 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (smartgaps == n) { ++ oe = 0; // outer gaps disabled ++ } ++ + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0; + else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie; ++ i = 0; ++ my = ty = m->gappoh*oe; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my, ++ .width = mw - m->gappiv*ie, .height = h}, 0); ++ my += c->geom.height + m->gappih*ie; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ r = n - i; ++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0); ++ ty += c->geom.height + m->gappih*ie; + } + i++; + } +@@ -2749,6 +2871,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.48.0 + + +From e3d10f01df9b7d6735ce9e43ebfdca35b8639f0e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Jul 2022 00:15:32 -0500 +Subject: [PATCH 2/2] allow gaps in monocle layout if requested +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 39e528b1..f4d4095d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int gappih = 10; /* horiz inner gap between windows */ + static const unsigned int gappiv = 10; /* vert inner gap between windows */ +diff --git a/dwl.c b/dwl.c +index fa823957..59eabb54 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1922,8 +1922,12 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); + n++; ++ if (!monoclegaps) ++ resize(c, m->w, 0); ++ else ++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov, ++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0); + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +-- +2.48.0 + diff --git a/dwl-bak/dwl-patches/patches/varcol/README.md b/dwl-bak/dwl-patches/patches/varcol/README.md new file mode 100644 index 0000000..9e5b1b0 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/varcol/README.md @@ -0,0 +1,15 @@ +### Description +A variable column layout. + +This layout behaves much the same as the `tile` layout, but adds key bindings that can be used to: +- Increase/decrease the number of non-master columns +- Increase/decrease the colfact to adjust the column spacing +- Push a client in or out of a special `left` column +- Toggle displaying the special `left` column + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/varcol) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/varcol/varcol.patch) + +### Authors +- [minego](https://codeberg.org/minego) diff --git a/dwl-bak/dwl-patches/patches/varcol/varcol.patch b/dwl-bak/dwl-patches/patches/varcol/varcol.patch new file mode 100644 index 0000000..b94baa2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/varcol/varcol.patch @@ -0,0 +1,618 @@ +From 24c197ea93388c222b72c2192b968aa80b444a9a Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 12:32:49 -0600 +Subject: [PATCH] varcol layout + +--- + config.def.h | 25 ++- + dwl.c | 29 ++++ + varcol.c | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 480 insertions(+), 3 deletions(-) + create mode 100644 varcol.c + +diff --git a/config.def.h b/config.def.h +index 9009517..9345ff2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,16 +21,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor isLeft */ + /* examples: +- { "Gimp", NULL, 0, 1, -1 }, ++ { "Gimp", NULL, 0, 1, -1, 0 }, ++ { "slack", NULL, 0, 1, -1, 1 }, + */ +- { "firefox", NULL, 1 << 8, 0, -1 }, ++ { "firefox", NULL, 1 << 8, 0, -1, 0 }, + }; + + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "=O=", varcol }, /* first entry is default */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -152,8 +154,25 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), ++ + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab, pushleft, {0}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft, {0}}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I, incncols, {.i = +1 }}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D, incncols, {.i = -1 }}, ++ { MODKEY, XKB_KEY_h, setcolfact, {.f = -0.05 }}, ++ { MODKEY, XKB_KEY_l, setcolfact, {.f = +0.05 }}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab, pushleft, {0}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft, {0}}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I, incncols, {.i = +1}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D, incncols, {.i = -1 }}, ++ { MODKEY, XKB_KEY_h, setcolfact, {.f = -0.05 }}, ++ { MODKEY, XKB_KEY_l, setcolfact, {.f = +0.05 }}, ++ + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is +diff --git a/dwl.c b/dwl.c +index 5867b0c..35343e7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,8 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ int isLeft; ++ float cfact; + } Client; + + typedef struct { +@@ -205,6 +207,18 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ float colfact[3]; /* Relative sizes of the different column types */ ++ int nmastercols; /* The number of master columns to use */ ++ int nrightcols; /* The number of right "stack" columns to use */ ++ ++ /* ++ NOTE: This patch does not set these values, but leaves these here as a ++ placeholder to make it easier to merge with patches that do set gaps. ++ */ ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + }; + + typedef struct { +@@ -228,6 +242,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int isLeft; + } Rule; + + typedef struct { +@@ -284,6 +299,7 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void incncols(const Arg *arg); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -307,6 +323,7 @@ static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); ++static void pushleft(const Arg *arg); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); +@@ -314,6 +331,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcolfact(const Arg *arg); + 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); +@@ -340,6 +358,7 @@ static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); ++static void varcol(Monitor *m); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); +@@ -351,6 +370,7 @@ static void zoom(const Arg *arg); + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; ++static const float colfact[3] = { 0.1f, 0.6f, 0.3f }; /* The relative factors for the size of each column */ + static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; +@@ -457,12 +477,16 @@ applyrules(Client *c) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; ++ c->isLeft = 0; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; ++ if (r->isLeft >= 0) { ++ c->isLeft = r->isLeft; ++ } + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) +@@ -882,6 +906,9 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; ++ m->colfact[0] = colfact[0]; ++ m->colfact[1] = colfact[1]; ++ m->colfact[2] = colfact[2]; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -2929,6 +2956,8 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++#include "varcol.c" ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +diff --git a/varcol.c b/varcol.c +new file mode 100644 +index 0000000..30fdb85 +--- /dev/null ++++ b/varcol.c +@@ -0,0 +1,429 @@ ++#include ++ ++static inline void ++client_get_size_hints(Client *c, struct wlr_box *max, struct wlr_box *min) ++{ ++ struct wlr_xdg_toplevel *toplevel; ++ struct wlr_xdg_toplevel_state *state; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) { ++ xcb_size_hints_t *size_hints = c->surface.xwayland->size_hints; ++ if (size_hints) { ++ max->width = size_hints->max_width; ++ max->height = size_hints->max_height; ++ min->width = size_hints->min_width; ++ min->height = size_hints->min_height; ++ } ++ return; ++ } ++#endif ++ toplevel = c->surface.xdg->toplevel; ++ state = &toplevel->current; ++ max->width = state->max_width; ++ max->height = state->max_height; ++ min->width = state->min_width; ++ min->height = state->min_height; ++} ++ ++static int isleft(Client *c); ++ ++/* ++ Variable Column Layout ++ ++ - Special 'left' column, with helper to move clients in or out of the left ++ column. This is useful for things like chat, or status monitor windows that ++ you want to keep visible. ++ ++ - Variable number of master columns ++ - Variable number of right columns ++ - Variable number of master windows ++ ++ // TODO Calculate remainders ++*/ ++ ++/* ++ Move a client within a column ++ ++ w The width of the column ++ x The left position of the column ++ offset The offset of this client within the column ++ count The number of clients in the column ++*/ ++static void placeClientInColumn(Monitor *m, Client *c, int w, int x, int offset, int count, int totaln) ++{ ++ struct wlr_box geom = {0}; ++ int monheight = m->w.height; ++ ++ if (totaln > 1) { ++ /* The total available space does not include the outer vertical gapps */ ++ monheight -= (2 * m->gappov); ++ ++ /* ++ Each client removes the inner vertical gapp from its own height, ++ but that leaves us off by one so account for that here. ++ */ ++ monheight += m->gappiv; ++ } ++ ++ geom.width = w; ++ geom.height = (int) floor(monheight / count); ++ ++ geom.x = x; ++ geom.y = m->w.y + (offset * geom.height); ++ ++ /* Adjust the height to account for gapps AFTER adjusting the y position */ ++ if (totaln > 1) { ++ geom.height -= m->gappiv; ++ } ++ ++ resize(c, geom, 0); ++} ++ ++/* ++ variable column layout ++ ++ This layout has a variable number of columns, in 3 categories. ++ 0-1 small left columns, containing clients that have been "pushed" left ++ 1-n master columns ++ 0-n right columns ++*/ ++void varcol(Monitor *m) ++{ ++ int i, mastern, masterw, rightn, leftw, rightw, x, monwidth; ++ unsigned int leftn, totaln, coln, offset; ++ float colfacts; ++ Client *c, *tmp; ++ int nmastercols = m->nmastercols; ++ int nrightcols = m->nrightcols; ++ struct wl_list left_clients; ++ ++ /* ++ Remove each of window that belongs in the left column, so they can be ++ reattached at the end of the list below. ++ */ ++ wl_list_init(&left_clients); ++ ++ i = 0; ++ wl_list_for_each_safe(c, tmp, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (i < m->nmaster) { ++ /* Master */ ++ ; ++ } else if (isleft(c)) { ++ /* Left; Detach and put in the left list */ ++ wl_list_remove(&c->link); ++ wl_list_insert(&left_clients, &c->link); ++ } ++ ++ i++; ++ } ++ ++ /* Reattach the left clients to the main list */ ++ wl_list_for_each_reverse_safe(c, tmp, &left_clients, link) { ++ wl_list_remove(&c->link); ++ wl_list_insert(clients.prev, &c->link); ++ } ++ ++ /* Count the windows for each column type */ ++ totaln = leftn = rightn = mastern = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (mastern < m->nmaster) { ++ mastern++; ++ } else if (isleft(c)) { ++ leftn++; ++ } else { ++ rightn++; ++ } ++ ++ totaln++; ++ } ++ ++ nmastercols = MAX(MIN(mastern, nmastercols), 1); ++ nrightcols = MAX(MIN(rightn, nrightcols), 1); ++ ++ if (mastern == 0) { ++ return; ++ } ++ ++ /* Calculate the total colfacts value */ ++ colfacts = 0; ++ ++ /* Left column */ ++ if (leftn > 0) { ++ colfacts += m->colfact[0]; ++ } ++ ++ /* Center column(s) */ ++ for (i = 0; i < nmastercols; i++) { ++ colfacts += m->colfact[1]; ++ } ++ ++ /* Right column(s) */ ++ if (rightn > 0) { ++ for (i = 0; i < nrightcols; i++) { ++ colfacts += m->colfact[2]; ++ } ++ } ++ ++ /* Calculate the usable width, with gapps removed */ ++ monwidth = m->w.width; ++ ++ if (rightn > 0) { ++ /* Gap to the left of reach right column */ ++ monwidth -= m->gappih * nrightcols; ++ } ++ if (leftn > 0) { ++ /* Gap on the right of the left column */ ++ monwidth -= m->gappih; ++ } ++ ++ if (totaln > 1) { ++ /* Outer gaps */ ++ monwidth -= (2 * m->gappoh); ++ } ++ ++ /* Calculate the width for each column type */ ++ leftw = (int) ((monwidth / colfacts) * m->colfact[0]); ++ masterw = (int) ((monwidth / colfacts) * m->colfact[1]); ++ rightw = (int) ((monwidth / colfacts) * m->colfact[2]); ++ ++ /* Adjust right and left column to fit all clients */ ++ wl_list_for_each(c, &clients, link) { ++ struct wlr_box min = {0}, max = {0}; ++ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ /* Get client size hints */ ++ client_get_size_hints(c, &max, &min); ++ ++ if (i < mastern) { ++ /* Master columns */ ++ ++ } else if (!isleft(c)) { ++ /* Right columns */ ++ x = min.width - rightw; ++ if (x > 0) { ++ rightw += x; ++ masterw -= x; ++ } ++ } else if (leftn > 0) { ++ /* left column */ ++ x = min.width - leftw; ++ if (x > 0) { ++ leftw += x; ++ masterw -= x; ++ } ++ } ++ } ++ ++ /* Place each client */ ++ x = m->w.x; ++ if (leftn > 0) { ++ x += leftw; ++ x += m->gappih; ++ } ++ if (totaln > 1) { ++ x += m->gappoh; ++ } ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link) { ++ struct wlr_box min = {0}, max = {0}; ++ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ /* Get client size hints */ ++ client_get_size_hints(c, &max, &min); ++ ++ if (i < mastern) { ++ /* Master columns */ ++ ++ /* Offset within the section */ ++ offset = i; ++ ++ /* Max number of items in each master column */ ++ coln = (unsigned int) ceil((float) mastern / nmastercols); ++ ++ placeClientInColumn(m, c, masterw, x, offset % coln, coln, totaln); ++ ++ /* Only increment x if this is the last client in this column */ ++ if ((++offset % coln) == 0) { ++ x += masterw; ++ x += m->gappih; ++ } ++ } else if (!isleft(c)) { ++ /* Right columns */ ++ ++ /* Offset within the section */ ++ offset = (i - mastern); ++ ++ /* Max number of items in each right column */ ++ coln = (unsigned int) ceil((float) rightn / nrightcols); ++ ++ placeClientInColumn(m, c, rightw, x, offset % coln, coln, totaln); ++ ++ /* Only increment x if this is the last client in this column */ ++ if ((++offset % coln) == 0) { ++ x += rightw; ++ x += m->gappih; ++ } ++ } else if (leftn > 0) { ++ /* left column */ ++ x = m->w.x; ++ if (totaln > 1) { ++ x += m->gappoh; ++ } ++ ++ /* Offset within the section */ ++ offset = i - (mastern + rightn); ++ ++ /* There is only one left column */ ++ coln = leftn; ++ ++ placeClientInColumn(m, c, leftw, x, offset, leftn, totaln); ++ } ++ ++ i++; ++ } ++} ++ ++static int isleft(Client *c) ++{ ++ if (c == NULL) { ++ return 0; ++ } ++ ++ if (c->mon != NULL && c->mon->m.width <= 2000) { ++ /* The left column is not worth using on a small monitor */ ++ return 0; ++ } ++ ++ return c->isLeft; ++} ++ ++/* Return non-zero if the currently selected client is in a master column */ ++static int ismaster(void) ++{ ++ Client *c, *sel = focustop(selmon); ++ int i; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return 0; ++ } ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (sel == c) { ++ /* c is the selected client, and is index i */ ++ if (i < selmon->nmaster) { ++ return 1; ++ } else { ++ return 0; ++ } ++ } ++ i++; ++ } ++ ++ return 0; ++} ++ ++/* A value >= 1.0 sets that colfact to that value - 1.0 */ ++void setcolfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ int index = 1; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ if (ismaster()) { ++ index = 0; ++ /* master */ ++ index = 0; ++ } else if (isleft(sel)) { ++ /* left */ ++ index = -1; ++ } else { ++ /* right */ ++ index = 1; ++ } ++ index++; ++ ++ if (arg->f >= 1.0) { ++ selmon->colfact[index] = arg->f - 1.0f; ++ } else { ++ /* Adjust the argument based on the selected column */ ++ selmon->colfact[index] += arg->f; ++ } ++ ++ if (selmon->colfact[index] < 0.1) { ++ selmon->colfact[index] = 0.1f; ++ } else if (selmon->colfact[index] > 0.9) { ++ selmon->colfact[index] = 0.9f; ++ } ++ arrange(selmon); ++} ++ ++static void pushleft(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ sel->isLeft = !sel->isLeft; ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++/* ++ Modify either the right or master column count ++*/ ++void incncols(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ if (selmon->nmastercols < 1) selmon->nmastercols = 1; ++ if (selmon->nrightcols < 1) selmon->nrightcols = 1; ++ ++ if (ismaster()) { ++ /* master */ ++ selmon->nmastercols += arg->i; ++ ++ /* Auto adjust nmaster as well */ ++ selmon->nmaster = selmon->nmastercols; ++ } else if (isleft(sel)) { ++ /* left */ ++ ; ++ } else { ++ /* right */ ++ selmon->nrightcols += arg->i; ++ } ++ ++ if (selmon->nmastercols < 1) selmon->nmastercols = 1; ++ if (selmon->nrightcols < 1) selmon->nrightcols = 1; ++ ++ arrange(selmon); ++} ++ +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/viewnextocctag/README.md b/dwl-bak/dwl-patches/patches/viewnextocctag/README.md new file mode 100644 index 0000000..d1a43d2 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/viewnextocctag/README.md @@ -0,0 +1,9 @@ +### Description +View the next or previous tag, skipping any tags that do not have any clients. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/viewnextocctag) +- [2023-01-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/viewnextocctag/viewnextocctag.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl-bak/dwl-patches/patches/viewnextocctag/viewnextocctag.patch b/dwl-bak/dwl-patches/patches/viewnextocctag/viewnextocctag.patch new file mode 100644 index 0000000..f797431 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/viewnextocctag/viewnextocctag.patch @@ -0,0 +1,92 @@ +From 330fa634a83e9b332494fade75552e02583bad6c Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] viewnextocctag + +--- + config.def.h | 2 ++ + dwl.c | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 36 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..e1a6a428 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -130,6 +130,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, viewnextocctag, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, viewnextocctag, {.i = +1} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 6f041a0d..df5461d0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -304,6 +304,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++unsigned int nextocctag(int); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -344,6 +345,7 @@ static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); ++static void viewnextocctag(const Arg *argint); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); + static Monitor *xytomon(double x, double y); +@@ -1868,6 +1870,27 @@ moveresize(const Arg *arg) + } + } + ++unsigned int ++nextocctag(int direction) ++{ ++ unsigned int seltag = selmon->tagset[selmon->seltags]; ++ unsigned int occ = 0, i; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ occ |= c->tags; ++ ++ for (i=0; i 0) ? ++ (seltag == (1u << (TAGCOUNT - 1)) ? 1u : seltag << 1) : ++ (seltag == 1 ? (1u << (TAGCOUNT - 1)) : seltag >> 1); ++ if (seltag & occ) ++ break; ++ } ++ ++ return seltag & TAGMASK; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2852,6 +2875,17 @@ view(const Arg *arg) + printstatus(); + } + ++void ++viewnextocctag(const Arg *arg) ++{ ++ unsigned int tmp; ++ ++ if ((tmp = nextocctag(arg->i)) == selmon->tagset[selmon->seltags]) ++ return; ++ ++ view(&(const Arg){.ui = tmp}); ++} ++ + void + virtualkeyboard(struct wl_listener *listener, void *data) + { +-- +2.45.1 + diff --git a/dwl-bak/dwl-patches/patches/virtual-pointer/README.md b/dwl-bak/dwl-patches/patches/virtual-pointer/README.md new file mode 100644 index 0000000..451d26e --- /dev/null +++ b/dwl-bak/dwl-patches/patches/virtual-pointer/README.md @@ -0,0 +1,12 @@ +### Description +implement wlr_virtual_pointer_v1 for things like wayvnc server to work + +**NOTE:** no longer neccessary if you are using a DWL version after https://codeberg.org/dwl/dwl/commit/ac6074f4fdb8cc263c877f08e16a5805d3bb22d2 + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/v0.5/virtual-pointer) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0096e49402bc59b4050e12cdb9befb79d0011006/virtual-pointer/virtual-pointer.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [youbitchoc](https://github.com/youbitchoc) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/virtual-pointer/virtual-pointer.patch b/dwl-bak/dwl-patches/patches/virtual-pointer/virtual-pointer.patch new file mode 100644 index 0000000..e0a1803 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/virtual-pointer/virtual-pointer.patch @@ -0,0 +1,126 @@ +From 4ab53a41256c8f2eac4c003c43b798b6aa312919 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Mon, 8 Apr 2024 11:39:18 -0500 +Subject: [PATCH] implement virtual-pointer + +--- + dwl.c | 49 +++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 39 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ef27a1d..20840cd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -318,11 +319,13 @@ static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); ++static void updatecapabilities(void); + static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); ++static void virtualpointer(struct wl_listener *listener, void *data); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -357,6 +360,7 @@ static struct wlr_output_manager_v1 *output_mgr; + static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; + static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; + static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; ++static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; + + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; +@@ -1338,7 +1342,6 @@ inputdevice(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; +- uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +@@ -1351,15 +1354,8 @@ inputdevice(struct wl_listener *listener, void *data) + /* TODO handle other input device types */ + break; + } +- +- /* We need to let the wlr_seat know what our capabilities are, which is +- * communiciated to the client. In dwl we always have a cursor, even if +- * there are no pointer devices, so we always include that capability. */ +- /* TODO do we actually require a cursor? */ +- caps = WL_SEAT_CAPABILITY_POINTER; +- if (!wl_list_empty(&keyboards)) +- caps |= WL_SEAT_CAPABILITY_KEYBOARD; +- wlr_seat_set_capabilities(seat, caps); ++ ++ updatecapabilities(); + } + + int +@@ -2331,6 +2327,8 @@ setup(void) + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); ++ virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); ++ LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +@@ -2536,6 +2534,21 @@ unmapnotify(struct wl_listener *listener, void *data) + motionnotify(0); + } + ++void ++updatecapabilities(void) ++{ ++ uint32_t caps; ++ ++ /* We need to let the wlr_seat know what our capabilities are, which is ++ * communicated to the client. In dwl we always have a cursor, even if ++ * there are no pointer devices, so we always include that capability. */ ++ /* TODO do we actually require a cursor? */ ++ caps = WL_SEAT_CAPABILITY_POINTER; ++ if (!wl_list_empty(&keyboards)) ++ caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ wlr_seat_set_capabilities(seat, caps); ++} ++ + void + updatemons(struct wl_listener *listener, void *data) + { +@@ -2674,6 +2687,22 @@ virtualkeyboard(struct wl_listener *listener, void *data) + { + struct wlr_virtual_keyboard_v1 *keyboard = data; + createkeyboard(&keyboard->keyboard); ++ updatecapabilities(); ++} ++ ++void ++virtualpointer(struct wl_listener *listener, void *data) ++{ ++ struct wlr_virtual_pointer_v1_new_pointer_event *event = data; ++ struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; ++ struct wlr_input_device *device = &pointer->pointer.base; ++ createpointer(&pointer->pointer); ++ updatecapabilities(); ++ ++ if (event->suggested_output) { ++ wlr_cursor_map_input_to_output(cursor, device, ++ event->suggested_output); ++ } + } + + Monitor * +-- +2.43.2 + diff --git a/dwl-bak/dwl-patches/patches/warpcursor/README.md b/dwl-bak/dwl-patches/patches/warpcursor/README.md new file mode 100644 index 0000000..34dadb9 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/warpcursor/README.md @@ -0,0 +1,15 @@ +### Description +Warp cursor to the centre of newly focused clients. + +Only moves the cursor if the cursor is currently not on the new client. + +This is my version of the orphaned cursorwarp patch except I left out the +config flag as I think it is unnecessary. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/warpcursor) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/warpcursor/warpcursor.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Faerryn](https://github.com/faerryn) diff --git a/dwl-bak/dwl-patches/patches/warpcursor/warpcursor.patch b/dwl-bak/dwl-patches/patches/warpcursor/warpcursor.patch new file mode 100644 index 0000000..6ac0329 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/warpcursor/warpcursor.patch @@ -0,0 +1,71 @@ +From 4951ccc89dac2b557994b2f6c3aacb2398e2d1b1 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 20:30:01 +1000 +Subject: [PATCH] warpcursor + +--- + dwl.c | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 145fd018..f7ad6c13 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -347,6 +347,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void warpcursor(const Client *c); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -514,6 +515,7 @@ arrange(Monitor *m) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); ++ warpcursor(focustop(selmon)); + } + + void +@@ -1323,6 +1325,10 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ /* Warp cursor to center of client if it is outside */ ++ if (lift) ++ warpcursor(c); ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -2927,6 +2933,27 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++void ++warpcursor(const Client *c) { ++ if (cursor_mode != CurNormal) { ++ return; ++ } ++ if (!c && selmon) { ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ selmon->w.x + selmon->w.width / 2.0 , ++ selmon->w.y + selmon->w.height / 2.0); ++ } ++ else if ( c && (cursor->x < c->geom.x || ++ cursor->x > c->geom.x + c->geom.width || ++ cursor->y < c->geom.y || ++ cursor->y > c->geom.y + c->geom.height)) ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ c->geom.x + c->geom.width / 2.0, ++ c->geom.y + c->geom.height / 2.0); ++} ++ + Monitor * + xytomon(double x, double y) + { +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/wayland-socket-handover/README.md b/dwl-bak/dwl-patches/patches/wayland-socket-handover/README.md new file mode 100644 index 0000000..ffc278a --- /dev/null +++ b/dwl-bak/dwl-patches/patches/wayland-socket-handover/README.md @@ -0,0 +1,21 @@ +### Description + +When your Wayland compositor crashes, your entire session dies with it. This +patch allows your session to survive a restart or crash of dwl by passing in +the Wayland socket from an outside wrapper program such as +[wl-restart](https://github.com/Ferdi265/wl-restart). + +To use, apply this patch and start dwl with `wl-restart --env dwl [args...]`. +This automatically restarts dwl when it crashes and tries to keep applications +alive. + +Currently, only **Qt6** supports surviving a compositor restart. The +environment variable `QT_WAYLAND_RECONNECT=1` needs to be set in order for Qt +apps to stay alive when the compositor crashes. + +## Download +- [git branch](https://codeberg.org/yrlf/dwl/src/branch/feature-socket-handover) +- [2025-03-26](https://codeberg.org/yrlf/dwl-patches/raw/branch/main/patches/wayland-socket-handover/wayland-socket-handover.patch) + +### Authors +- [yrlf](https://codeberg.org/yrlf) diff --git a/dwl-bak/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch b/dwl-bak/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch new file mode 100644 index 0000000..ad026ff --- /dev/null +++ b/dwl-bak/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch @@ -0,0 +1,81 @@ +From 1513c816f834b1620313e69318e2789aa2f091d0 Mon Sep 17 00:00:00 2001 +From: Ferdinand Bachmann +Date: Wed, 26 Mar 2025 17:18:17 +0100 +Subject: [PATCH] startup: implement wayland socket handover (wl-restart --env + mechanism) + +--- + dwl.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 45 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 4816159..2f2ed02 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2232,11 +2233,54 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++static const char* ++perform_socket_handover(void) ++{ ++ char *socket_name = NULL; ++ int socket_fd = -1; ++ ++ /* Parse wayland socket handover environment variables */ ++ if (getenv("WAYLAND_SOCKET_NAME")) { ++ socket_name = getenv("WAYLAND_SOCKET_NAME"); ++ unsetenv("WAYLAND_SOCKET_NAME"); ++ } ++ ++ if (getenv("WAYLAND_SOCKET_FD")) { ++ socket_fd = atoi(getenv("WAYLAND_SOCKET_FD")); ++ unsetenv("WAYLAND_SOCKET_FD"); ++ fcntl(socket_fd, F_SETFD, FD_CLOEXEC); ++ } ++ ++ /* Warn if either environment variable is missing */ ++ if (!socket_name && socket_fd == -1) { ++ return NULL; ++ } else if (!socket_name) { ++ fprintf(stderr, "Wayland socket handover failed, missing WAYLAND_SOCKET_FD\n"); ++ return NULL; ++ } else if (socket_fd == -1) { ++ fprintf(stderr, "Wayland socket handover failed, missing WAYLAND_SOCKET_NAME\n"); ++ return NULL; ++ } ++ ++ /* Add socket to the Wayland display */ ++ if (wl_display_add_socket_fd(dpy, socket_fd) < 0) { ++ fprintf(stderr, "Wayland socket handover failed, failed to add socket FD to display\n"); ++ return NULL; ++ } ++ ++ return socket_name; ++} ++ + void + run(char *startup_cmd) + { ++ /* Attempt Wayland socket handover. */ ++ const char *socket = perform_socket_handover(); ++ + /* Add a Unix socket to the Wayland display. */ +- const char *socket = wl_display_add_socket_auto(dpy); ++ if (!socket) ++ socket = wl_display_add_socket_auto(dpy); ++ + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); +-- +2.49.0 + diff --git a/dwl-bak/dwl-patches/patches/winview/README.md b/dwl-bak/dwl-patches/patches/winview/README.md new file mode 100644 index 0000000..4fb8dd5 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/winview/README.md @@ -0,0 +1,35 @@ +### Description +Implements the function `winview` which switches the visible tags to the tags on which the current client is visible. + +This patch is inspired from . Citing the description of the dwm patch: + +> Dwm tags are a powerfull feature that allows organizing windows in workspaces. Sometime it can be difficult to remember the tag to activate to unhide a window. With the winview patch the window to unhide can be selected from the all-window view. The user switches to the all-window view (Mod1-0), selects the window (Mod1-j/k or using the mouse) and press Mod1-o. The key Mod1-o switches the view to the selected window tag. +> +> #### Recommend patches +> +> The grid layout is well adapted to display many windows in a limited space. Using both grid and pertag patches you will be able to select this layout for the all-window view while keeping your preferred layout for the other views. +> Configuration and Installation +> Using the default configuration file +> +> Make sure the directory where you build dwm does not contain a config.h file; +> Apply the patch; +> Run make and make install. +> +> Using an existing customised configuration file +> +> Apply the patch; Add the following element in the keys array: +> +> `{ MODKEY, XK_o, winview, {0} },` +> +> Run make and make install. +> +> An example of how to insert this line can be found in the default config file template, config.def.h. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview.patch) +- [2024-06-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview-20240606.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/winview) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) + diff --git a/dwl-bak/dwl-patches/patches/winview/winview-20240606.patch b/dwl-bak/dwl-patches/patches/winview/winview-20240606.patch new file mode 100644 index 0000000..d3fc099 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/winview/winview-20240606.patch @@ -0,0 +1,42 @@ +diff --git a/config.def.h b/config.def.h +index a784eb4..d09ee55 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -140,6 +140,7 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY, XKB_KEY_o, winview, {0}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +diff --git a/dwl.c b/dwl.c +index 6f041a0..65df112 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -346,6 +346,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void winview(const Arg *a); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -2877,6 +2878,17 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++void ++winview(const Arg *a) { ++ Arg b = {0}; ++ Client *sel = focustop(selmon); ++ if(!sel) ++ return; ++ b.ui = sel -> tags; ++ view(&b); ++ return; ++} ++ + Monitor * + xytomon(double x, double y) + { diff --git a/dwl-bak/dwl-patches/patches/winview/winview.patch b/dwl-bak/dwl-patches/patches/winview/winview.patch new file mode 100644 index 0000000..66e6b20 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/winview/winview.patch @@ -0,0 +1,42 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..b812525 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -143,6 +143,7 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY, XKB_KEY_o, winview, {0}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +diff --git a/dwl.c b/dwl.c +index dc0c861..5c6862c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -351,6 +351,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void winview(const Arg *a); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -2942,6 +2943,17 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); + } + ++void ++winview(const Arg *a) { ++ Arg b = {0}; ++ Client *sel = focustop(selmon); ++ if(!sel) ++ return; ++ b.ui = sel -> tags; ++ view(&b); ++ return; ++} ++ + Monitor * + xytomon(double x, double y) + { diff --git a/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/README.md b/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/README.md new file mode 100644 index 0000000..dde0935 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/README.md @@ -0,0 +1,9 @@ +### Description +Some windows (wine) games go black screen after losing focus and never recover https://github.com/swaywm/sway/issues/4324. This patch fixes this by handling minimize requests that some xwayland clients do. + + ## Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/xwayland-handle-minimize) +- [2024-04-01](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch) + +### Authors +- [korei999](https://codeberg.org/korei999) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch b/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch new file mode 100644 index 0000000..d6d1f8f --- /dev/null +++ b/dwl-bak/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch @@ -0,0 +1,91 @@ +From 7277f668f19f5a7fcfbbc96e80cb2829487848ca Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Mon, 1 Apr 2024 15:13:11 +0300 +Subject: [PATCH] handle minimize request for xwayland clients + +--- + client.h | 9 ++++++--- + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 25 insertions(+), 3 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..c46cfb2 100644 +--- a/client.h ++++ b/client.h +@@ -94,9 +94,12 @@ client_activate_surface(struct wlr_surface *s, int activated) + { + struct wlr_xdg_toplevel *toplevel; + #ifdef XWAYLAND +- struct wlr_xwayland_surface *xsurface; +- if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { +- wlr_xwayland_surface_activate(xsurface, activated); ++ struct wlr_xwayland_surface *surface; ++ if ((surface = wlr_xwayland_surface_try_from_wlr_surface(s))) { ++ if (activated && surface->minimized) ++ wlr_xwayland_surface_set_minimized(surface, false); ++ ++ wlr_xwayland_surface_activate(surface, activated); + return; + } + #endif +diff --git a/dwl.c b/dwl.c +index 39ce68c..b49f57b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -131,6 +131,7 @@ typedef struct { + #ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; ++ struct wl_listener minimize; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +@@ -412,6 +413,7 @@ static void configurex11(struct wl_listener *listener, void *data); + static void createnotifyx11(struct wl_listener *listener, void *data); + static void dissociatex11(struct wl_listener *listener, void *data); + static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); ++static void minimizenotify(struct wl_listener *listener, void *data); + static void sethints(struct wl_listener *listener, void *data); + static void xwaylandready(struct wl_listener *listener, void *data); + static struct wlr_xwayland *xwayland; +@@ -1177,6 +1179,7 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); ++ wl_list_remove(&c->minimize.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +@@ -2984,6 +2987,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); ++ LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); +@@ -3011,6 +3015,21 @@ getatom(xcb_connection_t *xc, const char *name) + return atom; + } + ++void ++minimizenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, minimize); ++ struct wlr_xwayland_surface *xsurface = c->surface.xwayland; ++ struct wlr_xwayland_minimize_event *e = data; ++ int focused; ++ ++ if (xsurface->surface == NULL || !xsurface->surface->mapped) ++ return; ++ ++ focused = seat->keyboard_state.focused_surface == xsurface->surface; ++ wlr_xwayland_surface_set_minimized(xsurface, !focused && e->minimize); ++} ++ + void + sethints(struct wl_listener *listener, void *data) + { +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/patches/zoomswap/README.md b/dwl-bak/dwl-patches/patches/zoomswap/README.md new file mode 100644 index 0000000..83cbeb1 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/zoomswap/README.md @@ -0,0 +1,54 @@ +### Description +This patch swaps the current window (C) with the previous master (P) when zooming. +``` +Original behaviour : ++-----------------+-------+ +| | | +| | | +| | | +| P +-------| +| | | +| | C | +| | | ++-----------------+-------+ + ++-----------------+-------+ +| | | +| | P | +| | | +| C +-------| +| | | +| | | +| | | ++-----------------+-------+ + +New Behaviour : ++-----------------+-------+ +| | | +| | | +| | | +| C +-------+ +| | | +| | P | +| | | ++-----------------+-------+ + ++-----------------+-------+ +| | | +| | | +| | | +| P +-------+ +| | | +| | C | +| | | ++-----------------+-------+ +``` + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/zoomswap) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap-v0.6.patch) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch new file mode 100644 index 0000000..93de288 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch @@ -0,0 +1,55 @@ +From 2b4e1a8bbb2d17a3da5ca54f2995469dfec5bbbb Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 145fd01..e84202c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -207,6 +207,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Client *prevc; + }; + + typedef struct { +@@ -943,6 +944,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -2968,7 +2970,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -2990,9 +2992,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch new file mode 100644 index 0000000..d20ff32 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch @@ -0,0 +1,55 @@ +From cd4ce3f98b4231515c1363070a7fb5f9654a40bc Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5bf995e..4e2b44e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -208,6 +208,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Client *prevc; + }; + + typedef struct { +@@ -984,6 +985,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -3023,7 +3025,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3045,9 +3047,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.45.2 + diff --git a/dwl-bak/dwl-patches/patches/zoomswap/zoomswap.patch b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap.patch new file mode 100644 index 0000000..76e4466 --- /dev/null +++ b/dwl-bak/dwl-patches/patches/zoomswap/zoomswap.patch @@ -0,0 +1,55 @@ +From c82be3e2069fdb9cbea0da22a4c9ed69f4ab5aea Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index fa76db2..1dea8fe 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -202,6 +202,7 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ Client *prevc; + }; + + typedef struct { +@@ -858,6 +859,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -2793,7 +2795,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -2815,9 +2817,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.43.1 + diff --git a/dwl-bak/dwl-patches/stale-patches/bar-systray-old/README.md b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/README.md new file mode 100644 index 0000000..9c8b94e --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/README.md @@ -0,0 +1,33 @@ +### Description +Add a system tray next to the [bar](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar). Heed the warning, this is far from suckless ^^ + +![preview](systray.png) + +**Superseded by [bar-systray](/dwl/dwl-patches/src/branch/main/patches/bar-systray).** + +### Dependencies +- GTK4 +- [bar.patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar) as mentioned. +- [gtk4-layer-shell](https://github.com/wmww/gtk4-layer-shell) +- [statusnotifier-systray-gtk4](https://codeberg.org/janetski/statusnotifier-systray-gtk4) built as a static library. + +### Applying the patch +The patch applies on top of the bar patch. That needs to be applied first. + +The patch creates subdirectories `lib` and `include`. After patching, but before `make`, install +`libstatusnotifier-systray-gtk4.a` and `snsystray.h` from statusnotifier-systray-gtk4 in the +directories. One possible way to do that: + +1. Clone [https://codeberg.org/janetski/statusnotifier-systray-gtk4](https://codeberg.org/janetski/statusnotifier-systray-gtk4). Can clone to any location. +2. From statusnotifier-systray-gtk4 root: + 1. `$ meson setup --default-library=static --prefix=/ -Dgir=false -Dvala=false -Ddocs=false build` + 2. `$ meson compile -C build` + 3. `$ DESTDIR=$DWLDIR meson install -C build`, where $DWLDIR is the path to dwl root. +3. Finally, from dwl root, run `make`. + +### Download +- [git branch](/janetski/dwl/src/branch/0.7-systray) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar-systray/bar-systray-0.7.patch) + +### Authors +- [janetski](https://codeberg.org/janetski) ([.vetu](https://discordapp.com/users/355488216469471242) on discord) diff --git a/dwl-bak/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch new file mode 100644 index 0000000..eae4561 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch @@ -0,0 +1,398 @@ +From 71f7b97dca2d781668e826aae7e06544958534f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Janne=20Vetel=C3=A4inen?= +Date: Fri, 23 Aug 2024 18:39:17 +0300 +Subject: [PATCH 1/1] Add a system tray next to the sewn's bar + +--- + Makefile | 8 +- + config.def.h | 2 + + dwl.c | 222 ++++++++++++++++++++++++++++++++++++++++++++- + include/.gitignore | 1 + + lib/.gitignore | 1 + + 5 files changed, 225 insertions(+), 9 deletions(-) + create mode 100644 include/.gitignore + create mode 100644 lib/.gitignore + +diff --git a/Makefile b/Makefile +index 9bc67db..853b04c 100644 +--- a/Makefile ++++ b/Makefile +@@ -7,14 +7,14 @@ include config.mk + DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ + -DVERSION=\"$(VERSION)\" $(XWAYLAND) + DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ +- -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ ++ -Wno-unused-parameter -Wshadow -Wunused-macros \ + -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +-LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) gtk4 gtk4-layer-shell-0 ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -Iinclude ++LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) -Llib -lstatusnotifier-systray-gtk4 + + all: dwl + dwl: dwl.o util.o +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..bb9366f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,6 +11,8 @@ static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); ++static const int trayspacing = 4; /* Spacing between icons in system tray */ ++static const int traymargins = 4; /* System tray inner margins */ + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + static uint32_t colors[][3] = { +diff --git a/dwl.c b/dwl.c +index ece537a..24f550a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -72,9 +72,22 @@ + #include "util.h" + #include "drwl.h" + ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ + /* macros */ ++#ifndef MAX + #define MAX(A, B) ((A) > (B) ? (A) : (B)) ++#endif /* MAX */ ++#ifndef MIN + #define MIN(A, B) ((A) < (B) ? (A) : (B)) ++#endif /* MIN */ + #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) +@@ -324,6 +337,15 @@ 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 gtkactivate(GtkApplication *app, void *data); ++static void gtkclosewindows(void *data, void *udata); ++static void gtkhandletogglebarmsg(void *data); ++static void gtkhandlewidthnotify(SnSystray *systray, GParamSpec *pspec, void *data); ++static void* gtkinit(void *data); ++static void gtkspawnstray(Monitor *m, GtkApplication *app); ++static void gtkterminate(void *data); ++static void gtktoggletray(void *data, void *udata); ++static GdkMonitor* gtkwlrtogdkmon(Monitor *wlrmon); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -380,7 +402,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); +-static void updatebar(Monitor *m); ++static void updatebar(Monitor *m, int traywidth); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -394,6 +416,8 @@ static void zoom(const Arg *arg); + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; ++static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ++static pthread_t gtkthread; /* Gtk functions are only allowed to be called from this thread */ + static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; +@@ -1187,7 +1211,7 @@ createmon(struct wl_listener *listener, void *data) + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; +- updatebar(m); ++ updatebar(m, 0); + + wl_list_insert(&mons, &m->link); + drawbars(); +@@ -1518,6 +1542,8 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ pthread_mutex_lock(&mutex); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); +@@ -1566,6 +1592,7 @@ drawbar(Monitor *m) + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); ++ pthread_mutex_unlock(&mutex); + } + + void +@@ -1710,6 +1737,174 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++void ++gtkactivate(GtkApplication *app, void *data) ++{ ++ GdkDisplay *display; ++ GtkCssProvider *cssp; ++ char csss[64]; ++ Monitor *m; ++ uint32_t bgcolor; ++ ++ bgcolor = colors[SchemeNorm][1] >> 8; ++ display = gdk_display_get_default(); ++ cssp = gtk_css_provider_new(); ++ sprintf(csss, "window{background-color:#%06x;}", bgcolor); ++ gtk_css_provider_load_from_string(cssp, csss); ++ gtk_style_context_add_provider_for_display(display, ++ GTK_STYLE_PROVIDER(cssp), ++ GTK_STYLE_PROVIDER_PRIORITY_USER); ++ ++ wl_list_for_each(m, &mons, link) ++ gtkspawnstray(m, app); ++ ++ g_object_unref(cssp); ++} ++ ++void ++gtkclosewindows(void *data, void *udata) ++{ ++ GtkWindow *window = GTK_WINDOW(data); ++ ++ gtk_window_close(window); ++} ++ ++void ++gtkhandletogglebarmsg(void *data) ++{ ++ GtkApplication *app; ++ GList *windows; ++ ++ app = GTK_APPLICATION(g_application_get_default()); ++ windows = gtk_application_get_windows(app); ++ g_list_foreach(windows, gtktoggletray, data); ++} ++ ++void ++gtkhandlewidthnotify(SnSystray *systray, GParamSpec *pspec, void *data) ++{ ++ Monitor *m = (Monitor *)data; ++ int traywidth; ++ ++ traywidth = sn_systray_get_width(systray); ++ ++ updatebar(m, traywidth); ++ drawbar(m); ++} ++ ++void* ++gtkinit(void *data) ++{ ++ GtkApplication *app = gtk_application_new("org.dwl.systray", ++ G_APPLICATION_NON_UNIQUE); ++ g_signal_connect(app, "activate", G_CALLBACK(gtkactivate), NULL); ++ g_application_run(G_APPLICATION(app), 0, NULL); ++ ++ g_object_unref(app); ++ ++ return NULL; ++} ++ ++void ++gtkspawnstray(Monitor *m, GtkApplication *app) ++{ ++ GdkMonitor *gdkmon; ++ GtkWindow *window; ++ SnSystray *systray; ++ const char *conn; ++ gboolean anchors[4]; ++ int iconsize, tray_init_width, tray_height; ++ ++ gdkmon = gtkwlrtogdkmon(m); ++ if (gdkmon == NULL) ++ die("Failed to get gdkmon"); ++ ++ conn = gdk_monitor_get_connector(gdkmon); ++ iconsize = m->b.real_height - 2 * traymargins; ++ tray_height = m->b.real_height; ++ tray_init_width = m->b.real_height; ++ ++ if (topbar) { ++ anchors[GTK_LAYER_SHELL_EDGE_LEFT] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_TOP] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = false; ++ } else { ++ anchors[GTK_LAYER_SHELL_EDGE_LEFT] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_TOP] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = true; ++ } ++ ++ systray = sn_systray_new(iconsize, ++ traymargins, ++ trayspacing, ++ conn); ++ window = GTK_WINDOW(gtk_window_new()); ++ ++ gtk_window_set_default_size(window, tray_init_width, tray_height); ++ gtk_window_set_child(window, GTK_WIDGET(systray)); ++ gtk_window_set_application(window, app); ++ gtk_layer_init_for_window(window); ++ gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM); ++ gtk_layer_set_exclusive_zone(window, -1); ++ gtk_layer_set_monitor(window, gdkmon); ++ ++ for (int j = 0; j < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; j++) { ++ gtk_layer_set_anchor(window, j, anchors[j]); ++ } ++ ++ updatebar(m, tray_init_width); ++ g_signal_connect(systray, "notify::curwidth", G_CALLBACK(gtkhandlewidthnotify), m); ++ gtk_window_present(window); ++} ++ ++void ++gtkterminate(void *data) ++{ ++ GtkApplication *app; ++ GList *windows; ++ ++ app = GTK_APPLICATION(g_application_get_default()); ++ windows = gtk_application_get_windows(app); ++ g_list_foreach(windows, gtkclosewindows, NULL); ++} ++ ++GdkMonitor* ++gtkwlrtogdkmon(Monitor *wlrmon) ++{ ++ GdkMonitor *gdkmon = NULL; ++ ++ GListModel *gdkmons; ++ GdkDisplay *display; ++ const char *gdkname; ++ const char *wlrname; ++ unsigned int i; ++ ++ wlrname = wlrmon->wlr_output->name; ++ display = gdk_display_get_default(); ++ gdkmons = gdk_display_get_monitors(display); ++ ++ for (i = 0; i < g_list_model_get_n_items(gdkmons); i++) { ++ GdkMonitor *mon = g_list_model_get_item(gdkmons, i); ++ gdkname = gdk_monitor_get_connector(mon); ++ if (strcmp(wlrname, gdkname) == 0) ++ gdkmon = mon; ++ } ++ ++ return gdkmon; ++} ++ ++void ++gtktoggletray(void *data, void *udata) ++{ ++ GtkWidget *widget = GTK_WIDGET(data); ++ int *pbarvisible = (int *)udata; ++ int barvisible = GPOINTER_TO_INT(pbarvisible); ++ ++ gtk_widget_set_visible(widget, barvisible); ++} ++ + void + gpureset(struct wl_listener *listener, void *data) + { +@@ -2293,6 +2488,8 @@ powermgrsetmode(struct wl_listener *listener, void *data) + void + quit(const Arg *arg) + { ++ g_idle_add_once(gtkterminate, NULL); ++ pthread_join(gtkthread, NULL); + wl_display_terminate(dpy); + } + +@@ -2836,6 +3033,9 @@ setup(void) + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } + #endif ++ ++ // Gtk functions are only allowed to be called from this thread. ++ pthread_create(>kthread, NULL, >kinit, NULL); + } + + void +@@ -2943,9 +3143,21 @@ tile(Monitor *m) + void + togglebar(const Arg *arg) + { ++ int barvisible; ++ int *pbarvisible; ++ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); ++ ++ // Notify gtkthread ++ if (selmon->scene_buffer->node.enabled) ++ barvisible = 1; ++ else ++ barvisible = 0; ++ ++ pbarvisible = GINT_TO_POINTER(barvisible); ++ g_idle_add_once(gtkhandletogglebarmsg, pbarvisible); + } + + void +@@ -3140,7 +3352,7 @@ updatemons(struct wl_listener *listener, void *data) + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { +- updatebar(m); ++ updatebar(m, 0); + drawbar(m); + } + +@@ -3155,7 +3367,7 @@ updatemons(struct wl_listener *listener, void *data) + } + + void +-updatebar(Monitor *m) ++updatebar(Monitor *m, int traywidth) + { + size_t i; + int rw, rh; +@@ -3163,7 +3375,7 @@ updatebar(Monitor *m) + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; +- m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale) - traywidth; + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + +diff --git a/include/.gitignore b/include/.gitignore +new file mode 100644 +index 0000000..424c745 +--- /dev/null ++++ b/include/.gitignore +@@ -0,0 +1 @@ ++*.h +diff --git a/lib/.gitignore b/lib/.gitignore +new file mode 100644 +index 0000000..10301e2 +--- /dev/null ++++ b/lib/.gitignore +@@ -0,0 +1 @@ ++*.a +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/bar-systray-old/systray.png b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/systray.png new file mode 100644 index 0000000..54f9f98 Binary files /dev/null and b/dwl-bak/dwl-patches/stale-patches/bar-systray-old/systray.png differ diff --git a/dwl-bak/dwl-patches/stale-patches/master-right/README.md b/dwl-bak/dwl-patches/stale-patches/master-right/README.md new file mode 100644 index 0000000..0f69bbc --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/master-right/README.md @@ -0,0 +1,13 @@ +### Description +Show the master area to the right. + +### Reason for deprecation +I created this patch for a user on Discord and I have never used it. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/master-right) +- [main 2024-09-01](/dwl/dwl-patches/raw/branch/main/patches/master-right/master-right.patch) +- [master-right-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/master-right/master-right-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl-bak/dwl-patches/stale-patches/master-right/master-right-0.7.patch b/dwl-bak/dwl-patches/stale-patches/master-right/master-right-0.7.patch new file mode 100644 index 0000000..e4845f4 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/master-right/master-right-0.7.patch @@ -0,0 +1,35 @@ +From f72236247e5e7cb23c3cac86b496cdd2c523f7ff Mon Sep 17 00:00:00 2001 +From: Sevz17 +Date: Fri, 25 Jun 2021 19:50:56 -0500 +Subject: [PATCH] show master area to the right +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a2711f67..50b057a7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2710,11 +2710,12 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, ++ resize(c, (struct wlr_box){.x = m->w.x + m->w.width - mw, ++ .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ty, + .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); + ty += c->geom.height; + } +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/master-right/master-right.patch b/dwl-bak/dwl-patches/stale-patches/master-right/master-right.patch new file mode 100644 index 0000000..70c80a1 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/master-right/master-right.patch @@ -0,0 +1,35 @@ +From 0afd0a98998dda20e4fe4f4d2b5fcdec49c448c3 Mon Sep 17 00:00:00 2001 +From: Sevz17 +Date: Fri, 25 Jun 2021 19:50:56 -0500 +Subject: [PATCH] show master area to the right +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 9021e442..2bd354a3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2670,11 +2670,12 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, ++ resize(c, (struct wlr_box){.x = m->w.x + m->w.width - mw, ++ .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ty, + .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); + ty += c->geom.height; + } +-- +2.46.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/press_repeat_release/README.md b/dwl-bak/dwl-patches/stale-patches/press_repeat_release/README.md new file mode 100644 index 0000000..b9b4ff3 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/press_repeat_release/README.md @@ -0,0 +1,15 @@ +### Description +This patch adds 3 additional options to the `Key` struct, `on_press`, `on_repeat` and `on_release` which can be used to control which events a key binding should be triggered on. + +NOTE: Due to concerns about patching difficulties this patch does NOT include any changes to `config.def.h`. After applying you will need to add the 3 additional initializers to each key binding that you would like to modify. Any key binding that is not updated will cause a build warning but should function as it does in vanilla. + +2025-01-04 Moved to stale patches. +Outstanding issues with this patch: https://codeberg.org/dwl/dwl-patches/issues/98 +Patch maintainer notes he is no longer maintaining dwl patches: https://codeberg.org/dwl/dwl-patches/pulls/102 + +### Download +- [git branch](https://codeberg.org/USERNAME/dwl/src/branch/press_repeat_release) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/press_repeat_release/press_repeat_release.patch) + +### Authors +- [minego](https://codeberg.org/minego) diff --git a/dwl-bak/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch b/dwl-bak/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch new file mode 100644 index 0000000..b86666d --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch @@ -0,0 +1,108 @@ +From aee1dc3e9ca4d8deec5432d0c64921af6e301ecd Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 15:59:50 -0600 +Subject: [PATCH 1/2] onpress, onrepeat, onrelease + +--- + dwl.c | 18 ++++++++++++------ + 1 file changed, 12 insertions(+), 6 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..43bbf0c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -146,6 +146,10 @@ typedef struct { + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; ++ ++ int on_press; ++ int on_repeat; ++ int on_release; + } Key; + + typedef struct { +@@ -286,7 +290,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_release); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1428,7 +1432,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_release) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1439,8 +1443,10 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- k->func(&k->arg); +- return 1; ++ if ((on_press && k->on_press) || (on_repeat && k->on_repeat) || (on_release && k->on_release)) { ++ k->func(&k->arg); ++ return 1; ++ } + } + } + return 0; +@@ -1470,7 +1476,7 @@ keypress(struct wl_listener *listener, void *data) + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; ++ handled = keybinding(mods, syms[i], event->state == WL_KEYBOARD_KEY_STATE_PRESSED, 0, event->state == WL_KEYBOARD_KEY_STATE_RELEASED) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { +@@ -1518,7 +1524,7 @@ keyrepeat(void *data) + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keysyms[i], 0, 1, 0); + + return 0; + } +-- +2.44.0 + + +From 1875bb171c9b0cd2fb03bb7e6c3fb400e33eeaf1 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 16:26:52 -0600 +Subject: [PATCH 2/2] Modified logic so that an unmodified keybinding with + default values for the new flags will behave as it does in vanilla, while + keybindings with customized flags will function as expected + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 43bbf0c..55e7a40 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1443,7 +1443,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_ + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- if ((on_press && k->on_press) || (on_repeat && k->on_repeat) || (on_release && k->on_release)) { ++ if ((k->on_press == 0 && k->on_repeat == 0 && k->on_release == 0) || ++ (on_press && k->on_press) || ++ (on_repeat && k->on_repeat) || ++ (on_release && k->on_release) ++ ) { + k->func(&k->arg); + return 1; + } +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/remembertags/README.md b/dwl-bak/dwl-patches/stale-patches/remembertags/README.md new file mode 100644 index 0000000..b45e87c --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/remembertags/README.md @@ -0,0 +1,16 @@ +### Description +This patch modifies the behavior when selecting tags so that selecting a tag will also enable any other tags that were previously visible. + +For example: +1. Select tag 5, with mod+5 +2. Toggle tag 8, with ctrl+mod+8 +3. Select tag 1, with mod+1. Tags 5 and 8 should no longer be visible. +4. Select tag 5 again, with mod+5. Tag 8 should be visible since it was remembered. +5. Select tag 5 again, with mod_5. Selecting the already selected tag resets any remembered tags, so now tag 5 should be the only one visible. + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/remembertags) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/remembertags/remembertags.patch) + +### Authors +- [minego](https://codeberg.org/minego) \ No newline at end of file diff --git a/dwl-bak/dwl-patches/stale-patches/remembertags/remembertags.patch b/dwl-bak/dwl-patches/stale-patches/remembertags/remembertags.patch new file mode 100644 index 0000000..fd6135e --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/remembertags/remembertags.patch @@ -0,0 +1,105 @@ +From fea6eb3cfc84ede8403c89a3230f5c658a6c7bd1 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 13:05:09 -0600 +Subject: [PATCH] remembertags + +--- + config.def.h | 8 ++++---- + dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 52 insertions(+), 4 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..2312802 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -105,10 +105,10 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + #define MODKEY WLR_MODIFIER_ALT + + #define TAGKEYS(KEY,SKEY,TAG) \ +- { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY, KEY, remembertagsview, {.i = TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, SKEY, toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +diff --git a/dwl.c b/dwl.c +index 5867b0c..31a81aa 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -205,6 +205,11 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ unsigned int createtag[2]; /* Create windows on the last tag directly selected, not all selected */ ++ struct { ++ unsigned int tagset; ++ Client *zoomed; ++ } remembered[31]; + }; + + typedef struct { +@@ -308,6 +313,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); + static void quit(const Arg *arg); ++static void remembertagsview(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -1951,6 +1957,48 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++remembertagsview(const Arg *arg) { ++ unsigned newtags = (1 << arg->i) & TAGMASK; ++ int oldtag; ++ int active; ++ unsigned int newcreate; ++ ++ if (selmon == NULL) { ++ return; ++ } ++ ++ oldtag = selmon->createtag[selmon->seltags]; ++ active = (oldtag == arg->i); ++ ++ if (oldtag < TAGCOUNT) { ++ selmon->remembered[oldtag].tagset = selmon->tagset[selmon->seltags]; ++ } ++ ++ selmon->seltags ^= 1; /*toggle tagset*/ ++ ++ if (-1 == arg->i) { ++ /* A specific tag was not specified */ ++ active = 0; ++ newcreate = selmon->createtag[selmon->seltags]; ++ } else { ++ newcreate = arg->i; ++ } ++ ++ if (active) { ++ /* Select twice to isolate the tag */ ++ selmon->tagset[selmon->seltags] = newtags; ++ } else if (arg->i < TAGCOUNT) { ++ /* Restore whatever was previously on this tag */ ++ selmon->tagset[selmon->seltags] = newtags | selmon->remembered[newcreate].tagset; ++ } ++ ++ selmon->createtag[selmon->seltags] = newcreate; ++ focusclient(focustop(selmon), 1); ++ arrange(selmon); ++ printstatus(); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/tab-pango/README.md b/dwl-bak/dwl-patches/stale-patches/tab-pango/README.md new file mode 100644 index 0000000..c7e4a1c --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/tab-pango/README.md @@ -0,0 +1,10 @@ +### Description +Add a tab bar or window title to the top or bottom of windows. + +**This is the old version of the `tab` patch. Deprecated because the [new version](https://codeberg.org/dwl/dwl-patches/raw/branch-main/patches/tab) is significantly more efficient and well-written than this, and it better adheres to the suckless philosophy.** + +### Download +- [2024-03-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tab/tab.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl-bak/dwl-patches/stale-patches/tab-pango/tab.patch b/dwl-bak/dwl-patches/stale-patches/tab-pango/tab.patch new file mode 100644 index 0000000..5b7b8a0 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/tab-pango/tab.patch @@ -0,0 +1,461 @@ +From b624206781513cdff1b9609fc5ac4b848094e1b4 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Fri, 15 Mar 2024 16:37:23 -0500 +Subject: [PATCH] Tabbed patch + +--- + Makefile | 2 +- + config.def.h | 18 +++- + dwl.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 281 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index a67fdd3..182eb87 100644 +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement -Wno-unu + -Werror=strict-prototypes -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots wayland-server xkbcommon libinput cairo pangocairo $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 9009517..1ca270f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,16 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const double title_border_width = 0.75; ++static const unsigned int title_padding = 11; ++static const int title_top = 0; ++static const LayoutType floating_title_type = LAYOUT_TYPE_LABEL; ++static const char title_font[] = "Dejavu Sans Mono 10.5"; ++static const float title_font_color[] = COLOR(0xffffffff); ++static const float title_focus_bg[] = COLOR(0x3b3b3bff); ++static const float title_root_bg[] = COLOR(0x131313ff); ++static const float title_urgent_bg[] = COLOR(0x00ff00ff); ++static const float title_border_color[] = COLOR(0x3b3b3bff); + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -30,10 +40,10 @@ static const Rule rules[] = { + + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol type render_only_top arrange function */ ++ { "[]=", LAYOUT_TYPE_NONE, 0, tile }, ++ { "><>", LAYOUT_TYPE_LABEL, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS, 1, monocle }, + }; + + /* monitors */ +diff --git a/dwl.c b/dwl.c +index 5867b0c..e613d17 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2,6 +2,11 @@ + * See LICENSE file for copyright and license details. + */ + #include ++#include ++#include ++#include ++#include ++#include + #include + #include + #include +@@ -13,8 +18,10 @@ + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -110,6 +117,7 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *titlebar; + struct wl_list link; + struct wl_list flink; + union { +@@ -137,7 +145,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, titleisinit, istabbed; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -179,8 +187,17 @@ typedef struct { + struct wl_listener surface_commit; + } LayerSurface; + ++typedef enum { ++ LAYOUT_TYPE_NONE, ++ LAYOUT_TYPE_LABEL, ++ LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS, ++ LAYOUT_TYPE_TABS_ALWAYS ++} LayoutType; ++ + typedef struct { + const char *symbol; ++ LayoutType type; ++ int render_top_only; + void (*arrange)(Monitor *); + } Layout; + +@@ -282,6 +299,7 @@ static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -309,6 +327,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + static void printstatus(void); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); ++static void rendertitlebar(Client *client); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +@@ -349,6 +368,7 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + static void zoom(const Arg *arg); + + /* variables */ ++static int title_height; + static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; +@@ -973,6 +993,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->titleisinit = c->istabbed = 0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -1360,6 +1381,22 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ if (!m) ++ return NULL; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && (!c->isfloating || !m->lt[m->sellt]->arrange))) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -2003,6 +2040,195 @@ skip: + wlr_output_state_finish(&pending); + } + ++struct text_buffer { ++ struct wlr_buffer base; ++ void *data; ++ uint32_t format; ++ size_t stride; ++}; ++ ++static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { ++ struct text_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); ++ free(buffer->data); ++ free(buffer); ++} ++ ++static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, ++ uint32_t flags, void **data, uint32_t *format, size_t *stride) { ++ struct text_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); ++ if(data != NULL) { ++ *data = (void *)buffer->data; ++ } ++ if(format != NULL) { ++ *format = buffer->format; ++ } ++ if(stride != NULL) { ++ *stride = buffer->stride; ++ } ++ return true; ++} ++ ++static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { ++ // This space is intentionally left blank ++} ++ ++static const struct wlr_buffer_impl text_buffer_impl = { ++ .destroy = text_buffer_destroy, ++ .begin_data_ptr_access = text_buffer_begin_data_ptr_access, ++ .end_data_ptr_access = text_buffer_end_data_ptr_access, ++}; ++ ++static struct text_buffer *text_buffer_create(uint32_t width, uint32_t height, uint32_t stride) { ++ struct text_buffer *buffer = calloc(1, sizeof(*buffer)); ++ if (buffer == NULL) { ++ return NULL; ++ } ++ ++ wlr_buffer_init(&buffer->base, &text_buffer_impl, width, height); ++ buffer->format = DRM_FORMAT_ARGB8888; ++ buffer->stride = stride; ++ ++ buffer->data = malloc(buffer->stride * height); ++ if (buffer->data == NULL) { ++ free(buffer); ++ return NULL; ++ } ++ ++ return buffer; ++} ++ ++void ++rendertitlebar(Client *c) ++{ ++ struct wl_list *init_destroy, *cursor_destroy; ++ cairo_surface_t *surface; ++ cairo_status_t status; ++ cairo_t *cr; ++ PangoFontDescription *desc; ++ PangoLayout *layout; ++ LayoutType title_type; ++ Client *l, *sel; ++ unsigned int len, tabsize, i; ++ const char *title; ++ const float *color; ++ unsigned char *data; ++ int stride; ++ struct text_buffer *text_buffer; ++ void *data_ptr; ++ ++ if (!c || !c->scene || !c->mon || !selmon || (!VISIBLEON(c, selmon) && c->mon == selmon)) ++ return; ++ ++ if (c->titleisinit) { ++ init_destroy = cursor_destroy = &c->titlebar->node.events.destroy.listener_list; ++ do { ++ cursor_destroy = cursor_destroy->next; ++ } while (cursor_destroy && cursor_destroy != init_destroy); ++ if (!cursor_destroy) { ++ return; ++ } ++ wlr_scene_node_destroy(&c->titlebar->node); ++ } ++ c->titleisinit = c->istabbed = 0; ++ ++ sel = focustop_onlytiled(c->mon, c->isfloating + 1); ++ ++ if (c->isfullscreen) ++ return; ++ title_type = c->isfloating ? floating_title_type : c->mon->lt[c->mon->sellt]->type; ++ ++ if (title_type == LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS || title_type == LAYOUT_TYPE_TABS_ALWAYS) { ++ len = 0; ++ wl_list_for_each(l, &clients, link) { ++ if (VISIBLEON(l, c->mon) && l->isfloating == c->isfloating) ++ len++; ++ } ++ if (title_type == LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS && len <= 1) ++ return; ++ } ++ ++ if (c->mon->lt[c->mon->sellt]->render_top_only == 1 && !c->isfloating && c != sel) { ++ c->istabbed = 1; ++ return; ++ } /*else if (c->mon->lt[c->mon->sellt]->render_top_only == 2 && c != sel) { ++ c->istabbed = 1; ++ return; ++ }*/ ++ ++ if (title_type == LAYOUT_TYPE_NONE) ++ return; ++ ++ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, c->geom.width, title_height); ++ if ((status = cairo_surface_status(surface)) != CAIRO_STATUS_SUCCESS) { ++ wlr_log(WLR_ERROR, "cairo_image_surface_create failed: %s", ++ cairo_status_to_string(status)); ++ return; ++ } ++ cr = cairo_create(surface); ++ desc = pango_font_description_from_string(title_font); ++ layout = pango_cairo_create_layout(cr); ++ pango_layout_set_font_description(layout, desc); ++ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); ++ ++ cairo_set_line_width(cr, title_border_width); ++ ++ if (title_type == LAYOUT_TYPE_LABEL) { ++ cairo_rectangle(cr, 0, 0, c->geom.width, title_height); ++ cairo_set_source_rgba(cr, title_focus_bg[0], title_focus_bg[1], title_focus_bg[2], title_focus_bg[3]); ++ cairo_fill_preserve(cr); ++ cairo_set_source_rgba(cr, title_border_color[0], title_border_color[1], title_border_color[2], title_border_color[3]); ++ cairo_stroke(cr); ++ cairo_set_source_rgba(cr, title_font_color[0], title_font_color[1], title_font_color[2], title_font_color[3]); ++ title = client_get_title(c); ++ pango_layout_set_text(layout, title ? title : " ", (c->geom.width - title_padding) * PANGO_SCALE); ++ cairo_move_to(cr, title_padding, 0); ++ pango_cairo_show_layout(cr, layout); ++ } else { ++ tabsize = c->geom.width / len; ++ i = 0; ++ wl_list_for_each(l, &clients, link) { ++ if (VISIBLEON(l, c->mon) && l->isfloating == c->isfloating) { ++ cairo_rectangle(cr, i * tabsize, 0, (i + 1) * tabsize, title_height); ++ color = (l == sel) ? title_focus_bg ++ : (c->isurgent ? title_urgent_bg : title_root_bg); ++ cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]); ++ cairo_fill_preserve(cr); ++ cairo_set_source_rgba(cr, title_border_color[0], title_border_color[1], title_border_color[2], title_border_color[3]); ++ cairo_stroke(cr); ++ cairo_set_source_rgba(cr, title_font_color[0], title_font_color[1], title_font_color[2], title_font_color[3]); ++ title = client_get_title(l); ++ pango_layout_set_text(layout, title ? title : " ", (tabsize - title_padding) * PANGO_SCALE); ++ cairo_move_to(cr, (i * tabsize) + title_padding, 0); ++ pango_cairo_show_layout(cr, layout); ++ i++; ++ } ++ } ++ } ++ ++ data = cairo_image_surface_get_data(surface); ++ stride = cairo_image_surface_get_stride(surface); ++ text_buffer = text_buffer_create(c->geom.width, title_height, stride); ++ ++ if(!wlr_buffer_begin_data_ptr_access(&text_buffer->base, ++ WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data_ptr, NULL, NULL)) { ++ wlr_log(WLR_ERROR, "%s", "Failed to get pointer access to text buffer"); ++ return; ++ } ++ memcpy(data_ptr, data, stride * title_height); ++ wlr_buffer_end_data_ptr_access(&text_buffer->base); ++ cairo_surface_destroy(surface); ++ ++ c->titlebar = wlr_scene_buffer_create(c->scene, &text_buffer->base); ++ c->titleisinit = c->istabbed = 1; ++ ++ wlr_scene_node_set_position(&c->titlebar->node, 0, !title_top ? c->geom.height - title_height : 0); ++ wlr_scene_node_raise_to_top(&c->titlebar->node); ++ ++ pango_font_description_free(desc); ++ g_object_unref(layout); ++ cairo_destroy(cr); ++} ++ + void + requestdecorationmode(struct wl_listener *listener, void *data) + { +@@ -2036,24 +2262,30 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; + struct wlr_box clip; ++ unsigned int th; ++ int draw_borders = 1; + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + ++ rendertitlebar(c); ++ th = c->istabbed ? title_height : c->bw; ++ + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, title_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (title_top && c->istabbed) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (!title_top && c->istabbed) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - (c->bw + th)); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - (c->bw + th)); ++ wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - (title_top ? c->bw : th)); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, title_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, title_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - (c->bw + th)); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } +@@ -2274,6 +2506,11 @@ setup(void) + + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ cairo_surface_t *surface; ++ cairo_t *cr; ++ PangoFontDescription *desc; ++ PangoLayout *layout; ++ + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) +@@ -2506,6 +2743,24 @@ setup(void) + + wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); + ++ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); ++ ++ cr = cairo_create(surface); ++ ++ desc = pango_font_description_from_string(title_font); ++ /* Create Pango layout. */ ++ layout = pango_cairo_create_layout(cr); ++ pango_layout_set_font_description(layout, desc); ++ pango_layout_set_text(layout, " ", -1); ++ /* Set width and height to text size */ ++ pango_layout_get_pixel_size(layout, NULL, &title_height); ++ ++ /* Cleanup */ ++ pango_font_description_free (desc); ++ cairo_surface_destroy(surface); ++ g_object_unref (layout); ++ cairo_destroy(cr); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2978,6 +3233,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = borderpx; ++ c->titleisinit = c->istabbed = 0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.44.0 + diff --git a/dwl-bak/dwl-patches/stale-patches/togglekblayout/README.md b/dwl-bak/dwl-patches/stale-patches/togglekblayout/README.md new file mode 100644 index 0000000..366a6d8 --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/togglekblayout/README.md @@ -0,0 +1,14 @@ +### Description + +> This patch is no longer being maintained by me [wochap](https://codeberg.org/wochap), since I'm now using a different patch specific to my use case: https://codeberg.org/wochap/dwl/src/branch/v0.6-b/xkb-rules-switcher/xkb-rules-switcher.patch + +Switch between multiple keyboard layouts at runtime. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/togglekblayout) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayout/togglekblayout.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Stivvo](https://github.com/Stivvo) + diff --git a/dwl-bak/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch b/dwl-bak/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch new file mode 100644 index 0000000..4ef802a --- /dev/null +++ b/dwl-bak/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch @@ -0,0 +1,107 @@ +From 1bb99c78da484ce6036dc997962ed2f4c0d11208 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 19 Oct 2023 23:21:49 -0500 +Subject: [PATCH 1/2] apply main...Stivvo:toggleKbLayout.patch + +--- + config.def.h | 6 ++++++ + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 26 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..caa09ea 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,11 @@ static const struct xkb_rule_names xkb_rules = { + static const int repeat_rate = 25; + static const int repeat_delay = 600; + ++/* gb will be set the first time togglekblayout is called, then us.. it is ++ * recommended to set the same layout in position 0 of kblayouts and in ++ * xkb_rules */ ++static const char *kblayouts[] = {"us", "gb"}; ++ + /* Trackpad */ + static const int tap_to_click = 1; + static const int tap_and_drag = 1; +@@ -141,6 +146,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY, XKB_KEY_w, togglekblayout, {0} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index ef27a1d..25458e6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -312,6 +312,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 togglekblayout(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); +@@ -368,6 +369,7 @@ static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; + static struct wl_list keyboards; ++static unsigned int kblayout = 0; /* index of kblayouts */ + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ +@@ -2454,6 +2456,24 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglekblayout(const Arg *arg) ++{ ++ Keyboard *kb; ++ struct xkb_rule_names newrule = xkb_rules; ++ ++ kblayout = (kblayout + 1) % LENGTH(kblayouts); ++ newrule.layout = kblayouts[kblayout]; ++ wl_list_for_each(kb, &keyboards, link) { ++ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ struct xkb_keymap *keymap = xkb_map_new_from_names(context, &newrule, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ wlr_keyboard_set_keymap(kb->device->keyboard, keymap); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ } ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.42.0 + + +From 3428168a686e2da8ba8a9dc1473350610afaef19 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 19 Oct 2023 23:46:06 -0500 +Subject: [PATCH 2/2] fix build + +--- + dwl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 25458e6..090280f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2468,7 +2468,7 @@ togglekblayout(const Arg *arg) + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &newrule, + XKB_KEYMAP_COMPILE_NO_FLAGS); +- wlr_keyboard_set_keymap(kb->device->keyboard, keymap); ++ wlr_keyboard_set_keymap(kb->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + } +-- +2.42.0 diff --git a/dwl-bak/dwl.1 b/dwl-bak/dwl.1 new file mode 100644 index 0000000..780c78f --- /dev/null +++ b/dwl-bak/dwl.1 @@ -0,0 +1,158 @@ +.Dd January 8, 2021 +.Dt DWL 1 +.Os +.Sh NAME +.Nm dwl +.Nd dwm for Wayland +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl d +.Op Fl s Ar startup command +.Sh DESCRIPTION +.Nm +is a Wayland compositor based on wlroots. +It is intended to fill the same space in the Wayland world that +.Nm dwm +does for X11. +.Pp +When given the +.Fl v +option, +.Nm +writes its name and version to standard error and exits unsuccessfully. +.Pp +When given the +.Fl d +option, +.Nm +enables full wlroots logging, including debug information. +.Pp +When given the +.Fl s +option, +.Nm +starts a shell process running +.Ar command +when starting. +When stopping, it sends +.Dv SIGTERM +to the child process and waits for it to exit. +.Pp +Users are encouraged to customize +.Nm +by editing the sources, in particular +.Pa config.h . +The default key bindings are as follows: +.Bl -tag -width 20n -offset indent -compact +.It Mod-[1-9] +Show only all windows with a tag. +.It Mod-Ctrl-[1-9] +Show all windows with a tag. +.It Mod-Shift-[1-9] +Move window to a single tag. +.It Mod-Ctrl-Shift-[1-9] +Toggle tag for window. +.It Mod-p +Spawn +.Nm wmenu-run . +.It Mod-Shift-Return +Spawn +.Nm foot . +.It Mod-[jk] +Move focus down/up the stack. +.It Mod-[id] +Increase/decrease number of windows in master area. +.It Mod-[hl] +Decrease/increase master area. +.It Mod-Return +Move window on top of stack or switch top of stack with second window. +.It Mod-Tab +Show only all windows with previous tag. +.It Mod-Shift-c +Close window. +.It Mod-t +Switch to tabbed layout. +.It Mod-f +Switch to floating layout. +.It Mod-m +Switch to monocle layout. +.It Mod-Space +Switch to previous layout. +.It Mod-Shift-Space +Toggle floating state of window. +.It Mod-e +Toggle fullscreen state of window. +.It Mod-0 +Show all windows. +.It Mod-Shift-0 +Set all tags for window. +.It Mod-, +Move focus to previous monitor. +.It Mod-. +Move focus to next monitor. +.It Mod-Shift-, +Move window to previous monitor. +.It Mod-Shift-. +Move window to next monitor. +.It Mod-Shift-q +Quit +.Nm . +.El +These might differ depending on your keyboard layout. +.Sh ENVIRONMENT +These environment variables are used by +.Nm : +.Bl -tag -width XDG_RUNTIME_DIR +.It Ev XDG_RUNTIME_DIR +A directory where temporary user files, such as the Wayland socket, +are stored. +.It Ev XDG_CONFIG_DIR +A directory containing configuration of various programs and +libraries, including libxkbcommon. +.It Ev DISPLAY , WAYLAND_DISPLAY , WAYLAND_SOCKET +Tell how to connect to an underlying X11 or Wayland server. +.It Ev WLR_* +Various variables specific to wlroots. +.It Ev XKB_* , XLOCALEDIR , XCOMPOSEFILE +Various variables specific to libxkbcommon. +.It Ev XCURSOR_PATH +List of directories to search for XCursor themes in. +.It Ev HOME +A directory where there are always dear files there for you. +Waiting for you to clean them up. +.El +.Pp +These are set by +.Nm : +.Bl -tag -width WAYLAND_DISPLAY +.It Ev WAYLAND_DISPLAY +Tell how to connect to +.Nm . +.It Ev DISPLAY +If using +.Nm Xwayland , +tell how to connect to the +.Nm Xwayland +server. +.El +.Sh EXAMPLES +Start +.Nm +with s6 in the background: +.Dl dwl \-s \(aqs6\-svscan <&\-\(aq +.Sh SEE ALSO +.Xr foot 1 , +.Xr wmenu 1 , +.Xr dwm 1 , +.Xr xkeyboard-config 7 +.Sh CAVEATS +The child process's standard input is connected with a pipe to +.Nm . +If the child process neither reads from the pipe nor closes its +standard input, +.Nm +will freeze after a while due to it blocking when writing to the full +pipe buffer. +.Sh BUGS +All of them. diff --git a/dwl-bak/dwl.c b/dwl-bak/dwl.c new file mode 100644 index 0000000..c6c419e --- /dev/null +++ b/dwl-bak/dwl.c @@ -0,0 +1,3556 @@ +/* + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "drwl.h" + +/* macros */ +#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 LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << LENGTH(tags)) - 1) +#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) +#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ +#ifdef XWAYLAND +enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +#endif + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* LayerShell */ + struct wlr_box geom; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void autostartexec(void); +static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +static void cleanupmon(struct wl_listener *listener, void *data); +static void closemon(Monitor *m); +static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +static void commitnotify(struct wl_listener *listener, void *data); +static void commitpopup(struct wl_listener *listener, void *data); +static void createdecoration(struct wl_listener *listener, void *data); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroysessionlock(struct wl_listener *listener, void *data); +static void destroysessionmgr(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); +static Monitor *dirtomon(enum wlr_direction dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +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 handlesig(int signo); +static void incnmaster(const Arg *arg); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void monocle(Monitor *m); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +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 setgamma(struct wl_listener *listener, void *data); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); +static void setup(void); +static void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unlocksession(struct wl_listener *listener, void *data); +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); +static void unmapnotify(struct wl_listener *listener, void *data); +static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; +static struct wl_listener lock_listener = {.notify = locksession}; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(struct wl_listener *listener, void *data); +static void configurex11(struct wl_listener *listener, void *data); +static void createnotifyx11(struct wl_listener *listener, void *data); +static void dissociatex11(struct wl_listener *listener, void *data); +static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wlr_xwayland *xwayland; +static xcb_atom_t netatom[NetLast]; +#endif + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" + +static pid_t *autostart_pids; +static size_t autostart_len; + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i; + const Rule *r; + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + l->geom.x = l->scene->node.x; + l->geom.y = l->scene->node.y; + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exlusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } +} + +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * 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 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, + event->delta_discrete, event->source, event->relative_direction); +} + +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + +void +buttonpress(struct wl_listener *listener, void *data) +{ + unsigned int i = 0, x = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; + uint32_t mods; + Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + return; + } else { + cursor_mode = CurNormal; + } + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ + size_t i; +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + drawbars(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + wlr_fractional_scale_v1_notify_scale(layer_surface->surface, l->mon->wlr_output->scale); + wlr_surface_set_preferred_buffer_scale(layer_surface->surface, (int32_t)ceilf(l->mon->wlr_output->scale)); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title this will likely select + * a wrong monitor. + */ + applyrules(c); + wlr_surface_set_preferred_buffer_scale(client_surface(c), (int)ceilf(c->mon->wlr_output->scale)); + wlr_fractional_scale_v1_notify_scale(client_surface(c), c->mon->wlr_output->scale); + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + return; + } + + if (client_surface(c)->mapped && c->mon) + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) + return; + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->gaps = gaps; + + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + + wl_list_insert(&mons, &m->link); + drawbars(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method (device, scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroysessionmgr(struct wl_listener *listener, void *data) +{ + wl_list_remove(&lock_listener.link); + wl_list_remove(&listener->link); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wlr_keyboard_group_destroy(group->wlr_group); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } + drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +void +focusstack(const Arg *arg) +{ + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } + /* If only one client is visible on selmon, then c == sel */ + focusclient(c, 1); +} + +/* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + LISTEN_STATIC(&drw->events.lost, gpureset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) { + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid +#ifdef XWAYLAND + && (!xwayland || in.si_pid != xwayland->server->pid) +#endif + ) { + pid_t *p, *lim; + waitpid(in.si_pid, NULL, 0); + if (in.si_pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == in.si_pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +} + +void +incnmaster(const Arg *arg) +{ + if (!arg || !selmon) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + */ + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { + k->func(&k->arg); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); + LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); + LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ + Client* focused = focustop(selmon); + if (focused) + wl_list_insert(&focused->link, &c->link); + else + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor than its parent, if not + * try to apply rules for them */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + drawbars(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + +void +monocle(Monitor *m) +{ + Client *c; + int n = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + resize(c, m->w, 0); + n++; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); +} + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->geom.x : w->geom.x); + sy = cursor->y - (l ? l->geom.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } + + /* 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) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change, this to avoid + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + /* https://codeberg.org/dwl/dwl/issues/577 */ + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct wlr_gamma_control_v1 *gamma_control; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite + * wlr_scene_output_commit() in order to add the gamma to the pending + * state before committing, instead try to commit the gamma in one frame, + * and commit the rest of the state in the next one (or in the same frame if + * the gamma can not be committed). + */ + if (m->gamma_lut_changed) { + gamma_control + = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); + m->gamma_lut_changed = 0; + + if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) + goto commit; + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto commit; + } + wlr_output_commit_state(m->wlr_output, &pending); + wlr_output_schedule_frame(m->wlr_output); + } else { +commit: + wlr_scene_output_commit(m->scene_output, NULL); + } + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); + if (startup_cmd) { + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + close(STDIN_FILENO); + setsid(); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + } + + drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully + * initialized, as the image/coordinates are not transformed for the + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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 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); +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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)); +} + +void +setfloating(Client *c, int floating) +{ + Client *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + drawbars(); +} + +void +setgamma(struct wl_listener *listener, void *data) +{ + struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + Monitor *m = event->output->data; + if (!m) + return; + m->gamma_lut_changed = 1; + wlr_output_schedule_frame(m->wlr_output); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +void +setup(void) +{ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: + * wlr_renderer_init_wl_display(drw); + * but we need to create manually the linux_dmabuf interface to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + LISTEN_STATIC(&activation->events.request_activate, urgent); + + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + LISTEN_STATIC(&power_mgr->events.set_mode, powermgrsetmode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + LISTEN_STATIC(&backend->events.new_output, createmon); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); + LISTEN_STATIC(&xdg_shell->events.new_popup, createpopup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + LISTEN_STATIC(&layer_shell->events.new_surface, createlayersurface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + LISTEN_STATIC(&xdg_decoration_mgr->events.new_toplevel_decoration, createdecoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + LISTEN_STATIC(&pointer_constraints->events.new_constraint, createpointerconstraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + LISTEN_STATIC(&cursor->events.motion, motionrelative); + LISTEN_STATIC(&cursor->events.motion_absolute, motionabsolute); + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); + LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + LISTEN_STATIC(&xwayland->events.ready, xwaylandready); + LISTEN_STATIC(&xwayland->events.new_surface, createnotifyx11); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[1024]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + +void +tile(Monitor *m) +{ + unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + if (n == 0) + return; + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; + } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; + } + i++; + } +} + +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == exclusive_focus) + exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +unmapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + drawbars(); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + +void +zoom(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; + + /* Search for the first tiled window that is not sel, marking sel as + * NULL if we pass it along the way */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating) { + if (c != sel) + break; + sel = NULL; + } + } + + /* Return if no other tiled window was found */ + if (&c->link == &clients) + return; + + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) + sel = c; + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + + focusclient(sel, 1); + arrange(selmon); +} + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + /* TODO: figure out if there is another way to do this */ + if (!c->mon) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, + .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); + else + arrange(c->mon); +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +xcb_atom_t +getatom(xcb_connection_t *xc, const char *name) +{ + xcb_atom_t atom = 0; + xcb_intern_atom_reply_t *reply; + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) + atom = reply->atom; + free(reply); + + return atom; +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + drawbars(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); + int err = xcb_connection_has_error(xc); + if (err) { + fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); + return; + } + + /* Collect atoms we are interested in. If getatom returns 0, we will + * not detect that window type. */ + netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); + netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); + netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of dwl. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) + wlr_xwayland_set_cursor(xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + + xcb_disconnect(xc); +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else + goto usage; + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); +} diff --git a/dwl-bak/dwl.c.orig b/dwl-bak/dwl.c.orig new file mode 100644 index 0000000..4b5c419 --- /dev/null +++ b/dwl-bak/dwl.c.orig @@ -0,0 +1,3507 @@ +/* + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "drwl.h" + +/* macros */ +#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 LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << LENGTH(tags)) - 1) +#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) +#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ +#ifdef XWAYLAND +enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +#endif + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* LayerShell */ + struct wlr_box geom; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +static void cleanupmon(struct wl_listener *listener, void *data); +static void closemon(Monitor *m); +static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +static void commitnotify(struct wl_listener *listener, void *data); +static void commitpopup(struct wl_listener *listener, void *data); +static void createdecoration(struct wl_listener *listener, void *data); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroysessionlock(struct wl_listener *listener, void *data); +static void destroysessionmgr(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); +static Monitor *dirtomon(enum wlr_direction dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +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 handlesig(int signo); +static void incnmaster(const Arg *arg); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void monocle(Monitor *m); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +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 setgamma(struct wl_listener *listener, void *data); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); +static void setup(void); +static void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unlocksession(struct wl_listener *listener, void *data); +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); +static void unmapnotify(struct wl_listener *listener, void *data); +static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; +static struct wl_listener lock_listener = {.notify = locksession}; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(struct wl_listener *listener, void *data); +static void configurex11(struct wl_listener *listener, void *data); +static void createnotifyx11(struct wl_listener *listener, void *data); +static void dissociatex11(struct wl_listener *listener, void *data); +static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wlr_xwayland *xwayland; +static xcb_atom_t netatom[NetLast]; +#endif + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i; + const Rule *r; + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + l->geom.x = l->scene->node.x; + l->geom.y = l->scene->node.y; + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exlusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * 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 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, + event->delta_discrete, event->source, event->relative_direction); +} + +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + +void +buttonpress(struct wl_listener *listener, void *data) +{ + unsigned int i = 0, x = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; + uint32_t mods; + Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + return; + } else { + cursor_mode = CurNormal; + } + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + drawbars(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + wlr_fractional_scale_v1_notify_scale(layer_surface->surface, l->mon->wlr_output->scale); + wlr_surface_set_preferred_buffer_scale(layer_surface->surface, (int32_t)ceilf(l->mon->wlr_output->scale)); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title this will likely select + * a wrong monitor. + */ + applyrules(c); + wlr_surface_set_preferred_buffer_scale(client_surface(c), (int)ceilf(c->mon->wlr_output->scale)); + wlr_fractional_scale_v1_notify_scale(client_surface(c), c->mon->wlr_output->scale); + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + return; + } + + if (client_surface(c)->mapped && c->mon) + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) + return; + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->gaps = gaps; + + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + + wl_list_insert(&mons, &m->link); + drawbars(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method (device, scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroysessionmgr(struct wl_listener *listener, void *data) +{ + wl_list_remove(&lock_listener.link); + wl_list_remove(&listener->link); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wlr_keyboard_group_destroy(group->wlr_group); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } + drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +void +focusstack(const Arg *arg) +{ + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } + /* If only one client is visible on selmon, then c == sel */ + focusclient(c, 1); +} + +/* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + LISTEN_STATIC(&drw->events.lost, gpureset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) { +#ifdef XWAYLAND + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid + && (!xwayland || in.si_pid != xwayland->server->pid)) + waitpid(in.si_pid, NULL, 0); +#else + while (waitpid(-1, NULL, WNOHANG) > 0); +#endif + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +} + +void +incnmaster(const Arg *arg) +{ + if (!arg || !selmon) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + */ + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { + k->func(&k->arg); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); + LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); + LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ + Client* focused = focustop(selmon); + if (focused) + wl_list_insert(&focused->link, &c->link); + else + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor than its parent, if not + * try to apply rules for them */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + drawbars(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + +void +monocle(Monitor *m) +{ + Client *c; + int n = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + resize(c, m->w, 0); + n++; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); +} + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->geom.x : w->geom.x); + sy = cursor->y - (l ? l->geom.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } + + /* 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) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change, this to avoid + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + /* https://codeberg.org/dwl/dwl/issues/577 */ + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct wlr_gamma_control_v1 *gamma_control; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite + * wlr_scene_output_commit() in order to add the gamma to the pending + * state before committing, instead try to commit the gamma in one frame, + * and commit the rest of the state in the next one (or in the same frame if + * the gamma can not be committed). + */ + if (m->gamma_lut_changed) { + gamma_control + = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); + m->gamma_lut_changed = 0; + + if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) + goto commit; + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto commit; + } + wlr_output_commit_state(m->wlr_output, &pending); + wlr_output_schedule_frame(m->wlr_output); + } else { +commit: + wlr_scene_output_commit(m->scene_output, NULL); + } + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + close(STDIN_FILENO); + setsid(); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + } + + drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully + * initialized, as the image/coordinates are not transformed for the + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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 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); +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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)); +} + +void +setfloating(Client *c, int floating) +{ + Client *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + drawbars(); +} + +void +setgamma(struct wl_listener *listener, void *data) +{ + struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + Monitor *m = event->output->data; + if (!m) + return; + m->gamma_lut_changed = 1; + wlr_output_schedule_frame(m->wlr_output); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +void +setup(void) +{ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: + * wlr_renderer_init_wl_display(drw); + * but we need to create manually the linux_dmabuf interface to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + LISTEN_STATIC(&activation->events.request_activate, urgent); + + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + LISTEN_STATIC(&power_mgr->events.set_mode, powermgrsetmode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + LISTEN_STATIC(&backend->events.new_output, createmon); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); + LISTEN_STATIC(&xdg_shell->events.new_popup, createpopup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + LISTEN_STATIC(&layer_shell->events.new_surface, createlayersurface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + LISTEN_STATIC(&xdg_decoration_mgr->events.new_toplevel_decoration, createdecoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + LISTEN_STATIC(&pointer_constraints->events.new_constraint, createpointerconstraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + LISTEN_STATIC(&cursor->events.motion, motionrelative); + LISTEN_STATIC(&cursor->events.motion_absolute, motionabsolute); + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); + LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + LISTEN_STATIC(&xwayland->events.ready, xwaylandready); + LISTEN_STATIC(&xwayland->events.new_surface, createnotifyx11); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[1024]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + +void +tile(Monitor *m) +{ + unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + if (n == 0) + return; + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; + } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; + } + i++; + } +} + +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == exclusive_focus) + exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +unmapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + drawbars(); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + +void +zoom(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; + + /* Search for the first tiled window that is not sel, marking sel as + * NULL if we pass it along the way */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating) { + if (c != sel) + break; + sel = NULL; + } + } + + /* Return if no other tiled window was found */ + if (&c->link == &clients) + return; + + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) + sel = c; + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + + focusclient(sel, 1); + arrange(selmon); +} + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + /* TODO: figure out if there is another way to do this */ + if (!c->mon) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, + .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); + else + arrange(c->mon); +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +xcb_atom_t +getatom(xcb_connection_t *xc, const char *name) +{ + xcb_atom_t atom = 0; + xcb_intern_atom_reply_t *reply; + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) + atom = reply->atom; + free(reply); + + return atom; +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + drawbars(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); + int err = xcb_connection_has_error(xc); + if (err) { + fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); + return; + } + + /* Collect atoms we are interested in. If getatom returns 0, we will + * not detect that window type. */ + netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); + netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); + netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of dwl. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) + wlr_xwayland_set_cursor(xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + + xcb_disconnect(xc); +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else + goto usage; + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); +} diff --git a/dwl-bak/dwl.desktop b/dwl-bak/dwl.desktop new file mode 100644 index 0000000..e1380f7 --- /dev/null +++ b/dwl-bak/dwl.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=dwl +Comment=dwm for Wayland +Exec=dwl +Type=Application diff --git a/dwl-bak/protocols/wlr-layer-shell-unstable-v1.xml b/dwl-bak/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/dwl-bak/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/dwl-bak/protocols/wlr-output-power-management-unstable-v1.xml b/dwl-bak/protocols/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 0000000..a977839 --- /dev/null +++ b/dwl-bak/protocols/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create a output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + + diff --git a/dwl-bak/util.c b/dwl-bak/util.c new file mode 100644 index 0000000..51130af --- /dev/null +++ b/dwl-bak/util.c @@ -0,0 +1,51 @@ +/* See LICENSE.dwm file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +int +fd_set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags < 0) { + perror("fcntl(F_GETFL):"); + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl(F_SETFL):"); + return -1; + } + + return 0; +} diff --git a/dwl-bak/util.h b/dwl-bak/util.h new file mode 100644 index 0000000..226980d --- /dev/null +++ b/dwl-bak/util.h @@ -0,0 +1,5 @@ +/* See LICENSE.dwm file for copyright and license details. */ + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); +int fd_set_nonblock(int fd); diff --git a/dwl/.gitea/issue_template/bug_report.yml b/dwl/.gitea/issue_template/bug_report.yml new file mode 100644 index 0000000..77ce108 --- /dev/null +++ b/dwl/.gitea/issue_template/bug_report.yml @@ -0,0 +1,62 @@ +name: Bug Report +about: Something in dwl isn't working correctly +title: +labels: + - 'Kind/Bug' +body: + - type: markdown + attributes: + value: | + - Only report bugs that can be reproduced on the main (or wlroots-next) branch without patches. + - Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use dwl. + - Report patch issues to their respective authors. + + - type: input + id: dwl_version + attributes: + label: 'dwl version:' + placeholder: '`dwl -v`' + validations: + required: true + + - type: input + id: wlroots_version + attributes: + label: 'wlroots version:' + validations: + required: true + + - type: input + id: distro + attributes: + label: What distro (and version) are you using? + validations: + required: false + + - type: textarea + attributes: + label: Description + value: | + The steps you took to reproduce the problem. + validations: + required: false + + - type: textarea + id: debug_log + attributes: + label: Debug Log + value: | + Run `dwl -d 2> ~/dwl.log` from a TTY and attach the **full** (do not truncate it) file here, or upload it to a pastebin. + Please try to keep the reproduction as brief as possible and exit dwl. + validations: + required: false + + - type: textarea + id: backtrace + attributes: + label: Stack Trace + value: | + - Only required if dwl crashes. + - If the lines mentioning dwl or wlroots have `??`. Please compile both dwl and wlroots from source (enabling debug symbols) and try to reproduce. + validations: + required: false diff --git a/dwl/.gitea/issue_template/enhancement-idea.yml b/dwl/.gitea/issue_template/enhancement-idea.yml new file mode 100644 index 0000000..be1bbf2 --- /dev/null +++ b/dwl/.gitea/issue_template/enhancement-idea.yml @@ -0,0 +1,9 @@ +name: Enhancement idea +about: Suggest a feature or improvement +title: +labels: + - 'Kind/Feature' +body: + - type: textarea + attributes: + label: Description diff --git a/dwl/.gitignore b/dwl/.gitignore new file mode 100644 index 0000000..0dde90e --- /dev/null +++ b/dwl/.gitignore @@ -0,0 +1,6 @@ +dwl +*.o +*-protocol.c +*-protocol.h +.ccls-cache +config.h diff --git a/dwl/.mailmap b/dwl/.mailmap new file mode 100644 index 0000000..911248c --- /dev/null +++ b/dwl/.mailmap @@ -0,0 +1 @@ +Lennart Jablonka diff --git a/dwl/CHANGELOG.md b/dwl/CHANGELOG.md new file mode 100644 index 0000000..3a299c4 --- /dev/null +++ b/dwl/CHANGELOG.md @@ -0,0 +1,200 @@ +# Changelog + +* [0.7](#0.7) +* [0.6](#0.6) +* [0.5](#0.5) + + +## 0.7 + +See also [0.6](#0.6) release notes. 0.7 builds against wlroots 0.18.x. + +### Added + +* Add support for the alpha-modifier-v1 protocol ([wlroots!4616][wlroots!4616]). +* dwl now will survive GPU resets ([#601][601]). + +[wlroots!4616]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4616 +[601]: https://codeberg.org/dwl/dwl/issues/601 + + +### Fixed + +* Crash when re-mapping unmapped clients. + + +### Contributors + +Guido Cella +Lennart Jablonka + + +## 0.6 + +### Added + +* Add `rootcolor` to change the default background color ([#544][544]). +* Implement the wlr-virtual-pointer-unstable-v1 protocol ([#574][574]). +* Implement the pointer-constraints and relative-pointer protocols ([#317][317]) +* Implement the wlr-output-power-management protocol ([#599][599]) + +[544]: https://codeberg.org/dwl/dwl/pulls/544 +[574]: https://codeberg.org/dwl/dwl/pulls/574 +[317]: https://codeberg.org/dwl/dwl/issues/317 +[599]: https://codeberg.org/dwl/dwl/issues/559 + + +### Changed + +* Keyboards are now managed through keyboard groups ([#549][549]). +* Only the first matched keybinding is executed. +* Allow toggling the layout before selecting a different one ([#570][570]). +* Fullscreen clients are now rendered above wlr_layer_surfaces in the top layer + ([#609][609]). +* The default menu was changed from `bemenu-run` to `wmenu-run` ([#553][553]). +* The option `sloppyfocus` now replicates the dwm behavior ([#599][599]). +* Allow configure position of monitors with negative values. (-1, -1) is + used to auto-configure them ([#635][635]). +* dwl now kills the entire process group of `startup_cmd` +* The O_NONBLOCK flag is set for stdout. + +[549]: https://codeberg.org/dwl/dwl/pulls/549 +[570]: https://codeberg.org/dwl/dwl/pulls/570 +[609]: https://codeberg.org/dwl/dwl/pulls/609 +[553]: https://codeberg.org/dwl/dwl/issues/553 +[599]: https://codeberg.org/dwl/dwl/pulls/599 +[635]: https://codeberg.org/dwl/dwl/pulls/635 + + +### Removed + +* The SLOC limit is now removed ([#497][497]). + +[497]: https://codeberg.org/dwl/dwl/pulls/497 + + +### Fixed + +* Clients not having the correct border color when mapping. +* Compliance with the xdg-decoration-unstable-v1 ([#546][546]). +* dwl no longer sends negative values in xdg_toplevel.configure events. +* Crashes with disabled monitors ([#472][472]). + +[546]: https://codeberg.org/dwl/dwl/pulls/546 +[472]: https://codeberg.org/dwl/dwl/issues/472 + + +### Contributors + +Ben Jargowsky +Benjamin Chausse +David Donahue +Devin J. Pohly +Dima Krasner +Emil Miler +Forrest Bushstone +Guido Cella +Peter Hofmann +Rutherther +Squibid +choc +fictitiousexistence +korei999 +sewn +thanatos + + +## 0.5 + +### Added + +* Allow configure x and y position of outputs ([#301][301]) +* Implement repeatable keybindings ([#368][368]) +* Print app id in printstatus() output ([#381][381]) +* Display client count in monocle symbol ([#387][387]) +* Export XCURSOR_SIZE to fix apps using an older version of Qt ([#425][425]) +* Support for wp-fractional-scale-v1 (through wlr_scene: [wlroots!3511][wlroots!3511]) +* dwl now sends `wl_surface.preferred_buffer_scale` (through wlr_scene: [wlroots!4269][wlroots!4269]) +* Add support for xdg-shell v6 ([#465][465]) +* Add support for wp-cursor-shape-v1 ([#444][444]) +* Add desktop file ([#484][484]) +* Add macro to easily configure colors ([#466][466]) +* Color of urgent clients are now red ([#494][494]) +* New flag `-d` and option `log_level` to change the wlroots debug level +* Add CHANGELOG.md ([#501][501]) + +[301]: https://github.com/djpohly/dwl/pull/301 +[368]: https://github.com/djpohly/dwl/pull/368 +[381]: https://github.com/djpohly/dwl/pull/381 +[387]: https://github.com/djpohly/dwl/issues/387 +[425]: https://github.com/djpohly/dwl/pull/425 +[wlroots!4269]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4269 +[wlroots!3511]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/3511 +[465]: https://github.com/djpohly/dwl/pull/465 +[444]: https://github.com/djpohly/dwl/pull/444 +[484]: https://github.com/djpohly/dwl/pull/484 +[466]: https://github.com/djpohly/dwl/issues/466 +[494]: https://github.com/djpohly/dwl/pull/494 +[501]: https://github.com/djpohly/dwl/pull/501 + + +### Changed + +* Replace `tags` with `TAGCOUNT` in config.def.h ([#403][403]) +* Pop ups are now destroyed when focusing another client ([#408][408]) +* dwl does not longer respect size hints, instead clip windows if they are + larger than they should be ([#455][455]) +* The version of wlr-layer-shell-unstable-v1 was lowered to 3 (from 4) +* Use the same border color as dwm ([#494][494]) + +[403]: https://github.com/djpohly/dwl/pull/403 +[408]: https://github.com/djpohly/dwl/pull/409 +[455]: https://github.com/djpohly/dwl/pull/455 +[494]: https://github.com/djpohly/dwl/pull/494 + + +### Removed + +* Remove unused `rootcolor` option ([#401][401]) +* Remove support for wlr-input-inhibitor-unstable-v1 ([#430][430]) +* Remove support for KDE idle protocol ([#431][431]) + +[401]: https://github.com/djpohly/dwl/pull/401 +[430]: https://github.com/djpohly/dwl/pull/430 +[431]: https://github.com/djpohly/dwl/pull/431 + + +### Fixed + +* Fix crash when creating a layer surface with all outputs disabled + ([#421][421]) +* Fix other clients being shown as focused if the focused client have pop ups + open ([#408][408]) +* Resize fullscreen clients when updating monitor mode +* dwl no longer crash at exit like sometimes did +* Fullscreen background appearing above clients ([#487][487]) +* Fix a segfault when user provides invalid xkb_rules ([#518][518]) + +[421]: https://github.com/djpohly/dwl/pull/421 +[408]: https://github.com/djpohly/dwl/issues/408 +[487]: https://github.com/djpohly/dwl/issues/487 +[518]: https://github.com/djpohly/dwl/pull/518 + + +### Contributors + +* A Frederick Christensen +* Angelo Antony +* Ben Collerson +* Devin J. Pohly +* Forrest Bushstone +* gan-of-culture +* godalming123 +* Job79 +* link2xt +* Micah Gorrell +* Nikita Ivanov +* Palanix +* pino-desktop +* Weiseguy +* Yves Zoundi diff --git a/dwl/LICENSE b/dwl/LICENSE new file mode 100644 index 0000000..658085a --- /dev/null +++ b/dwl/LICENSE @@ -0,0 +1,692 @@ +dwl - dwm for Wayland + +Copyright © 2020 dwl team + +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/dwl/LICENSE.dwm b/dwl/LICENSE.dwm new file mode 100644 index 0000000..507e4dc --- /dev/null +++ b/dwl/LICENSE.dwm @@ -0,0 +1,39 @@ +Portions of dwl based on dwm code are used under the following license: + +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk
+© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dwl/LICENSE.sway b/dwl/LICENSE.sway new file mode 100644 index 0000000..3e0cacc --- /dev/null +++ b/dwl/LICENSE.sway @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dwl/LICENSE.tinywl b/dwl/LICENSE.tinywl new file mode 100644 index 0000000..7023690 --- /dev/null +++ b/dwl/LICENSE.tinywl @@ -0,0 +1,127 @@ +dwl is originally based on TinyWL, which is used under the following license: + +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/dwl/Makefile b/dwl/Makefile new file mode 100644 index 0000000..9bc67db --- /dev/null +++ b/dwl/Makefile @@ -0,0 +1,78 @@ +.POSIX: +.SUFFIXES: + +include config.mk + +# flags for compiling +DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ + -DVERSION=\"$(VERSION)\" $(XWAYLAND) +DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ + -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ + -Wfloat-conversion + +# CFLAGS / LDFLAGS +PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) +DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +all: dwl +dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h +util.o: util.c util.h + +# wayland-scanner is a tool which generates C headers and rigging for Wayland +# protocols, which are specified in XML. wlroots requires you to rig these up +# to your build system yourself and provide them in the include path. +WAYLAND_SCANNER = `$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner` +WAYLAND_PROTOCOLS = `$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols` + +cursor-shape-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/staging/cursor-shape/cursor-shape-v1.xml $@ +pointer-constraints-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + $(WAYLAND_PROTOCOLS)/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml $@ +wlr-layer-shell-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) enum-header \ + protocols/wlr-layer-shell-unstable-v1.xml $@ +wlr-output-power-management-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/wlr-output-power-management-unstable-v1.xml $@ +xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ + +config.h: + cp config.def.h $@ +clean: + rm -f dwl *.o *-protocol.h + +dist: clean + mkdir -p dwl-$(VERSION) + cp -R LICENSE* Makefile CHANGELOG.md README.md client.h config.def.h \ + config.mk protocols dwl.1 dwl.c util.c util.h dwl.desktop \ + dwl-$(VERSION) + tar -caf dwl-$(VERSION).tar.gz dwl-$(VERSION) + rm -rf dwl-$(VERSION) + +install: dwl + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 + mkdir -p $(DESTDIR)$(DATADIR)/wayland-sessions + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + +.SUFFIXES: .c .o +.c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< diff --git a/dwl/README.md b/dwl/README.md new file mode 100644 index 0000000..aa95bab --- /dev/null +++ b/dwl/README.md @@ -0,0 +1,201 @@ +# dwl - dwm for Wayland + +Join us on our IRC channel: [#dwl on Libera Chat] +Or on our [Discord server]. + +dwl is a compact, hackable compositor for [Wayland] based on [wlroots]. It is +intended to fill the same space in the Wayland world that dwm does in X11, +primarily in terms of functionality, and secondarily in terms of +philosophy. Like dwm, dwl is: + +- Easy to understand, hack on, and extend with patches +- One C source file (or a very small number) configurable via `config.h` +- Tied to as few external dependencies as possible + +## Getting Started: + +### **dwl branch 0.7 and releases based upon 0.7 build against [wlroots] 0.18** + +### Latest semi-stable [release] +This is probably where you want to start. This builds against the dependent +packages' versions currently shipping in major distributions. If your +distribution's wlroots version is older, use an earlier dwl [release] or [0.x +branch]. + +### Development branch [main] +Active development progresses on the `main` branch. The `main` branch is built +against a late (and often changing) git commit of wlroots. While the adventurous +are welcome to use `main`, it is a rocky road. Using `main` requires that the +user be willing to chase git commits of wlroots. Testing development pull +requests may involve merging unmerged pull requests in [wlroots]' git repository +and/or git commits of wayland. + +### Building dwl +dwl has the following dependencies: +- libinput +- wayland +- wlroots (compiled with the libinput backend) +- xkbcommon +- wayland-protocols (compile-time only) +- pkg-config (compile-time only) + +dwl has the following additional dependencies if XWayland support is enabled: +- libxcb +- libxcb-wm +- wlroots (compiled with X11 support) +- Xwayland (runtime only) + +Install these (and their `-devel` versions if your distro has separate +development packages) and run `make`. If you wish to build against a released +version of wlroots (*you probably do*), use a [release] or a [0.x branch]. If +you want to use the unstable development `main` branch, you need to use the git +version of [wlroots]. + +To enable XWayland, you should uncomment its flags in `config.mk`. + +## Configuration + +All configuration is done by editing `config.h` and recompiling, in the same +manner as dwm. There is no way to separately restart the window manager in +Wayland without restarting the entire display server, so any changes will take +effect the next time dwl is executed. + +As in the dwm community, we encourage users to share patches they have +created. Check out the [dwl-patches] repository! + +## Running dwl + +dwl can be run on any of the backends supported by wlroots. This means you can +run it as a separate window inside either an X11 or Wayland session, as well as +directly from a VT console. Depending on your distro's setup, you may need to +add your user to the `video` and `input` groups before you can run dwl on a +VT. If you are using `elogind` or `systemd-logind` you need to install polkit; +otherwise you need to add yourself in the `seat` group and enable/start the +seatd daemon. + +When dwl is run with no arguments, it will launch the server and begin handling +any shortcuts configured in `config.h`. There is no status bar or other +decoration initially; these are instead clients that can be run within the +Wayland session. Do note that the default background color is black. This can be +modified in `config.h`. + +If you would like to run a script or command automatically at startup, you can +specify the command using the `-s` option. This command will be executed as a +shell command using `/bin/sh -c`. It serves a similar function to `.xinitrc`, +but differs in that the display server will not shut down when this process +terminates. Instead, dwl will send this process a SIGTERM at shutdown and wait +for it to terminate (if it hasn't already). This makes it ideal for execing into +a user service manager like [s6], [anopa], [runit], [dinit], or [`systemd +--user`]. + +Note: The `-s` command is run as a *child process* of dwl, which means that it +does not have the ability to affect the environment of dwl or of any processes +that it spawns. If you need to set environment variables that affect the entire +dwl session, these must be set prior to running dwl. For example, Wayland +requires a valid `XDG_RUNTIME_DIR`, which is usually set up by a session manager +such as `elogind` or `systemd-logind`. If your system doesn't do this +automatically, you will need to configure it prior to launching `dwl`, e.g.: + + export XDG_RUNTIME_DIR=/tmp/xdg-runtime-$(id -u) + mkdir -p $XDG_RUNTIME_DIR + dwl + +### Status information + +Information about selected layouts, current window title, app-id, and +selected/occupied/urgent tags is written to the stdin of the `-s` command (see +the `printstatus()` function for details). This information can be used to +populate an external status bar with a script that parses the +information. Failing to read this information will cause dwl to block, so if you +do want to run a startup command that does not consume the status information, +you can close standard input with the `<&-` shell redirection, for example: + + dwl -s 'foot --server <&-' + +If your startup command is a shell script, you can achieve the same inside the +script with the line + + exec <&- + +To get a list of status bars that work with dwl consult our [wiki]. + +## Replacements for X applications + +You can find a [list of useful resources on our wiki]. + +## Background + +dwl is not meant to provide every feature under the sun. Instead, like dwm, it +sticks to features which are necessary, simple, and straightforward to implement +given the base on which it is built. Implemented default features are: + +- Any features provided by dwm/Xlib: simple window borders, tags, keybindings, + client rules, mouse move/resize. Providing a built-in status bar is an + exception to this goal, to avoid dependencies on font rendering and/or drawing + libraries when an external bar could work well. +- Configurable multi-monitor layout support, including position and rotation +- Configurable HiDPI/multi-DPI support +- Idle-inhibit protocol which lets applications such as mpv disable idle + monitoring +- Provide information to external status bars via stdout/stdin +- Urgency hints via xdg-activate protocol +- Support screen lockers via ext-session-lock-v1 protocol +- Various Wayland protocols +- XWayland support as provided by wlroots (can be enabled in `config.mk`) +- Zero flickering - Wayland users naturally expect that "every frame is perfect" +- Layer shell popups (used by Waybar) +- Damage tracking provided by scenegraph API + +Given the Wayland architecture, dwl has to implement features from dwm **and** +the xorg-server. Because of this, it is impossible to maintain the original +project goal of 2000 SLOC and have a reasonably complete compositor with +features comparable to dwm. However, this does not mean that the code will grow +indiscriminately. We will try to keep the code as small as possible. + +Features under consideration (possibly as patches) are: + +- Protocols made trivial by wlroots +- Implement the text-input and input-method protocols to support IME once ibus + implements input-method v2 (see https://github.com/ibus/ibus/pull/2256 and + https://codeberg.org/dwl/dwl/pulls/235) + +Feature *non-goals* for the main codebase include: + +- Client-side decoration (any more than is necessary to tell the clients not to) +- Client-initiated window management, such as move, resize, and close, which can + be done through the compositor +- Animations and visual effects + +## Acknowledgements + +dwl began by extending the TinyWL example provided (CC0) by the sway/wlroots +developers. This was made possible in many cases by looking at how sway +accomplished something, then trying to do the same in as suckless a way as +possible. + +Many thanks to suckless.org and the dwm developers and community for the +inspiration, and to the various contributors to the project, including: + +- **Devin J. Pohly for creating and nurturing the fledgling project** +- Alexander Courtis for the XWayland implementation +- Guido Cella for the layer-shell protocol implementation, patch maintenance, + and for helping to keep the project running +- Stivvo for output management and fullscreen support, and patch maintenance + + +[`systemd --user`]: https://wiki.archlinux.org/title/Systemd/User +[#dwl on Libera Chat]: https://web.libera.chat/?channels=#dwl +[0.7-rc1]: https://codeberg.org/dwl/dwl/releases/tag/v0.7-rc1 +[0.x branch]: https://codeberg.org/dwl/dwl/branches +[anopa]: https://jjacky.com/anopa/ +[dinit]: https://davmac.org/projects/dinit/ +[dwl-patches]: https://codeberg.org/dwl/dwl-patches +[list of useful resources on our wiki]: https://codeberg.org/dwl/dwl/wiki/Home#migrating-from-x +[main]: https://codeberg.org/dwl/dwl/src/branch/main +[release]: https://codeberg.org/dwl/dwl/releases +[runit]: http://smarden.org/runit/faq.html#userservices +[s6]: https://skarnet.org/software/s6/ +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots/ +[wiki]: https://codeberg.org/dwl/dwl/wiki/Home#compatible-status-bars +[Discord server]: https://discord.gg/jJxZnrGPWN +[Wayland]: https://wayland.freedesktop.org/ diff --git a/dwl/client.h b/dwl/client.h new file mode 100644 index 0000000..42f225f --- /dev/null +++ b/dwl/client.h @@ -0,0 +1,408 @@ +/* + * Attempt to consolidate unavoidable suck into one file, away from dwl.c. This + * file is not meant to be pretty. We use a .h file with static inline + * functions instead of a separate .c module, or function pointers like sway, so + * that they will simply compile out if the chosen #defines leave them unused. + */ + +/* Leave these functions first; they're used in the others */ +static inline int +client_is_x11(Client *c) +{ +#ifdef XWAYLAND + return c->type == X11; +#endif + return 0; +} + +static inline struct wlr_surface * +client_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->surface; +#endif + return c->surface.xdg->surface; +} + +static inline int +toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) +{ + struct wlr_xdg_surface *xdg_surface, *tmp_xdg_surface; + struct wlr_surface *root_surface; + struct wlr_layer_surface_v1 *layer_surface; + Client *c = NULL; + LayerSurface *l = NULL; + int type = -1; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; +#endif + + if (!s) + return -1; + root_surface = wlr_surface_get_root_surface(s); + +#ifdef XWAYLAND + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(root_surface))) { + c = xsurface->data; + type = c->type; + goto end; + } +#endif + + if ((layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(root_surface))) { + l = layer_surface->data; + type = LayerShell; + goto end; + } + + xdg_surface = wlr_xdg_surface_try_from_wlr_surface(root_surface); + while (xdg_surface) { + tmp_xdg_surface = NULL; + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_POPUP: + if (!xdg_surface->popup || !xdg_surface->popup->parent) + return -1; + + tmp_xdg_surface = wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent); + + if (!tmp_xdg_surface) + return toplevel_from_wlr_surface(xdg_surface->popup->parent, pc, pl); + + xdg_surface = tmp_xdg_surface; + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + c = xdg_surface->data; + type = c->type; + goto end; + case WLR_XDG_SURFACE_ROLE_NONE: + return -1; + } + } + +end: + if (pl) + *pl = l; + if (pc) + *pc = c; + return type; +} + +/* The others */ +static inline void +client_activate_surface(struct wlr_surface *s, int activated) +{ + struct wlr_xdg_toplevel *toplevel; +#ifdef XWAYLAND + struct wlr_xwayland_surface *xsurface; + if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { + wlr_xwayland_surface_activate(xsurface, activated); + return; + } +#endif + if ((toplevel = wlr_xdg_toplevel_try_from_wlr_surface(s))) + wlr_xdg_toplevel_set_activated(toplevel, activated); +} + +static inline uint32_t +client_set_bounds(Client *c, int32_t width, int32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) >= + XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION && width >= 0 && height >= 0 + && (c->bounds.width != width || c->bounds.height != height)) { + c->bounds.width = width; + c->bounds.height = height; + return wlr_xdg_toplevel_set_bounds(c->surface.xdg->toplevel, width, height); + } + return 0; +} + +static inline const char * +client_get_appid(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->class; +#endif + return c->surface.xdg->toplevel->app_id; +} + +static inline void +client_get_clip(Client *c, struct wlr_box *clip) +{ + struct wlr_box xdg_geom = {0}; + *clip = (struct wlr_box){ + .x = 0, + .y = 0, + .width = c->geom.width - c->bw, + .height = c->geom.height - c->bw, + }; + +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_surface_get_geometry(c->surface.xdg, &xdg_geom); + clip->x = xdg_geom.x; + clip->y = xdg_geom.y; +} + +static inline void +client_get_geometry(Client *c, struct wlr_box *geom) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + geom->x = c->surface.xwayland->x; + geom->y = c->surface.xwayland->y; + geom->width = c->surface.xwayland->width; + geom->height = c->surface.xwayland->height; + return; + } +#endif + wlr_xdg_surface_get_geometry(c->surface.xdg, geom); +} + +static inline Client * +client_get_parent(Client *c) +{ + Client *p = NULL; +#ifdef XWAYLAND + if (client_is_x11(c)) { + if (c->surface.xwayland->parent) + toplevel_from_wlr_surface(c->surface.xwayland->parent->surface, &p, NULL); + return p; + } +#endif + if (c->surface.xdg->toplevel->parent) + toplevel_from_wlr_surface(c->surface.xdg->toplevel->parent->base->surface, &p, NULL); + return p; +} + +static inline int +client_has_children(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return !wl_list_empty(&c->surface.xwayland->children); +#endif + /* surface.xdg->link is never empty because it always contains at least the + * surface itself. */ + return wl_list_length(&c->surface.xdg->link) > 1; +} + +static inline const char * +client_get_title(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->title; +#endif + return c->surface.xdg->toplevel->title; +} + +static inline int +client_is_float_type(Client *c) +{ + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_toplevel_state state; + +#ifdef XWAYLAND + if (client_is_x11(c)) { + struct wlr_xwayland_surface *surface = c->surface.xwayland; + xcb_size_hints_t *size_hints = surface->size_hints; + size_t i; + if (surface->modal) + return 1; + + for (i = 0; i < surface->window_type_len; i++) + if (surface->window_type[i] == netatom[NetWMWindowTypeDialog] + || surface->window_type[i] == netatom[NetWMWindowTypeSplash] + || surface->window_type[i] == netatom[NetWMWindowTypeToolbar] + || surface->window_type[i] == netatom[NetWMWindowTypeUtility]) + return 1; + + return size_hints && size_hints->min_width > 0 && size_hints->min_height > 0 + && (size_hints->max_width == size_hints->min_width + || size_hints->max_height == size_hints->min_height); + } +#endif + + toplevel = c->surface.xdg->toplevel; + state = toplevel->current; + return toplevel->parent || (state.min_width != 0 && state.min_height != 0 + && (state.min_width == state.max_width + || state.min_height == state.max_height)); +} + +static inline int +client_is_rendered_on_mon(Client *c, Monitor *m) +{ + /* This is needed for when you don't want to check formal assignment, + * but rather actual displaying of the pixels. + * Usually VISIBLEON suffices and is also faster. */ + struct wlr_surface_output *s; + int unused_lx, unused_ly; + if (!wlr_scene_node_coords(&c->scene->node, &unused_lx, &unused_ly)) + return 0; + wl_list_for_each(s, &client_surface(c)->current_outputs, link) + if (s->output == m->wlr_output) + return 1; + return 0; +} + +static inline int +client_is_stopped(Client *c) +{ + int pid; + siginfo_t in = {0}; +#ifdef XWAYLAND + if (client_is_x11(c)) + return 0; +#endif + + wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); + if (waitid(P_PID, pid, &in, WNOHANG|WCONTINUED|WSTOPPED|WNOWAIT) < 0) { + /* This process is not our child process, while is very unluckely that + * it is stopped, in order to do not skip frames assume that it is. */ + if (errno == ECHILD) + return 1; + } else if (in.si_pid) { + if (in.si_code == CLD_STOPPED || in.si_code == CLD_TRAPPED) + return 1; + if (in.si_code == CLD_CONTINUED) + return 0; + } + + return 0; +} + +static inline int +client_is_unmanaged(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->override_redirect; +#endif + return 0; +} + +static inline void +client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) +{ + if (kb) + wlr_seat_keyboard_notify_enter(seat, s, kb->keycodes, + kb->num_keycodes, &kb->modifiers); + else + wlr_seat_keyboard_notify_enter(seat, s, NULL, 0, NULL); +} + +static inline void +client_restack_surface(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + wlr_xwayland_surface_restack(c->surface.xwayland, NULL, + XCB_STACK_MODE_ABOVE); +#endif + return; +} + +static inline void +client_send_close(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_close(c->surface.xwayland); + return; + } +#endif + wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel); +} + +static inline void +client_set_border_color(Client *c, const float color[static 4]) +{ + int i; + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); +} + +static inline void +client_set_fullscreen(Client *c, int fullscreen) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_set_fullscreen(c->surface.xwayland, fullscreen); + return; + } +#endif + wlr_xdg_toplevel_set_fullscreen(c->surface.xdg->toplevel, fullscreen); +} + +static inline uint32_t +client_set_size(Client *c, uint32_t width, uint32_t height) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) { + wlr_xwayland_surface_configure(c->surface.xwayland, + c->geom.x + c->bw, c->geom.y + c->bw, width, height); + return 0; + } +#endif + if ((int32_t)width == c->surface.xdg->toplevel->current.width + && (int32_t)height == c->surface.xdg->toplevel->current.height) + return 0; + return wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, (int32_t)width, (int32_t)height); +} + +static inline void +client_set_tiled(Client *c, uint32_t edges) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + if (wl_resource_get_version(c->surface.xdg->toplevel->resource) + >= XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION) { + wlr_xdg_toplevel_set_tiled(c->surface.xdg->toplevel, edges); + } else { + wlr_xdg_toplevel_set_maximized(c->surface.xdg->toplevel, edges != WLR_EDGE_NONE); + } +} + +static inline void +client_set_suspended(Client *c, int suspended) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return; +#endif + + wlr_xdg_toplevel_set_suspended(c->surface.xdg->toplevel, suspended); +} + +static inline int +client_wants_focus(Client *c) +{ +#ifdef XWAYLAND + return client_is_unmanaged(c) + && wlr_xwayland_or_surface_wants_focus(c->surface.xwayland) + && wlr_xwayland_icccm_input_model(c->surface.xwayland) != WLR_ICCCM_INPUT_MODEL_NONE; +#endif + return 0; +} + +static inline int +client_wants_fullscreen(Client *c) +{ +#ifdef XWAYLAND + if (client_is_x11(c)) + return c->surface.xwayland->fullscreen; +#endif + return c->surface.xdg->toplevel->requested.fullscreen; +} diff --git a/dwl/config.def.h b/dwl/config.def.h new file mode 100644 index 0000000..87594bc --- /dev/null +++ b/dwl/config.def.h @@ -0,0 +1,210 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +static uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; + +/* tagging - TAGCOUNT must be no greater than 31 */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +/* logging */ +static int log_level = WLR_ERROR; + +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +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; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 40, 0, 0 }}}, + { MODKEY, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, -40, 0, 0 }}}, + { MODKEY, XKB_KEY_Right, moveresizekb, {.v = (int []){ 40, 0, 0, 0 }}}, + { MODKEY, XKB_KEY_Left, moveresizekb, {.v = (int []){ -40, 0, 0, 0 }}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 0, 0, 40 }}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, 0, 0, -40 }}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, moveresizekb, {.v = (int []){ 0, 0, 40, 0 }}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, moveresizekb, {.v = (int []){ 0, 0, -40, 0 }}}, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, +}; diff --git a/dwl/config.def.h.orig b/dwl/config.def.h.orig new file mode 100644 index 0000000..0e40959 --- /dev/null +++ b/dwl/config.def.h.orig @@ -0,0 +1,202 @@ +/* Taken from https://github.com/djpohly/dwl/issues/466 */ +#define COLOR(hex) { ((hex >> 24) & 0xFF) / 255.0f, \ + ((hex >> 16) & 0xFF) / 255.0f, \ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } +/* appearance */ +static const int sloppyfocus = 1; /* focus follows mouse */ +static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = {"monospace:size=10"}; +static const float rootcolor[] = COLOR(0x000000ff); +/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +static uint32_t colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, +}; + +/* tagging - TAGCOUNT must be no greater than 31 */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +/* logging */ +static int log_level = WLR_ERROR; + +/* Autostart */ +static const char *const autostart[] = { + "wbg", "/path/to/your/image", NULL, + NULL /* terminate */ +}; + + +/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ +static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ +}; + +/* layout(s) */ +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* monitors */ +/* (x=-1, y=-1) is reserved as an "autoconfigure" monitor position indicator + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 +*/ +/* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ +static const MonitorRule monrules[] = { + /* name mfact nmaster scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ + { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, +}; + +/* keyboard */ +static const struct xkb_rule_names xkb_rules = { + /* can specify fields: rules, model, layout, variant, options */ + /* example: + .options = "ctrl:nocaps", + */ + .options = NULL, +}; + +static const int repeat_rate = 25; +static const int repeat_delay = 600; + +/* Trackpad */ +static const int tap_to_click = 1; +static const int tap_and_drag = 1; +static const int drag_lock = 1; +static const int natural_scrolling = 0; +static const int disable_while_typing = 1; +static const int left_handed = 0; +static const int middle_button_emulation = 0; +/* You can choose between: +LIBINPUT_CONFIG_SCROLL_NO_SCROLL +LIBINPUT_CONFIG_SCROLL_2FG +LIBINPUT_CONFIG_SCROLL_EDGE +LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN +*/ +static const enum libinput_config_scroll_method scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + +/* You can choose between: +LIBINPUT_CONFIG_CLICK_METHOD_NONE +LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS +LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER +*/ +static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + +/* You can choose between: +LIBINPUT_CONFIG_SEND_EVENTS_ENABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED +LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +*/ +static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +/* You can choose between: +LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT +LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +*/ +static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +static const double accel_speed = 0.0; + +/* You can choose between: +LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +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; + +/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ +#define MODKEY WLR_MODIFIER_ALT + +#define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *termcmd[] = { "foot", NULL }; +static const char *menucmd[] = { "wmenu-run", NULL }; + +static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), + TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), + TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), + TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; + +static const Button buttons[] = { + { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, + { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, + { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, + { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, + { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + { ClkTagBar, 0, BTN_LEFT, view, {0} }, + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, +}; diff --git a/dwl/config.mk b/dwl/config.mk new file mode 100644 index 0000000..fb99950 --- /dev/null +++ b/dwl/config.mk @@ -0,0 +1,17 @@ +_VERSION = 0.7 +VERSION = `git describe --tags --dirty 2>/dev/null || echo $(_VERSION)` + +PKG_CONFIG = pkg-config + +# paths +PREFIX = /usr/local +MANDIR = $(PREFIX)/share/man +DATADIR = $(PREFIX)/share + +XWAYLAND = +XLIBS = +# Uncomment to build XWayland support +XWAYLAND = -DXWAYLAND +XLIBS = xcb xcb-icccm + +CC = gcc diff --git a/dwl/drwl.h b/dwl/drwl.h new file mode 100644 index 0000000..b06a736 --- /dev/null +++ b/dwl/drwl.h @@ -0,0 +1,311 @@ +/* + * drwl - https://codeberg.org/sewn/drwl + * + * Copyright (c) 2023-2024 sewn + * Copyright (c) 2024 notchoc + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The UTF-8 Decoder included is from Bjoern Hoehrmann: + * Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + */ +#pragma once + +#include +#include +#include + +enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ + +typedef struct fcft_font Fnt; +typedef pixman_image_t Img; + +typedef struct { + Img *image; + Fnt *font; + uint32_t *scheme; +} Drwl; + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 +#define UTF8_INVALID 0xFFFD + +static const uint8_t utf8d[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) +{ + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +static int +drwl_init(void) +{ + fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); + return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); +} + +static Drwl * +drwl_create(void) +{ + Drwl *drwl; + + if (!(drwl = calloc(1, sizeof(Drwl)))) + return NULL; + + return drwl; +} + +static void +drwl_setfont(Drwl *drwl, Fnt *font) +{ + if (drwl) + drwl->font = font; +} + +static void +drwl_setimage(Drwl *drwl, Img *image) +{ + if (drwl) + drwl->image = image; +} + +static Fnt * +drwl_font_create(Drwl *drwl, size_t count, + const char *names[static count], const char *attributes) +{ + Fnt *font = fcft_from_name(count, names, attributes); + if (drwl) + drwl_setfont(drwl, font); + return font; +} + +static void +drwl_font_destroy(Fnt *font) +{ + fcft_destroy(font); +} + +static inline pixman_color_t +convert_color(uint32_t clr) +{ + return (pixman_color_t){ + ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, + (clr & 0xFF) * 0x101 + }; +} + +static void +drwl_setscheme(Drwl *drwl, uint32_t *scm) +{ + if (drwl) + drwl->scheme = scm; +} + +static Img * +drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) +{ + Img *image; + pixman_region32_t clip; + + image = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, w, h, bits, w * 4); + if (!image) + return NULL; + pixman_region32_init_rect(&clip, 0, 0, w, h); + pixman_image_set_clip_region32(image, &clip); + pixman_region32_fini(&clip); + + if (drwl) + drwl_setimage(drwl, image); + return image; +} + +static void +drwl_rect(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + int filled, int invert) +{ + pixman_color_t clr; + if (!drwl || !drwl->scheme || !drwl->image) + return; + + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + if (filled) + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, + &(pixman_rectangle16_t){x, y, w, h}); + else + pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, + (pixman_rectangle16_t[4]){ + { x, y, w, 1 }, + { x, y + h - 1, w, 1 }, + { x, y, 1, h }, + { x + w - 1, y, 1, h }}); +} + +static int +drwl_text(Drwl *drwl, + int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert) +{ + int ty; + int render = x || y || w || h; + long x_kern; + uint32_t cp = 0, last_cp = 0, state; + pixman_color_t clr; + pixman_image_t *fg_pix = NULL; + int noellipsis = 0; + const struct fcft_glyph *glyph, *eg = NULL; + int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; + + if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); + fg_pix = pixman_image_create_solid_fill(&clr); + + drwl_rect(drwl, x, y, w, h, 1, !invert); + + x += lpad; + w -= lpad; + } + + if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) + fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; + + if (render) + eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); + + for (const char *p = text, *pp; pp = p, *p; p++) { + for (state = UTF8_ACCEPT; *p && + utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) + ; + if (!*p || state == UTF8_REJECT) { + cp = UTF8_INVALID; + if (p > pp) + p--; + } + + glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); + if (!glyph) + continue; + + x_kern = 0; + if (last_cp) + fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); + last_cp = cp; + + ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; + + if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && + *(p + 1) != '\0') { + /* cannot fit ellipsis after current codepoint */ + if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { + noellipsis = 1; + } else { + w -= eg->advance.x; + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, + x + eg->x, ty - eg->y, eg->width, eg->height); + } + } + + if ((x_kern + glyph->advance.x) > w) + break; + + x += x_kern; + + if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) + /* pre-rendered glyphs (eg. emoji) */ + pixman_image_composite32( + PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + else if (render) + pixman_image_composite32( + PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, + x + glyph->x, ty - glyph->y, glyph->width, glyph->height); + + x += glyph->advance.x; + w -= glyph->advance.x; + } + + if (render) + pixman_image_unref(fg_pix); + + return x + (render ? w : 0); +} + +static unsigned int +drwl_font_getwidth(Drwl *drwl, const char *text) +{ + if (!drwl || !drwl->font || !text) + return 0; + return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); +} + +static void +drwl_image_destroy(Img *image) +{ + pixman_image_unref(image); +} + +static void +drwl_destroy(Drwl *drwl) +{ + if (drwl->font) + drwl_font_destroy(drwl->font); + if (drwl->image) + drwl_image_destroy(drwl->image); + free(drwl); +} + +static void +drwl_fini(void) +{ + fcft_fini(); +} diff --git a/dwl/dwl-patches/LICENSE b/dwl/dwl-patches/LICENSE new file mode 100644 index 0000000..658085a --- /dev/null +++ b/dwl/dwl-patches/LICENSE @@ -0,0 +1,692 @@ +dwl - dwm for Wayland + +Copyright © 2020 dwl team + +See also the files LICENSE.tinywl, LICENSE.dwm and LICENSE.sway. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +---- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/dwl/dwl-patches/LICENSE.dwm b/dwl/dwl-patches/LICENSE.dwm new file mode 100644 index 0000000..507e4dc --- /dev/null +++ b/dwl/dwl-patches/LICENSE.dwm @@ -0,0 +1,39 @@ +Portions of dwl based on dwm code are used under the following license: + +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dwl/dwl-patches/LICENSE.sway b/dwl/dwl-patches/LICENSE.sway new file mode 100644 index 0000000..3e0cacc --- /dev/null +++ b/dwl/dwl-patches/LICENSE.sway @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Drew DeVault + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dwl/dwl-patches/LICENSE.tinywl b/dwl/dwl-patches/LICENSE.tinywl new file mode 100644 index 0000000..7023690 --- /dev/null +++ b/dwl/dwl-patches/LICENSE.tinywl @@ -0,0 +1,127 @@ +dwl is originally based on TinyWL, which is used under the following license: + +This work is licensed under CC0, which effectively puts it in the public domain. + +--- + +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/dwl/dwl-patches/README.md b/dwl/dwl-patches/README.md new file mode 100644 index 0000000..bdfa62a --- /dev/null +++ b/dwl/dwl-patches/README.md @@ -0,0 +1,87 @@ +# dwl-patches +* A general [dwl wiki](https://codeberg.org/dwl/dwl/wiki) is available at the main [dwl] repository. +* This repository is exclusively for dwl PATCHES. + +*Note: All patches are user-submitted content. The authors of dwl do not continually monitor them. You are responsible for downloading and reviewing a patch before using it!* + +## Reporting Issues +- Issues with existing patches can be generated here in the dwl-patches [issues]. Please be sure to "@" reference the patch author in your issue. + +## Contributing Patches to `dwl-patches` +Since dwl follows [suckless](https://suckless.org/) philosophy, it doesn't provide every feature under the sun. To broaden dwl's functionality, one needs to get familiar with the concept of patching. To get your feet wet, consult [the hacking page](https://suckless.org/hacking/) of the suckless website. + +Patches should normally target the latest dwl [release]. +If you target an older release, specify that in the `Download` link on your `README.md` page. +If you target the unstable `main` branch, specify that in the `Download` link on your `README.md` page. + +0. Starting from a local clone of [dwl] (not dwl-patches) +1. If you do not have it already, add the remote for the main dwl repository in your local copy and fetch it: + `git remote add --fetch upstream https://codeberg.org/dwl/dwl` +2. Use git to create a branch for your new patch and hack away creating your patched version of [dwl]. +3. In your local clone of dwl, create a .patch file + `git format-patch upstream/main... --stdout > PATCHNAME.patch` +4. Now fork [dwl-patches] (not dwl) in Codeberg and clone it locally +5. Configure your `dwl-patches` local clone + `git config --local pull.rebase true` +6. In your local `dwl-patches` clone, add a directory called `patches/PATCHNAME`. Place the `PATCHNAME.patch` you created in step (2) into the `patches/PATCHNAME` directory. +7. Add a `README.md` page to the `PATCHNAME` directory using this template (add/remove sections as you like): + ```markdown + ### Description + Insert a short summary of changes that your patch implements. + + ### Download + - [git branch](/USERNAME/dwl/src/branch/PATCHNAME) + ^^^^^^^^^^ OPTIONAL: Patchers are no longer obligated to maintain public `dwl` branches devoted to their patches + - [0.7](/dwl/dwl-patches/raw/branch/main/patches/PATCHNAME/PATCHNAME.patch) + Use the ^RAW^ patch link here + ^^^ "0.7" is an example. Use the release that your patch targets + - [main YYYY-MM-DD](/dwl/dwl-patches/raw/branch/main/patches/PATCHNAME/PATCHNAME.patch) + ^^^^^^^^^^ Patches targeting the unstable "main" branch include a YYYY-MM-DD indicator + ### Authors - latest at top [Codeberg nick is mandatory; other contact methods optional] + - [YOUR_NICK](https://codeberg.org/USERNAME) + your_email@if_you_wish_to.share.it + your_irc_nick at [Libera IRC dwl channel](https://web.libera.chat/?channels=#dwl) + your_discord_handle at [dwl Discord](https://discord.gg/jJxZnrGPWN) + ``` + You may choose to include screenshots (hosted in your patch's subdirectory) in your `README.md`. The process is described [here](https://docs.codeberg.org/markdown/using-images/). + +8. Use the Codeberg web interface to send a pull request to [dwl-patches] (NOT to [dwl]) +9. WHEN YOUR PULL REQUEST IS APPROVED, your Codeberg account will also be granted commit access to [dwl-patches]. Once you have write access, you can make direct modifications/upates to your patches and you are free to create new patches rather than creating pull requests. + +Individuals who have made known that they no longer intend to maintain their patches will have commit access to the [dwl-pathces] repository removed. + +A returning user who formerly had commit access is welcome to open an issue on [dwl-patches] requesting commit access be reinstated. When doing so, please link to the original issue opened that granted commit access. + +## Updating/Modifying/Adopting Existing Patches +- If the existing patch is already being maintained by another author, do not make modifications to it. +- Create an issue at [issues] @mentioning the current maintainer. +- If you receive no reply for seven days, you may assume the patch abandoned and you may adopt the patch. +- Modify the `README.md` with new links for your raw patch and for your git branch. + - **LEAVE PREVIOUS AUTHOR(S)' NICKS/LINKS INTACT UNDER THE "Authors" HEADING!** + - Add your own nick/link to the top of the "Authors" list. + +## Deprecating Existing Patches +- Patches will not be removed from this archive but may instead be deprecated if the author(s)/maintainer(s) of a patch so desire. +- Please do not open issues or contact maintainers to request deprecation of a patch. +- Deprecation of a patch will only occur if *all* authors or current maintainers of the patch agree to the decision to deprecate. +- In such a circumstance the author(s)/maintainer(s): + - Will create a commit moving the patch to the `stale-patches` directory + - May explain in the associated `README.md` any relevant details of the decision to deprecate the patch. +- This process allows current or future users of the patch the option to adopt, modify, or integrate stale/historical code or portions thereof. + +## stale-patches +Deprecated or unmaintained patches are held in the [stale-patches] directory. +Currently, this directory also contains `.md` description files from ancient patches predating the move to Codeberg. + +If you have the inclination to revive one of these, please follow the same procedures outlined below for contributing new patches. + +In your initial pull request (or in the commit that revives the stale patch if you already have write access), remove the corresponding `.md` file or the patch directory from [stale-patches]. + +[stale-patches]: https://codeberg.org/dwl/dwl-patches/src/branch/main/stale-patches +[dwl]: https://codeberg.org/dwl/dwl +[dwl-patches]: https://codeberg.org/dwl/dwl-patches +[issues]: https://codeberg.org/dwl/dwl-patches/issues +[release]: https://codeberg.org/dwl/dwl/releases +[Codeberg]: https://codeberg.org + +## diff --git a/dwl/dwl-patches/patches/accessnthmon/README.md b/dwl/dwl-patches/patches/accessnthmon/README.md new file mode 100644 index 0000000..b7cd6a9 --- /dev/null +++ b/dwl/dwl-patches/patches/accessnthmon/README.md @@ -0,0 +1,9 @@ +### Description +Port of dwm's accessnthmon. Adds functions to tag and focus monitor by index. + +### Download +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/patch/accessnthmonitor) +- [2024-05-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/accessnthmon/accessnthmon.patch) +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/accessnthmon/accessnthmon.patch b/dwl/dwl-patches/patches/accessnthmon/accessnthmon.patch new file mode 100644 index 0000000..f69f57d --- /dev/null +++ b/dwl/dwl-patches/patches/accessnthmon/accessnthmon.patch @@ -0,0 +1,122 @@ +From 5f531bfb1387ded7b8817faf7df760d3b998742b Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Sat, 27 Apr 2024 21:25:16 +0200 +Subject: [PATCH] feat: access nth monitor + +--- + config.def.h | 4 +++- + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..4709c5d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -108,7 +108,9 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} }, \ ++ { WLR_MODIFIER_ALT, KEY, focusnthmon, {.ui = TAG} }, \ ++ { WLR_MODIFIER_ALT|WLR_MODIFIER_SHIFT, SKEY, tagnthmon, {.ui = TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +diff --git a/dwl.c b/dwl.c +index bf763df..1d42caf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -278,8 +279,10 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static Monitor *numtomon(int num); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); ++static void focusnthmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); +@@ -329,6 +332,7 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tagnthmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -1233,6 +1237,25 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++Monitor * ++numtomon(int num) ++{ ++ Monitor *m = NULL; ++ int found = 0; ++ int i = 0; ++ ++ wl_list_for_each(m, &mons, link) { ++ if (!m->wlr_output->enabled) ++ i--; ++ else if (i == num) { ++ found = true; ++ break; ++ } ++ i++; ++ } ++ return found ? m : NULL; ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1320,6 +1343,16 @@ focusmon(const Arg *arg) + focusclient(focustop(selmon), 1); + } + ++void ++focusnthmon(const Arg *arg) ++{ ++ Monitor *m = numtomon(arg->i); ++ if (!m || m == selmon) ++ return; ++ selmon = m; ++ focusclient(focustop(selmon), 1); ++} ++ + void + focusstack(const Arg *arg) + { +@@ -2569,6 +2602,19 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tagnthmon(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ Monitor *m = numtomon(arg->i); ++ if (!m || !sel) ++ return; ++ setmon(sel, m, 0); ++ ++ arrange(selmon); ++ arrange(m); ++} ++ + void + tile(Monitor *m) + { +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/alwayscenter/README.md b/dwl/dwl-patches/patches/alwayscenter/README.md new file mode 100644 index 0000000..63d6f86 --- /dev/null +++ b/dwl/dwl-patches/patches/alwayscenter/README.md @@ -0,0 +1,9 @@ +### Description +Automatically center floating windows. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/alwayscenter) +- [2024-06-05](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/alwayscenter/alwayscenter.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl/dwl-patches/patches/alwayscenter/alwayscenter.patch b/dwl/dwl-patches/patches/alwayscenter/alwayscenter.patch new file mode 100644 index 0000000..59a83dc --- /dev/null +++ b/dwl/dwl-patches/patches/alwayscenter/alwayscenter.patch @@ -0,0 +1,39 @@ +From f43a49324c2ddd21100d6308d1adde9d894746e2 Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Wed, 5 Jun 2024 12:05:16 +0200 +Subject: [PATCH] center floating windows + +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 12f441e..c377c67 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -499,6 +499,11 @@ applyrules(Client *c) + } + } + ++ if (mon) { ++ c->geom.x = (mon->w.width - c->geom.width) / 2 + mon->m.x; ++ c->geom.y = (mon->w.height - c->geom.height) / 2 + mon->m.y; ++ } ++ + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); + } +@@ -1787,6 +1792,10 @@ mapnotify(struct wl_listener *listener, void *data) + * If there is no parent, apply rules */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; ++ if (p->mon) { ++ c->geom.x = (p->mon->w.width - c->geom.width) / 2 + p->mon->m.x; ++ c->geom.y = (p->mon->w.height - c->geom.height) / 2 + p->mon->m.y; ++ } + setmon(c, p->mon, p->tags); + } else { + applyrules(c); +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/attachbottom/README.md b/dwl/dwl-patches/patches/attachbottom/README.md new file mode 100644 index 0000000..80e12a0 --- /dev/null +++ b/dwl/dwl-patches/patches/attachbottom/README.md @@ -0,0 +1,10 @@ +### Description +Newly created windows are placed at the bottom of the client tile stack. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/attachbottom) +- [2024-05-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/attachbottom/attachbottom.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Aurel Weinhold](https://github.com/AurelWeinhold) diff --git a/dwl/dwl-patches/patches/attachbottom/attachbottom.patch b/dwl/dwl-patches/patches/attachbottom/attachbottom.patch new file mode 100644 index 0000000..470a79b --- /dev/null +++ b/dwl/dwl-patches/patches/attachbottom/attachbottom.patch @@ -0,0 +1,29 @@ +From 0dda3ed8634154fd3887b71133b451d66a11b61d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 23:31:41 +1000 +Subject: [PATCH] attachbottom + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index bf763dfc..12e08e2b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1605,7 +1605,11 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ if (clients.prev) ++ // tile at the bottom ++ wl_list_insert(clients.prev, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/attachfocused/README.md b/dwl/dwl-patches/patches/attachfocused/README.md new file mode 100644 index 0000000..a4f6049 --- /dev/null +++ b/dwl/dwl-patches/patches/attachfocused/README.md @@ -0,0 +1,11 @@ +### Description +Makes windows attach below the currently active window. + +KNOWN ISSUES: Upon closing the last client when using multiple monitors, the last client will briefly flash on all +monitors before closing. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/attachfocused/attachfocused.patch) + +### Authors +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl/dwl-patches/patches/attachfocused/attachfocused.patch b/dwl/dwl-patches/patches/attachfocused/attachfocused.patch new file mode 100644 index 0000000..2b8cd38 --- /dev/null +++ b/dwl/dwl-patches/patches/attachfocused/attachfocused.patch @@ -0,0 +1,29 @@ +From d03851c14073874f5b3d19a095e184dc24d219cd Mon Sep 17 00:00:00 2001 +From: MayOrMayNotBeACat +Date: Sun, 11 May 2025 20:24:51 -0400 +Subject: [PATCH] Make new windows attach to active client + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index cf3ef70..1907c5f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1726,7 +1726,11 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ Client* focused = focustop(selmon); ++ if (focused) ++ wl_list_insert(&focused->link, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/attachtop/README.md b/dwl/dwl-patches/patches/attachtop/README.md new file mode 100644 index 0000000..0241b7d --- /dev/null +++ b/dwl/dwl-patches/patches/attachtop/README.md @@ -0,0 +1,13 @@ +### Description +This is a port of attachtop patch for dwm: https://dwm.suckless.org/patches/attachtop + +New client attaches below the last master/on top of the stack. + +Behavior feels very intuitive as it doesn't disrupt existing masters no matter the amount of them, it only pushes the clients in stack down. + +### Download +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/attachtop) +- [2025-06-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/attachtop/attachtop.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) diff --git a/dwl/dwl-patches/patches/attachtop/attachtop.patch b/dwl/dwl-patches/patches/attachtop/attachtop.patch new file mode 100644 index 0000000..d447246 --- /dev/null +++ b/dwl/dwl-patches/patches/attachtop/attachtop.patch @@ -0,0 +1,36 @@ +From 29e6a4bef02c473cc5bd04804fe508bda265077d Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sun, 7 Apr 2024 21:10:17 +0200 +Subject: [PATCH] New client are attached on top of the stack + +--- + dwl.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index bf763df..02e3d07 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1605,7 +1605,18 @@ mapnotify(struct wl_listener *listener, void *data) + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ +- wl_list_insert(&clients, &c->link); ++ i = 0; ++ wl_list_for_each(w, &clients, link) { ++ if (!VISIBLEON(w, selmon) || w->isfloating) ++ continue; ++ p = w; ++ if (++i >= selmon->nmaster) ++ break; ++ } ++ if (i > 0) ++ wl_list_insert(&p->link, &c->link); ++ else ++ wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/autostart/README.md b/dwl/dwl-patches/patches/autostart/README.md new file mode 100644 index 0000000..1fd26d3 --- /dev/null +++ b/dwl/dwl-patches/patches/autostart/README.md @@ -0,0 +1,14 @@ +### Description +Allow dwl to execute commands from autostart array in your config.h file. And when you exit dwl all processes from autostart array will be killed. + +Note: Commands from array are executed using execvp(). So if you need to execute shell command you need to prefix it with "sh", "-c" (change sh to any shell you like). + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/autostart) +- [2025-01-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/autostart/autostart.patch) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/autostart/autostart-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Rayan Nakib](https://nakibrayan2.pages.dev/) +- [NFVblog](https://github.com/nf02) diff --git a/dwl/dwl-patches/patches/autostart/autostart-0.7.patch b/dwl/dwl-patches/patches/autostart/autostart-0.7.patch new file mode 100644 index 0000000..12e6d7e --- /dev/null +++ b/dwl/dwl-patches/patches/autostart/autostart-0.7.patch @@ -0,0 +1,154 @@ +From 787f7252d63945996f009828aff3c44afd0f7781 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:11:36 -0600 +Subject: [PATCH] port autostart patch from dwm +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +https://dwm.suckless.org/patches/cool_autostart/ +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 7 +++++++ + dwl.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 61 insertions(+), 5 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8dc6502 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,13 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index 5bf995e..e8b8727 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -249,6 +249,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -432,6 +433,9 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -580,6 +584,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -676,11 +701,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1497,18 +1532,31 @@ void + handlesig(int signo) + { + if (signo == SIGCHLD) { +-#ifdef XWAYLAND + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid +- && (!xwayland || in.si_pid != xwayland->server->pid)) +- waitpid(in.si_pid, NULL, 0); +-#else +- while (waitpid(-1, NULL, WNOHANG) > 0); ++#ifdef XWAYLAND ++ && (!xwayland || in.si_pid != xwayland->server->pid) + #endif ++ ) { ++ pid_t *p, *lim; ++ waitpid(in.si_pid, NULL, 0); ++ if (in.si_pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == in.si_pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +@@ -2224,6 +2272,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/autostart/autostart.patch b/dwl/dwl-patches/patches/autostart/autostart.patch new file mode 100644 index 0000000..71d8718 --- /dev/null +++ b/dwl/dwl-patches/patches/autostart/autostart.patch @@ -0,0 +1,148 @@ +From 3b0b0249d900121a90528616f4d11f733c7a5ca2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:11:36 -0600 +Subject: [PATCH] port autostart patch from dwm +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +https://dwm.suckless.org/patches/cool_autostart/ +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 7 +++++++ + dwl.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 62 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..8dc6502c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,13 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index ad21e1ba..3118e07f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -246,6 +246,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -455,6 +456,9 @@ static struct wlr_xwayland *xwayland; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -599,6 +603,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -695,12 +720,23 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; ++ + cleanuplisteners(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1551,10 +1587,25 @@ gpureset(struct wl_listener *listener, void *data) + void + handlesig(int signo) + { +- if (signo == SIGCHLD) +- while (waitpid(-1, NULL, WNOHANG) > 0); +- else if (signo == SIGINT || signo == SIGTERM) ++ if (signo == SIGCHLD) { ++ pid_t pid, *p, *lim; ++ while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { ++ if (pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } ++ } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); ++ } + } + + void +@@ -2241,6 +2292,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/bar-systray/README.md b/dwl/dwl-patches/patches/bar-systray/README.md new file mode 100644 index 0000000..b504173 --- /dev/null +++ b/dwl/dwl-patches/patches/bar-systray/README.md @@ -0,0 +1,19 @@ +### Description +Add a system tray to the [bar](/dwl/dwl-patches/src/branch/main/patches/bar). + +To keep dependencies to minimum, icon(svg) loading from the filesystem is not +supported. Applications that expect this will show the initial letter of the +program name, instead of a real icon. + +Menus for the icons are handled by a dmenu-like program of the user's choice. + +### Dependencies +- [bar.patch](/dwl/dwl-patches/src/branch/main/patches/bar) +- [libdbus](https://gitlab.freedesktop.org/dbus/dbus) + +### Download +- [git branch](/janetski/dwl/src/branch/0.7-bar-systray) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar-systray/bar-systray-0.7.patch) + +### Authors +- [janetski](https://codeberg.org/janetski) ([.vetu](https://discordapp.com/users/355488216469471242) on discord) diff --git a/dwl/dwl-patches/patches/bar-systray/bar-systray-0.7.patch b/dwl/dwl-patches/patches/bar-systray/bar-systray-0.7.patch new file mode 100644 index 0000000..1dda4c6 --- /dev/null +++ b/dwl/dwl-patches/patches/bar-systray/bar-systray-0.7.patch @@ -0,0 +1,3023 @@ +From cf228147250f4616d150fbe5276088c5f9969bba Mon Sep 17 00:00:00 2001 +From: vetu104 +Date: Sat, 29 Mar 2025 19:22:37 +0200 +Subject: [PATCH] Add a system tray next to sewn's bar + +--- + Makefile | 23 +- + config.def.h | 5 + + dbus.c | 242 +++++++++++++++ + dbus.h | 10 + + dwl.c | 107 ++++++- + systray/helpers.c | 43 +++ + systray/helpers.h | 12 + + systray/icon.c | 149 +++++++++ + systray/icon.h | 26 ++ + systray/item.c | 403 ++++++++++++++++++++++++ + systray/item.h | 46 +++ + systray/menu.c | 757 ++++++++++++++++++++++++++++++++++++++++++++++ + systray/menu.h | 11 + + systray/tray.c | 237 +++++++++++++++ + systray/tray.h | 37 +++ + systray/watcher.c | 551 +++++++++++++++++++++++++++++++++ + systray/watcher.h | 35 +++ + 17 files changed, 2681 insertions(+), 13 deletions(-) + create mode 100644 dbus.c + create mode 100644 dbus.h + create mode 100644 systray/helpers.c + create mode 100644 systray/helpers.h + create mode 100644 systray/icon.c + create mode 100644 systray/icon.h + create mode 100644 systray/item.c + create mode 100644 systray/item.h + create mode 100644 systray/menu.c + create mode 100644 systray/menu.h + create mode 100644 systray/tray.c + create mode 100644 systray/tray.h + create mode 100644 systray/watcher.c + create mode 100644 systray/watcher.h + +diff --git a/Makefile b/Makefile +index 9bc67db..9d50189 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,17 +12,28 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) dbus-1 + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + ++TRAYOBJS = systray/watcher.o systray/tray.o systray/item.o systray/icon.o systray/menu.o systray/helpers.o ++TRAYDEPS = systray/watcher.h systray/tray.h systray/item.h systray/icon.h systray/menu.h systray/helpers.h ++ + all: dwl +-dwl: dwl.o util.o +- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl: dwl.o util.o dbus.o $(TRAYOBJS) $(TRAYDEPS) ++ $(CC) dwl.o util.o dbus.o $(TRAYOBJS) $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ ++dwl.o: dwl.c client.h dbus.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ $(TRAYDEPS) + util.o: util.c util.h ++dbus.o: dbus.c dbus.h ++systray/watcher.o: systray/watcher.c $(TRAYDEPS) ++systray/tray.o: systray/tray.c $(TRAYDEPS) ++systray/item.o: systray/item.c $(TRAYDEPS) ++systray/icon.o: systray/icon.c $(TRAYDEPS) ++systray/menu.o: systray/menu.c $(TRAYDEPS) ++systray/helpers.o: systray/helpers.c $(TRAYDEPS) + + # wayland-scanner is a tool which generates C headers and rigging for Wayland + # protocols, which are specified in XML. wlroots requires you to rig these up +@@ -49,7 +60,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h systray/*.o + + dist: clean + mkdir -p dwl-$(VERSION) +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..451643e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,8 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int systrayspacing = 2; /* systray spacing */ ++static const int showsystray = 1; /* 0 means no systray */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; +@@ -127,6 +129,7 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* commands */ + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; ++static const char *dmenucmd[] = { "wmenu", NULL }; + + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +@@ -188,4 +191,6 @@ static const Button buttons[] = { + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, ++ { ClkTray, 0, BTN_LEFT, trayactivate, {0} }, ++ { ClkTray, 0, BTN_RIGHT, traymenu, {0} }, + }; +diff --git a/dbus.c b/dbus.c +new file mode 100644 +index 0000000..125312c +--- /dev/null ++++ b/dbus.c +@@ -0,0 +1,242 @@ ++#include "dbus.h" ++ ++#include "util.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++static void ++close_pipe(void *data) ++{ ++ int *pipefd = data; ++ ++ close(pipefd[0]); ++ close(pipefd[1]); ++ free(pipefd); ++} ++ ++static int ++dwl_dbus_dispatch(int fd, unsigned int mask, void *data) ++{ ++ DBusConnection *conn = data; ++ ++ int pending; ++ DBusDispatchStatus oldstatus, newstatus; ++ ++ oldstatus = dbus_connection_get_dispatch_status(conn); ++ newstatus = dbus_connection_dispatch(conn); ++ ++ /* Don't clear pending flag if status didn't change */ ++ if (oldstatus == newstatus) ++ return 0; ++ ++ if (read(fd, &pending, sizeof(int)) < 0) { ++ perror("read"); ++ die("Error in dbus dispatch"); ++ } ++ ++ return 0; ++} ++ ++static int ++dwl_dbus_watch_handle(int fd, uint32_t mask, void *data) ++{ ++ DBusWatch *watch = data; ++ ++ uint32_t flags = 0; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return 0; ++ ++ if (mask & WL_EVENT_READABLE) ++ flags |= DBUS_WATCH_READABLE; ++ if (mask & WL_EVENT_WRITABLE) ++ flags |= DBUS_WATCH_WRITABLE; ++ if (mask & WL_EVENT_HANGUP) ++ flags |= DBUS_WATCH_HANGUP; ++ if (mask & WL_EVENT_ERROR) ++ flags |= DBUS_WATCH_ERROR; ++ ++ dbus_watch_handle(watch, flags); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int fd; ++ struct wl_event_source *watch_source; ++ uint32_t mask = 0, flags; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return TRUE; ++ ++ flags = dbus_watch_get_flags(watch); ++ if (flags & DBUS_WATCH_READABLE) ++ mask |= WL_EVENT_READABLE; ++ if (flags & DBUS_WATCH_WRITABLE) ++ mask |= WL_EVENT_WRITABLE; ++ ++ fd = dbus_watch_get_unix_fd(watch); ++ watch_source = wl_event_loop_add_fd(loop, fd, mask, ++ dwl_dbus_watch_handle, watch); ++ ++ dbus_watch_set_data(watch, watch_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_source *watch_source = dbus_watch_get_data(watch); ++ ++ if (watch_source) ++ wl_event_source_remove(watch_source); ++} ++ ++static int ++dwl_dbus_timeout_handle(void *data) ++{ ++ DBusTimeout *timeout = data; ++ ++ if (dbus_timeout_get_enabled(timeout)) ++ dbus_timeout_handle(timeout); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int r, interval; ++ struct wl_event_source *timeout_source; ++ ++ if (!dbus_timeout_get_enabled(timeout)) ++ return TRUE; ++ ++ interval = dbus_timeout_get_interval(timeout); ++ ++ timeout_source = ++ wl_event_loop_add_timer(loop, dwl_dbus_timeout_handle, timeout); ++ ++ r = wl_event_source_timer_update(timeout_source, interval); ++ if (r < 0) { ++ wl_event_source_remove(timeout_source); ++ return FALSE; ++ } ++ ++ dbus_timeout_set_data(timeout, timeout_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_source *timeout_source; ++ ++ timeout_source = dbus_timeout_get_data(timeout); ++ ++ if (timeout_source) { ++ wl_event_source_timer_update(timeout_source, 0); ++ wl_event_source_remove(timeout_source); ++ } ++} ++ ++static void ++dwl_dbus_dispatch_status(DBusConnection *conn, DBusDispatchStatus status, ++ void *data) ++{ ++ int *pipefd = data; ++ ++ if (status != DBUS_DISPATCH_COMPLETE) { ++ int pending = 1; ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) { ++ perror("write"); ++ die("Error in dispatch status"); ++ } ++ } ++} ++ ++struct wl_event_source * ++startbus(DBusConnection *conn, struct wl_event_loop *loop) ++{ ++ int *pipefd; ++ int pending = 1, flags; ++ struct wl_event_source *bus_source = NULL; ++ ++ pipefd = ecalloc(2, sizeof(int)); ++ ++ /* ++ * Libdbus forbids calling dbus_connection_dipatch from the ++ * DBusDispatchStatusFunction directly. Notify the event loop of ++ * updates via a self-pipe. ++ */ ++ if (pipe(pipefd) < 0) ++ goto fail; ++ if (((flags = fcntl(pipefd[0], F_GETFD)) < 0) || ++ fcntl(pipefd[0], F_SETFD, flags | FD_CLOEXEC) < 0 || ++ ((flags = fcntl(pipefd[1], F_GETFD)) < 0) || ++ fcntl(pipefd[1], F_SETFD, flags | FD_CLOEXEC) < 0) { ++ goto fail; ++ } ++ ++ dbus_connection_set_exit_on_disconnect(conn, FALSE); ++ ++ bus_source = wl_event_loop_add_fd(loop, pipefd[0], WL_EVENT_READABLE, ++ dwl_dbus_dispatch, conn); ++ if (!bus_source) ++ goto fail; ++ ++ dbus_connection_set_dispatch_status_function(conn, ++ dwl_dbus_dispatch_status, ++ pipefd, close_pipe); ++ if (!dbus_connection_set_watch_functions(conn, dwl_dbus_add_watch, ++ dwl_dbus_remove_watch, NULL, ++ loop, NULL)) { ++ goto fail; ++ } ++ if (!dbus_connection_set_timeout_functions(conn, dwl_dbus_add_timeout, ++ dwl_dbus_remove_timeout, ++ NULL, loop, NULL)) { ++ goto fail; ++ } ++ if (dbus_connection_get_dispatch_status(conn) != DBUS_DISPATCH_COMPLETE) ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) ++ goto fail; ++ ++ return bus_source; ++ ++fail: ++ if (bus_source) ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++ ++ return NULL; ++} ++ ++void ++stopbus(DBusConnection *conn, struct wl_event_source *bus_source) ++{ ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++} +diff --git a/dbus.h b/dbus.h +new file mode 100644 +index 0000000..b374b98 +--- /dev/null ++++ b/dbus.h +@@ -0,0 +1,10 @@ ++#ifndef DWLDBUS_H ++#define DWLDBUS_H ++ ++#include ++#include ++ ++struct wl_event_source* startbus (DBusConnection *conn, struct wl_event_loop *loop); ++void stopbus (DBusConnection *conn, struct wl_event_source *bus_source); ++ ++#endif /* DWLDBUS_H */ +diff --git a/dwl.c b/dwl.c +index ece537a..7753ef6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -71,6 +72,9 @@ + + #include "util.h" + #include "drwl.h" ++#include "dbus.h" ++#include "systray/tray.h" ++#include "systray/watcher.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -89,7 +93,7 @@ enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +-enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot, ClkTray }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -218,6 +222,7 @@ struct Monitor { + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ ++ Tray *tray; + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -376,6 +381,9 @@ static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); ++static void trayactivate(const Arg *arg); ++static void traymenu(const Arg *arg); ++static void traynotify(void *data); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); +@@ -451,6 +459,10 @@ static Monitor *selmon; + static char stext[256]; + static struct wl_event_source *status_event_source; + ++static DBusConnection *bus_conn; ++static struct wl_event_source *bus_source; ++static Watcher watcher = {.running = 0}; ++ + static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, +@@ -721,8 +733,8 @@ bufrelease(struct wl_listener *listener, void *data) + void + buttonpress(struct wl_listener *listener, void *data) + { +- unsigned int i = 0, x = 0; +- double cx; ++ unsigned int i = 0, x = 0, ti = 0; ++ double cx, tx = 0; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; +@@ -732,6 +744,7 @@ buttonpress(struct wl_listener *listener, void *data) + Arg arg = {0}; + Client *c; + const Button *b; ++ int traywidth; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + +@@ -751,6 +764,8 @@ buttonpress(struct wl_listener *listener, void *data) + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ traywidth = tray_get_width(selmon->tray); ++ + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -759,8 +774,16 @@ buttonpress(struct wl_listener *listener, void *data) + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; +- else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2) && cx < selmon->b.width - traywidth) { + click = ClkStatus; ++ } else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ unsigned int tray_n_items = watcher_get_n_items(&watcher); ++ tx = selmon->b.width - traywidth; ++ do ++ tx += tray_n_items ? (int)(traywidth / tray_n_items) : 0; ++ while (cx >= tx && ++ti < tray_n_items); ++ click = ClkTray; ++ arg.ui = ti; + } else + click = ClkTitle; + } +@@ -774,7 +797,12 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { +- b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); ++ if (click == ClkTagBar && b->arg.i == 0) ++ b->func(&arg); ++ else if (click == ClkTray && b->arg.i == 0) ++ b->func(&arg); ++ else ++ b->func(&b->arg); + return; + } + } +@@ -840,6 +868,14 @@ cleanup(void) + + destroykeyboardgroup(&kb_group->destroy, NULL); + ++ if (watcher.running) ++ watcher_stop(&watcher); ++ ++ if (showbar && showsystray) { ++ stopbus(bus_conn, bus_source); ++ dbus_connection_unref(bus_conn); ++ } ++ + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); +@@ -868,6 +904,9 @@ cleanupmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + ++ if (showsystray) ++ destroytray(m->tray); ++ + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + +@@ -1506,6 +1545,7 @@ dirtomon(enum wlr_direction dir) + void + drawbar(Monitor *m) + { ++ int traywidth = 0; + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; +@@ -1518,11 +1558,13 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ traywidth = tray_get_width(m->tray); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ drwl_text(m->drw, m->b.width - (tw + traywidth), 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { +@@ -1548,7 +1590,7 @@ drawbar(Monitor *m) + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->b.width - tw - x) > m->b.height) { ++ if ((w = m->b.width - (tw + x + traywidth)) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); +@@ -1560,6 +1602,15 @@ drawbar(Monitor *m) + } + } + ++ if (traywidth > 0) { ++ pixman_image_composite32(PIXMAN_OP_SRC, ++ m->tray->image, NULL, m->drw->image, ++ 0, 0, ++ 0, 0, ++ m->b.width - traywidth, 0, ++ traywidth, m->b.height); ++ } ++ + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, +@@ -1568,6 +1619,26 @@ drawbar(Monitor *m) + wlr_buffer_unlock(&buf->base); + } + ++void ++traynotify(void *data) ++{ ++ Monitor *m = data; ++ ++ drawbar(m); ++} ++ ++void ++trayactivate(const Arg *arg) ++{ ++ tray_leftclicked(selmon->tray, arg->ui); ++} ++ ++void ++traymenu(const Arg *arg) ++{ ++ tray_rightclicked(selmon->tray, arg->ui, dmenucmd); ++} ++ + void + drawbars(void) + { +@@ -2818,6 +2889,15 @@ setup(void) + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + ++ bus_conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); ++ if (!bus_conn) ++ die("Failed to connect to bus"); ++ bus_source = startbus(bus_conn, event_loop); ++ if (!bus_source) ++ die("Failed to start listening to bus events"); ++ if (showbar && showsystray) ++ watcher_start(&watcher, bus_conn, event_loop); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -3160,6 +3240,7 @@ updatebar(Monitor *m) + size_t i; + int rw, rh; + char fontattrs[12]; ++ Tray *tray; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; +@@ -3185,6 +3266,18 @@ updatebar(Monitor *m) + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++ ++ if (showsystray) { ++ if (m->tray) ++ destroytray(m->tray); ++ tray = createtray(m, ++ m->b.height, systrayspacing, colors[SchemeNorm], fonts, fontattrs, ++ &traynotify, &watcher); ++ if (!tray) ++ die("Couldn't create tray for monitor"); ++ m->tray = tray; ++ wl_list_insert(&watcher.trays, &tray->link); ++ } + } + + void +diff --git a/systray/helpers.c b/systray/helpers.c +new file mode 100644 +index 0000000..d1af9f8 +--- /dev/null ++++ b/systray/helpers.c +@@ -0,0 +1,43 @@ ++#include "helpers.h" ++ ++#include ++ ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++int ++request_property(DBusConnection *conn, const char *busname, const char *busobj, ++ const char *prop, const char *iface, PropHandler handler, ++ void *data) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ int r; ++ ++ if (!(msg = dbus_message_new_method_call(busname, busobj, ++ DBUS_INTERFACE_PROPERTIES, ++ "Get")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, handler, data, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ if (msg) ++ dbus_message_unref(msg); ++ return r; ++} +diff --git a/systray/helpers.h b/systray/helpers.h +new file mode 100644 +index 0000000..2c592e0 +--- /dev/null ++++ b/systray/helpers.h +@@ -0,0 +1,12 @@ ++#ifndef HELPERS_H ++#define HELPERS_H ++ ++#include ++ ++typedef void (*PropHandler)(DBusPendingCall *pcall, void *data); ++ ++int request_property (DBusConnection *conn, const char *busname, ++ const char *busobj, const char *prop, const char *iface, ++ PropHandler handler, void *data); ++ ++#endif /* HELPERS_H */ +diff --git a/systray/icon.c b/systray/icon.c +new file mode 100644 +index 0000000..1b97866 +--- /dev/null ++++ b/systray/icon.c +@@ -0,0 +1,149 @@ ++#include "icon.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#define PREMUL_ALPHA(chan, alpha) (chan * alpha + 127) / 255 ++ ++/* ++ * Converts pixels from uint8_t[4] to uint32_t and ++ * straight alpha to premultiplied alpha. ++ */ ++static uint32_t * ++to_pixman(const uint8_t *src, int n_pixels, size_t *pix_size) ++{ ++ uint32_t *dest = NULL; ++ ++ *pix_size = n_pixels * sizeof(uint32_t); ++ dest = malloc(*pix_size); ++ if (!dest) ++ return NULL; ++ ++ for (int i = 0; i < n_pixels; i++) { ++ uint8_t a = src[i * 4 + 0]; ++ uint8_t r = src[i * 4 + 1]; ++ uint8_t g = src[i * 4 + 2]; ++ uint8_t b = src[i * 4 + 3]; ++ ++ /* ++ * Skip premultiplying fully opaque and fully transparent ++ * pixels. ++ */ ++ if (a == 0) { ++ dest[i] = 0; ++ ++ } else if (a == 255) { ++ dest[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ++ ((uint32_t)g << 8) | ((uint32_t)b); ++ ++ } else { ++ dest[i] = ((uint32_t)a << 24) | ++ ((uint32_t)PREMUL_ALPHA(r, a) << 16) | ++ ((uint32_t)PREMUL_ALPHA(g, a) << 8) | ++ ((uint32_t)PREMUL_ALPHA(b, a)); ++ } ++ } ++ ++ return dest; ++} ++ ++Icon * ++createicon(const uint8_t *buf, int width, int height, int size) ++{ ++ Icon *icon = NULL; ++ ++ int n_pixels; ++ pixman_image_t *img = NULL; ++ size_t pixbuf_size; ++ uint32_t *buf_pixman = NULL; ++ uint8_t *buf_orig = NULL; ++ ++ n_pixels = size / 4; ++ ++ icon = calloc(1, sizeof(Icon)); ++ buf_orig = malloc(size); ++ buf_pixman = to_pixman(buf, n_pixels, &pixbuf_size); ++ if (!icon || !buf_orig || !buf_pixman) ++ goto fail; ++ ++ img = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, ++ buf_pixman, width * 4); ++ if (!img) ++ goto fail; ++ ++ memcpy(buf_orig, buf, size); ++ ++ icon->buf_orig = buf_orig; ++ icon->buf_pixman = buf_pixman; ++ icon->img = img; ++ icon->size_orig = size; ++ icon->size_pixman = pixbuf_size; ++ ++ return icon; ++ ++fail: ++ free(buf_orig); ++ if (img) ++ pixman_image_unref(img); ++ free(buf_pixman); ++ free(icon); ++ return NULL; ++} ++ ++void ++destroyicon(Icon *icon) ++{ ++ if (icon->img) ++ pixman_image_unref(icon->img); ++ free(icon->buf_orig); ++ free(icon->buf_pixman); ++ free(icon); ++} ++ ++FallbackIcon * ++createfallbackicon(const char *appname, int fgcolor, struct fcft_font *font) ++{ ++ const struct fcft_glyph *glyph; ++ char initial; ++ ++ if ((unsigned char)appname[0] > 127) { ++ /* first character is not ascii */ ++ initial = '?'; ++ } else { ++ initial = toupper(*appname); ++ } ++ ++ glyph = fcft_rasterize_char_utf32(font, initial, FCFT_SUBPIXEL_DEFAULT); ++ if (!glyph) ++ return NULL; ++ ++ return glyph; ++} ++ ++int ++resize_image(pixman_image_t *image, int new_width, int new_height) ++{ ++ int src_width = pixman_image_get_width(image); ++ int src_height = pixman_image_get_height(image); ++ pixman_transform_t transform; ++ pixman_fixed_t scale_x, scale_y; ++ ++ if (src_width == new_width && src_height == new_height) ++ return 0; ++ ++ scale_x = pixman_double_to_fixed((double)src_width / new_width); ++ scale_y = pixman_double_to_fixed((double)src_height / new_height); ++ ++ pixman_transform_init_scale(&transform, scale_x, scale_y); ++ if (!pixman_image_set_filter(image, PIXMAN_FILTER_BEST, NULL, 0) || ++ !pixman_image_set_transform(image, &transform)) { ++ return -1; ++ } ++ ++ return 0; ++} +diff --git a/systray/icon.h b/systray/icon.h +new file mode 100644 +index 0000000..20f281b +--- /dev/null ++++ b/systray/icon.h +@@ -0,0 +1,26 @@ ++#ifndef ICON_H ++#define ICON_H ++ ++#include ++#include ++ ++#include ++#include ++ ++typedef const struct fcft_glyph FallbackIcon; ++ ++typedef struct { ++ pixman_image_t *img; ++ uint32_t *buf_pixman; ++ uint8_t *buf_orig; ++ size_t size_orig; ++ size_t size_pixman; ++} Icon; ++ ++Icon *createicon (const uint8_t *buf, int width, int height, int size); ++FallbackIcon *createfallbackicon (const char *appname, int fgcolor, ++ struct fcft_font *font); ++void destroyicon (Icon *icon); ++int resize_image (pixman_image_t *orig, int new_width, int new_height); ++ ++#endif /* ICON_H */ +diff --git a/systray/item.c b/systray/item.c +new file mode 100644 +index 0000000..4359a28 +--- /dev/null ++++ b/systray/item.c +@@ -0,0 +1,403 @@ ++#include "item.h" ++ ++#include "helpers.h" ++#include "icon.h" ++#include "watcher.h" ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define RULEBSIZE 256 ++#define MIN(A, B) ((A) < (B) ? (A) : (B)) ++ ++static const char *match_string = ++ "type='signal'," ++ "sender='%s'," ++ "interface='" SNI_NAME ++ "'," ++ "member='NewIcon'"; ++ ++static Watcher * ++item_get_watcher(const Item *item) ++{ ++ if (!item) ++ return NULL; ++ ++ return item->watcher; ++} ++ ++static DBusConnection * ++item_get_connection(const Item *item) ++{ ++ if (!item || !item->watcher) ++ return NULL; ++ ++ return item->watcher->conn; ++} ++ ++static const uint8_t * ++extract_image(DBusMessageIter *iter, dbus_int32_t *width, dbus_int32_t *height, ++ int *size) ++{ ++ DBusMessageIter vals, bytes; ++ const uint8_t *buf; ++ ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, width); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, height); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&vals, &bytes); ++ if (dbus_message_iter_get_arg_type(&bytes) != DBUS_TYPE_BYTE) ++ goto fail; ++ dbus_message_iter_get_fixed_array(&bytes, &buf, size); ++ if (size == 0) ++ goto fail; ++ ++ return buf; ++ ++fail: ++ return NULL; ++} ++ ++static int ++select_image(DBusMessageIter *iter, int target_width) ++{ ++ DBusMessageIter vals; ++ dbus_int32_t cur_width; ++ int i = 0; ++ ++ do { ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ return -1; ++ dbus_message_iter_get_basic(&vals, &cur_width); ++ if (cur_width >= target_width) ++ return i; ++ ++ i++; ++ } while (dbus_message_iter_next(iter)); ++ ++ /* return last index if desired not found */ ++ return --i; ++} ++ ++static void ++menupath_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, opath; ++ char *path_dup = NULL; ++ const char *path; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get menupath\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &opath); ++ if (dbus_message_iter_get_arg_type(&opath) != DBUS_TYPE_OBJECT_PATH) ++ goto fail; ++ dbus_message_iter_get_basic(&opath, &path); ++ ++ path_dup = strdup(path); ++ if (!path_dup) ++ goto fail; ++ ++ item->menu_busobj = path_dup; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ free(path_dup); ++ dbus_error_free(&err); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++/* ++ * Gets the Id dbus property, which is the name of the application, ++ * most of the time... ++ * The initial letter will be used as a fallback icon ++ */ ++static void ++id_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, string; ++ Watcher *watcher; ++ char *id_dup = NULL; ++ const char *id; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get appid\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &string); ++ if (dbus_message_iter_get_arg_type(&string) != DBUS_TYPE_STRING) ++ goto fail; ++ dbus_message_iter_get_basic(&string, &id); ++ ++ id_dup = strdup(id); ++ if (!id_dup) ++ goto fail; ++ item->appid = id_dup; ++ ++ /* Don't trigger update if this item already has a real icon */ ++ if (!item->icon) ++ watcher_update_trays(watcher); ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ dbus_error_free(&err); ++ if (id_dup) ++ free(id_dup); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static void ++pixmap_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, array, select, strct; ++ Icon *icon = NULL; ++ Watcher *watcher; ++ dbus_int32_t width, height; ++ int selected_index, size; ++ const uint8_t *buf; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) ++ goto fail; ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &array); ++ if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&array, &select); ++ if (dbus_message_iter_get_arg_type(&select) != DBUS_TYPE_STRUCT) ++ goto fail; ++ selected_index = select_image(&select, 22); // Get the 22*22 image ++ if (selected_index < 0) ++ goto fail; ++ ++ dbus_message_iter_recurse(&array, &strct); ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_STRUCT) ++ goto fail; ++ for (int i = 0; i < selected_index; i++) ++ dbus_message_iter_next(&strct); ++ buf = extract_image(&strct, &width, &height, &size); ++ if (!buf) ++ goto fail; ++ ++ if (!item->icon) { ++ /* First icon */ ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else if (memcmp(item->icon->buf_orig, buf, ++ MIN(item->icon->size_orig, (size_t)size)) != 0) { ++ /* New icon */ ++ destroyicon(item->icon); ++ item->icon = NULL; ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else { ++ /* Icon didn't change */ ++ } ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (icon) ++ destroyicon(icon); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static DBusHandlerResult ++handle_newicon(Item *item, DBusConnection *conn, DBusMessage *msg) ++{ ++ const char *sender = dbus_message_get_sender(msg); ++ ++ if (sender && strcmp(sender, item->busname) == 0) { ++ request_property(conn, item->busname, item->busobj, ++ "IconPixmap", SNI_IFACE, pixmap_ready_handler, ++ item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ } else { ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Item *item = data; ++ ++ if (dbus_message_is_signal(msg, SNI_IFACE, "NewIcon")) ++ return handle_newicon(item, conn, msg); ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++Item * ++createitem(const char *busname, const char *busobj, Watcher *watcher) ++{ ++ DBusConnection *conn; ++ Item *item; ++ char *busname_dup = NULL; ++ char *busobj_dup = NULL; ++ char match_rule[RULEBSIZE]; ++ ++ item = calloc(1, sizeof(Item)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!item || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ conn = watcher->conn; ++ item->busname = busname_dup; ++ item->busobj = busobj_dup; ++ item->watcher = watcher; ++ ++ request_property(conn, busname, busobj, "IconPixmap", SNI_IFACE, ++ pixmap_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Id", SNI_IFACE, ++ id_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Menu", SNI_IFACE, ++ menupath_ready_handler, item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, busname) >= ++ RULEBSIZE) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, item, NULL)) ++ goto fail; ++ dbus_bus_add_match(conn, match_rule, NULL); ++ ++ return item; ++ ++fail: ++ free(busname_dup); ++ free(busobj_dup); ++ return NULL; ++} ++ ++void ++destroyitem(Item *item) ++{ ++ DBusConnection *conn; ++ char match_rule[RULEBSIZE]; ++ ++ conn = item_get_connection(item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, ++ item->busname) < RULEBSIZE) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, item); ++ } ++ if (item->icon) ++ destroyicon(item->icon); ++ free(item->menu_busobj); ++ free(item->busname); ++ free(item->busobj); ++ free(item->appid); ++ free(item); ++} ++ ++void ++item_activate(Item *item) ++{ ++ DBusConnection *conn; ++ DBusMessage *msg = NULL; ++ dbus_int32_t x = 0, y = 0; ++ ++ conn = item_get_connection(item); ++ ++ if (!(msg = dbus_message_new_method_call(item->busname, item->busobj, ++ SNI_IFACE, "Activate")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, ++ &y, DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, NULL, -1)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (msg) ++ dbus_message_unref(msg); ++} +diff --git a/systray/item.h b/systray/item.h +new file mode 100644 +index 0000000..dc22e25 +--- /dev/null ++++ b/systray/item.h +@@ -0,0 +1,46 @@ ++#ifndef ITEM_H ++#define ITEM_H ++ ++#include "icon.h" ++#include "watcher.h" ++ ++#include ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierItem"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierItem" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ * ++ */ ++#define SNI_NAME "org.kde.StatusNotifierItem" ++#define SNI_OPATH "/StatusNotifierItem" ++#define SNI_IFACE "org.kde.StatusNotifierItem" ++ ++typedef struct Item { ++ struct wl_list icons; ++ char *busname; ++ char *busobj; ++ char *menu_busobj; ++ char *appid; ++ Icon *icon; ++ FallbackIcon *fallback_icon; ++ ++ Watcher *watcher; ++ ++ int fgcolor; ++ ++ int ready; ++ ++ struct wl_list link; ++} Item; ++ ++Item *createitem (const char *busname, const char *busobj, Watcher *watcher); ++void destroyitem (Item *item); ++ ++void item_activate (Item *item); ++void item_show_menu (Item *item); ++ ++#endif /* ITEM_H */ +diff --git a/systray/menu.c b/systray/menu.c +new file mode 100644 +index 0000000..ff3bfb5 +--- /dev/null ++++ b/systray/menu.c +@@ -0,0 +1,757 @@ ++#include "menu.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define DBUSMENU_IFACE "com.canonical.dbusmenu" ++#define BUFSIZE 512 ++#define LABEL_MAX 64 ++ ++typedef struct { ++ struct wl_array layout; ++ DBusConnection *conn; ++ struct wl_event_loop *loop; ++ char *busname; ++ char *busobj; ++ const char **menucmd; ++} Menu; ++ ++typedef struct { ++ char label[LABEL_MAX]; ++ dbus_int32_t id; ++ struct wl_array submenu; ++ int has_submenu; ++} MenuItem; ++ ++typedef struct { ++ struct wl_event_loop *loop; ++ struct wl_event_source *fd_source; ++ struct wl_array *layout_node; ++ Menu *menu; ++ pid_t menu_pid; ++ int fd; ++} MenuShowContext; ++ ++static int extract_menu (DBusMessageIter *av, struct wl_array *menu); ++static int real_show_menu (Menu *menu, struct wl_array *m); ++static void submenus_destroy_recursive (struct wl_array *m); ++ ++static void ++menuitem_init(MenuItem *mi) ++{ ++ wl_array_init(&mi->submenu); ++ mi->id = -1; ++ *mi->label = '\0'; ++ mi->has_submenu = 0; ++} ++ ++static void ++submenus_destroy_recursive(struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, layout_node) { ++ if (mi->has_submenu) { ++ submenus_destroy_recursive(&mi->submenu); ++ wl_array_release(&mi->submenu); ++ } ++ } ++} ++ ++static void ++menu_destroy(Menu *menu) ++{ ++ submenus_destroy_recursive(&menu->layout); ++ wl_array_release(&menu->layout); ++ free(menu->busname); ++ free(menu->busobj); ++ free(menu); ++} ++ ++static void ++menu_show_ctx_finalize(MenuShowContext *ctx, int error) ++{ ++ if (ctx->fd_source) ++ wl_event_source_remove(ctx->fd_source); ++ ++ if (ctx->fd >= 0) ++ close(ctx->fd); ++ ++ if (ctx->menu_pid >= 0) { ++ if (waitpid(ctx->menu_pid, NULL, WNOHANG) == 0) ++ kill(ctx->menu_pid, SIGTERM); ++ } ++ ++ if (error) ++ menu_destroy(ctx->menu); ++ ++ free(ctx); ++} ++ ++static void ++remove_newline(char *buf) ++{ ++ size_t len; ++ ++ len = strlen(buf); ++ if (len > 0 && buf[len - 1] == '\n') ++ buf[len - 1] = '\0'; ++} ++ ++static void ++send_clicked(const char *busname, const char *busobj, int itemid, ++ DBusConnection *conn) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter sub = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *data = ""; ++ const char *eventid = "clicked"; ++ time_t timestamp; ++ ++ timestamp = time(NULL); ++ ++ msg = dbus_message_new_method_call(busname, busobj, DBUSMENU_IFACE, ++ "Event"); ++ if (!msg) ++ goto fail; ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &itemid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, ++ &eventid) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_STRING_AS_STRING, ++ &sub) || ++ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &data) || ++ !dbus_message_iter_close_container(&iter, &sub) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, ++ ×tamp)) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(conn, msg, NULL, -1)) ++ goto fail; ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&iter, &sub); ++ if (msg) ++ dbus_message_unref(msg); ++} ++ ++static void ++menuitem_selected(const char *label, struct wl_array *m, Menu *menu) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, m) { ++ if (strcmp(mi->label, label) == 0) { ++ if (mi->has_submenu) { ++ real_show_menu(menu, &mi->submenu); ++ ++ } else { ++ send_clicked(menu->busname, menu->busobj, ++ mi->id, menu->conn); ++ menu_destroy(menu); ++ } ++ ++ return; ++ } ++ } ++} ++ ++static int ++read_pipe(int fd, uint32_t mask, void *data) ++{ ++ MenuShowContext *ctx = data; ++ ++ char buf[BUFSIZE]; ++ ssize_t bytes_read; ++ ++ bytes_read = read(fd, buf, BUFSIZE); ++ /* 0 == Got EOF, menu program closed without writing to stdout */ ++ if (bytes_read <= 0) ++ goto fail; ++ ++ buf[bytes_read] = '\0'; ++ remove_newline(buf); ++ ++ menuitem_selected(buf, ctx->layout_node, ctx->menu); ++ menu_show_ctx_finalize(ctx, 0); ++ return 0; ++ ++fail: ++ menu_show_ctx_finalize(ctx, 1); ++ return 0; ++} ++ ++static MenuShowContext * ++prepare_show_ctx(struct wl_event_loop *loop, int monitor_fd, int dmenu_pid, ++ struct wl_array *layout_node, Menu *menu) ++{ ++ MenuShowContext *ctx = NULL; ++ struct wl_event_source *fd_src = NULL; ++ ++ ctx = calloc(1, sizeof(MenuShowContext)); ++ if (!ctx) ++ goto fail; ++ ++ fd_src = wl_event_loop_add_fd(menu->loop, monitor_fd, WL_EVENT_READABLE, ++ read_pipe, ctx); ++ if (!fd_src) ++ goto fail; ++ ++ ctx->fd_source = fd_src; ++ ctx->fd = monitor_fd; ++ ctx->menu_pid = dmenu_pid; ++ ctx->layout_node = layout_node; ++ ctx->menu = menu; ++ ++ return ctx; ++ ++fail: ++ if (fd_src) ++ wl_event_source_remove(fd_src); ++ free(ctx); ++ return NULL; ++} ++ ++static int ++write_dmenu_buf(char *buf, struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ int r; ++ size_t curlen = 0; ++ ++ *buf = '\0'; ++ ++ wl_array_for_each(mi, layout_node) { ++ curlen += strlen(mi->label) + ++ 2; /* +2 is newline + nul terminator */ ++ if (curlen + 1 > BUFSIZE) { ++ r = -1; ++ goto fail; ++ } ++ ++ strcat(buf, mi->label); ++ strcat(buf, "\n"); ++ } ++ remove_newline(buf); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Failed to construct dmenu input\n"); ++ return r; ++} ++ ++static int ++real_show_menu(Menu *menu, struct wl_array *layout_node) ++{ ++ MenuShowContext *ctx = NULL; ++ char buf[BUFSIZE]; ++ int to_pipe[2], from_pipe[2]; ++ pid_t pid; ++ ++ if (pipe(to_pipe) < 0 || pipe(from_pipe) < 0) ++ goto fail; ++ ++ pid = fork(); ++ if (pid < 0) { ++ goto fail; ++ } else if (pid == 0) { ++ dup2(to_pipe[0], STDIN_FILENO); ++ dup2(from_pipe[1], STDOUT_FILENO); ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ close(from_pipe[0]); ++ ++ if (execvp(menu->menucmd[0], (char *const *)menu->menucmd)) { ++ perror("Error spawning menu program"); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ ctx = prepare_show_ctx(menu->loop, from_pipe[0], pid, layout_node, ++ menu); ++ if (!ctx) ++ goto fail; ++ ++ if (write_dmenu_buf(buf, layout_node) < 0 || ++ write(to_pipe[1], buf, strlen(buf)) < 0) { ++ goto fail; ++ } ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ return 0; ++ ++fail: ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ menu_show_ctx_finalize(ctx, 1); ++ return -1; ++} ++ ++static void ++createmenuitem(MenuItem *mi, dbus_int32_t id, const char *label, ++ int toggle_state, int has_submenu) ++{ ++ char *tok; ++ char temp[LABEL_MAX]; ++ ++ if (toggle_state == 0) ++ strcpy(mi->label, "☐ "); ++ else if (toggle_state == 1) ++ strcpy(mi->label, "✓ "); ++ else ++ strcpy(mi->label, " "); ++ ++ /* Remove "mnemonics" (underscores which mark keyboard shortcuts) */ ++ strcpy(temp, label); ++ tok = strtok(temp, "_"); ++ do { ++ strcat(mi->label, tok); ++ } while ((tok = strtok(NULL, "_"))); ++ ++ if (has_submenu) { ++ mi->has_submenu = 1; ++ strcat(mi->label, " →"); ++ } ++ ++ mi->id = id; ++} ++ ++/** ++ * Populates the passed in menuitem based on the dictionary contents. ++ * ++ * @param[in] dict ++ * @param[in] itemid ++ * @param[in] mi ++ * @param[out] has_submenu ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++read_dict(DBusMessageIter *dict, dbus_int32_t itemid, MenuItem *mi, ++ int *has_submenu) ++{ ++ DBusMessageIter member, val; ++ const char *children_display = NULL, *label = NULL, *toggle_type = NULL; ++ const char *key; ++ dbus_bool_t visible = TRUE, enabled = TRUE; ++ dbus_int32_t toggle_state = 1; ++ int r; ++ ++ do { ++ dbus_message_iter_recurse(dict, &member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&member, &key); ++ ++ dbus_message_iter_next(&member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&member, &val); ++ ++ if (strcmp(key, "visible") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &visible); ++ ++ } else if (strcmp(key, "enabled") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &enabled); ++ ++ } else if (strcmp(key, "toggle-type") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_type); ++ ++ } else if (strcmp(key, "toggle-state") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_state); ++ ++ } else if (strcmp(key, "children-display") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &children_display); ++ ++ if (strcmp(children_display, "submenu") == 0) ++ *has_submenu = 1; ++ ++ } else if (strcmp(key, "label") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &label); ++ } ++ } while (dbus_message_iter_next(dict)); ++ ++ /* Skip hidden etc items */ ++ if (!label || !visible || !enabled) ++ return 1; ++ ++ /* ++ * 4 characters for checkmark and submenu indicator, ++ * 1 for nul terminator ++ */ ++ if (strlen(label) + 5 > LABEL_MAX) { ++ fprintf(stderr, "Too long menu entry label: %s! Skipping...\n", ++ label); ++ return 1; ++ } ++ ++ if (toggle_type && strcmp(toggle_type, "checkmark") == 0) ++ createmenuitem(mi, itemid, label, toggle_state, *has_submenu); ++ else ++ createmenuitem(mi, itemid, label, -1, *has_submenu); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Error parsing menu data\n"); ++ return r; ++} ++ ++/** ++ * Extracts a menuitem from a DBusMessage ++ * ++ * @param[in] strct ++ * @param[in] mi ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++extract_menuitem(DBusMessageIter *strct, MenuItem *mi) ++{ ++ DBusMessageIter val, dict; ++ dbus_int32_t itemid; ++ int has_submenu = 0; ++ int r; ++ ++ dbus_message_iter_recurse(strct, &val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &itemid); ++ ++ if (!dbus_message_iter_next(&val) || ++ dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&val, &dict); ++ if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = read_dict(&dict, itemid, mi, &has_submenu); ++ if (r < 0) { ++ goto fail; ++ ++ } else if (r == 0 && has_submenu) { ++ dbus_message_iter_next(&val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) ++ goto fail; ++ r = extract_menu(&val, &mi->submenu); ++ if (r < 0) ++ goto fail; ++ } ++ ++ return r; ++ ++fail: ++ return r; ++} ++ ++static int ++extract_menu(DBusMessageIter *av, struct wl_array *layout_node) ++{ ++ DBusMessageIter variant, menuitem; ++ MenuItem *mi; ++ int r; ++ ++ dbus_message_iter_recurse(av, &variant); ++ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ ++ do { ++ dbus_message_iter_recurse(&variant, &menuitem); ++ if (dbus_message_iter_get_arg_type(&menuitem) != ++ DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = extract_menuitem(&menuitem, mi); ++ if (r < 0) ++ goto fail; ++ else if (r == 0) { ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ } ++ /* r > 0: no action was performed on mi */ ++ } while (dbus_message_iter_next(&variant)); ++ ++ return 0; ++ ++fail: ++ return r; ++} ++ ++static void ++layout_ready(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, strct; ++ dbus_uint32_t revision; ++ int r; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { ++ r = -1; ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&iter, &revision); ++ ++ if (!dbus_message_iter_next(&iter) || ++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&iter, &strct); ++ ++ /* ++ * id 0 is the root, which contains nothing of interest. ++ * Traverse past it. ++ */ ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_INT32 || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ ++ /* Root traversed over, extract the menu */ ++ wl_array_init(&menu->layout); ++ r = extract_menu(&strct, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ r = real_show_menu(menu, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ menu_destroy(menu); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static int ++request_layout(Menu *menu) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter strings = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusPendingCall *pending = NULL; ++ dbus_int32_t parentid, depth; ++ int r; ++ ++ parentid = 0; ++ depth = -1; ++ ++ /* menu busobj request answer didn't arrive yet. */ ++ if (!menu->busobj) { ++ r = -1; ++ goto fail; ++ } ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "GetLayout"); ++ if (!msg) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, ++ &parentid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &depth) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &strings) || ++ !dbus_message_iter_close_container(&iter, &strings)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, layout_ready, menu, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ dbus_message_iter_abandon_container_if_open(&iter, &strings); ++ if (msg) ++ dbus_message_unref(msg); ++ menu_destroy(menu); ++ return r; ++} ++ ++static void ++about_to_show_handle(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (request_layout(menu) < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++ menu_destroy(menu); ++} ++ ++void ++menu_show(DBusConnection *conn, struct wl_event_loop *loop, const char *busname, ++ const char *busobj, const char **menucmd) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ Menu *menu = NULL; ++ char *busname_dup = NULL, *busobj_dup = NULL; ++ dbus_int32_t parentid = 0; ++ ++ menu = calloc(1, sizeof(Menu)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!menu || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ menu->conn = conn; ++ menu->loop = loop; ++ menu->busname = busname_dup; ++ menu->busobj = busobj_dup; ++ menu->menucmd = menucmd; ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "AboutToShow"); ++ if (!msg) ++ goto fail; ++ ++ if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, &parentid, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, about_to_show_handle, menu, ++ NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (pending) ++ dbus_pending_call_unref(pending); ++ if (msg) ++ dbus_message_unref(msg); ++ free(menu); ++} +diff --git a/systray/menu.h b/systray/menu.h +new file mode 100644 +index 0000000..7f48ada +--- /dev/null ++++ b/systray/menu.h +@@ -0,0 +1,11 @@ ++#ifndef MENU_H ++#define MENU_H ++ ++#include ++#include ++ ++/* The menu is built on demand and not kept around */ ++void menu_show (DBusConnection *conn, struct wl_event_loop *loop, ++ const char *busname, const char *busobj, const char **menucmd); ++ ++#endif /* MENU_H */ +diff --git a/systray/tray.c b/systray/tray.c +new file mode 100644 +index 0000000..7f9b1b0 +--- /dev/null ++++ b/systray/tray.c +@@ -0,0 +1,237 @@ ++#include "tray.h" ++ ++#include "icon.h" ++#include "item.h" ++#include "menu.h" ++#include "watcher.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#define PIXMAN_COLOR(hex) \ ++ { .red = ((hex >> 24) & 0xff) * 0x101, \ ++ .green = ((hex >> 16) & 0xff) * 0x101, \ ++ .blue = ((hex >> 8) & 0xff) * 0x101, \ ++ .alpha = (hex & 0xff) * 0x101 } ++ ++static Watcher * ++tray_get_watcher(const Tray *tray) ++{ ++ if (!tray) ++ return NULL; ++ ++ return tray->watcher; ++} ++ ++static pixman_image_t * ++createcanvas(int width, int height, int bgcolor) ++{ ++ pixman_image_t *src, *dest; ++ pixman_color_t bgcolor_pix = PIXMAN_COLOR(bgcolor); ++ ++ dest = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, ++ 0); ++ src = pixman_image_create_solid_fill(&bgcolor_pix); ++ ++ pixman_image_composite32(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0, ++ 0, width, height); ++ ++ pixman_image_unref(src); ++ return dest; ++} ++ ++void ++tray_update(Tray *tray) ++{ ++ Item *item; ++ Watcher *watcher; ++ int icon_size, i = 0, canvas_width, canvas_height, n_items, spacing; ++ pixman_image_t *canvas = NULL, *img; ++ ++ watcher = tray_get_watcher(tray); ++ n_items = watcher_get_n_items(watcher); ++ ++ if (!n_items) { ++ if (tray->image) { ++ pixman_image_unref(tray->image); ++ tray->image = NULL; ++ } ++ tray->cb(tray->monitor); ++ return; ++ } ++ ++ icon_size = tray->height; ++ spacing = tray->spacing; ++ canvas_width = n_items * (icon_size + spacing) + spacing; ++ canvas_height = tray->height; ++ ++ canvas = createcanvas(canvas_width, canvas_height, tray->scheme[1]); ++ if (!canvas) ++ goto fail; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ int slot_x_start = spacing + i * (icon_size + spacing); ++ int slot_x_end = slot_x_start + icon_size + spacing; ++ int slot_x_width = slot_x_end - slot_x_start; ++ ++ int slot_y_start = 0; ++ int slot_y_end = canvas_height; ++ int slot_y_width = slot_y_end - slot_y_start; ++ ++ if (item->icon) { ++ /* Real icon */ ++ img = item->icon->img; ++ if (resize_image(img, icon_size, icon_size) < 0) ++ goto fail; ++ pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, ++ canvas, 0, 0, 0, 0, ++ slot_x_start, 0, canvas_width, ++ canvas_height); ++ ++ } else if (item->appid) { ++ /* Font glyph alpha mask */ ++ const struct fcft_glyph *g; ++ int pen_y, pen_x; ++ pixman_color_t fg_color = PIXMAN_COLOR(tray->scheme[0]); ++ pixman_image_t *fg; ++ ++ if (item->fallback_icon) { ++ g = item->fallback_icon; ++ } else { ++ g = createfallbackicon(item->appid, ++ item->fgcolor, ++ tray->font); ++ if (!g) ++ goto fail; ++ item->fallback_icon = g; ++ } ++ ++ pen_x = slot_x_start + (slot_x_width - g->width) / 2; ++ pen_y = slot_y_start + (slot_y_width - g->height) / 2; ++ ++ fg = pixman_image_create_solid_fill(&fg_color); ++ pixman_image_composite32(PIXMAN_OP_OVER, fg, g->pix, ++ canvas, 0, 0, 0, 0, pen_x, ++ pen_y, canvas_width, ++ canvas_height); ++ pixman_image_unref(fg); ++ } ++ i++; ++ } ++ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ tray->image = canvas; ++ tray->cb(tray->monitor); ++ ++ return; ++ ++fail: ++ if (canvas) ++ pixman_image_unref(canvas); ++ return; ++} ++ ++void ++destroytray(Tray *tray) ++{ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ if (tray->font) ++ fcft_destroy(tray->font); ++ free(tray); ++} ++ ++Tray * ++createtray(void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher) ++{ ++ Tray *tray = NULL; ++ char fontattrs_my[128]; ++ struct fcft_font *font = NULL; ++ ++ sprintf(fontattrs_my, "%s:%s", fontattrs, "weight:bold"); ++ ++ tray = calloc(1, sizeof(Tray)); ++ font = fcft_from_name(1, fonts, fontattrs_my); ++ if (!tray || !font) ++ goto fail; ++ ++ tray->monitor = monitor; ++ tray->height = height; ++ tray->spacing = spacing; ++ tray->scheme = colorscheme; ++ tray->cb = cb; ++ tray->watcher = watcher; ++ tray->font = font; ++ ++ return tray; ++ ++fail: ++ if (font) ++ fcft_destroy(font); ++ free(tray); ++ return NULL; ++} ++ ++int ++tray_get_width(const Tray *tray) ++{ ++ if (tray && tray->image) ++ return pixman_image_get_width(tray->image); ++ else ++ return 0; ++} ++ ++int ++tray_get_icon_width(const Tray *tray) ++{ ++ if (!tray) ++ return 0; ++ ++ return tray->height; ++} ++ ++void ++tray_rightclicked(Tray *tray, unsigned int index, const char **menucmd) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ menu_show(watcher->conn, watcher->loop, item->busname, ++ item->menu_busobj, menucmd); ++ return; ++ } ++ count++; ++ } ++} ++ ++void ++tray_leftclicked(Tray *tray, unsigned int index) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ item_activate(item); ++ return; ++ } ++ count++; ++ } ++} +diff --git a/systray/tray.h b/systray/tray.h +new file mode 100644 +index 0000000..af4e5e3 +--- /dev/null ++++ b/systray/tray.h +@@ -0,0 +1,37 @@ ++#ifndef TRAY_H ++#define TRAY_H ++ ++#include "watcher.h" ++ ++#include ++#include ++ ++#include ++ ++typedef void (*TrayNotifyCb)(void *data); ++ ++typedef struct { ++ pixman_image_t *image; ++ struct fcft_font *font; ++ uint32_t *scheme; ++ TrayNotifyCb cb; ++ Watcher *watcher; ++ void *monitor; ++ int height; ++ int spacing; ++ ++ struct wl_list link; ++} Tray; ++ ++Tray *createtray (void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher); ++void destroytray (Tray *tray); ++ ++int tray_get_width (const Tray *tray); ++int tray_get_icon_width (const Tray *tray); ++void tray_update (Tray *tray); ++void tray_leftclicked (Tray *tray, unsigned int index); ++void tray_rightclicked (Tray *tray, unsigned int index, const char **menucmd); ++ ++#endif /* TRAY_H */ +diff --git a/systray/watcher.c b/systray/watcher.c +new file mode 100644 +index 0000000..8dd84b9 +--- /dev/null ++++ b/systray/watcher.c +@@ -0,0 +1,551 @@ ++#include "watcher.h" ++ ++#include "item.h" ++#include "tray.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++static const char *const match_rule = ++ "type='signal'," ++ "interface='" DBUS_INTERFACE_DBUS ++ "'," ++ "member='NameOwnerChanged'"; ++ ++static const char *const snw_xml = ++ "\n" ++ "\n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ "\n"; ++ ++static void ++unregister_item(Watcher *watcher, Item *item) ++{ ++ wl_list_remove(&item->link); ++ destroyitem(item); ++ ++ watcher_update_trays(watcher); ++} ++ ++static Item * ++item_name_to_ptr(const Watcher *watcher, const char *busname) ++{ ++ Item *item; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!item || !item->busname) ++ return NULL; ++ if (strcmp(item->busname, busname) == 0) ++ return item; ++ } ++ ++ return NULL; ++} ++ ++static DBusHandlerResult ++handle_nameowner_changed(Watcher *watcher, DBusConnection *conn, ++ DBusMessage *msg) ++{ ++ char *name, *old_owner, *new_owner; ++ Item *item; ++ ++ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, ++ DBUS_TYPE_STRING, &old_owner, ++ DBUS_TYPE_STRING, &new_owner, ++ DBUS_TYPE_INVALID)) { ++ return DBUS_HANDLER_RESULT_HANDLED; ++ } ++ ++ if (*new_owner != '\0' || *name == '\0') ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ item = item_name_to_ptr(watcher, name); ++ if (!item) ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ unregister_item(watcher, item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, ++ "NameOwnerChanged")) ++ return handle_nameowner_changed(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static DBusHandlerResult ++respond_register_item(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED; ++ ++ DBusMessage *reply = NULL; ++ Item *item; ++ const char *sender, *param, *busobj, *registree_name; ++ ++ if (!(sender = dbus_message_get_sender(msg)) || ++ !dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, ¶m, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, ++ "Malformed message"); ++ goto send; ++ } ++ ++ switch (*param) { ++ case '/': ++ registree_name = sender; ++ busobj = param; ++ break; ++ case ':': ++ registree_name = param; ++ busobj = SNI_OPATH; ++ break; ++ default: ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Bad argument: \"%s\"", ++ param); ++ goto send; ++ } ++ ++ if (*registree_name != ':' || ++ !dbus_validate_bus_name(registree_name, NULL)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Invalid busname %s", ++ registree_name); ++ goto send; ++ } ++ ++ if (item_name_to_ptr(watcher, registree_name)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "%s already tracked", ++ registree_name); ++ goto send; ++ } ++ ++ item = createitem(registree_name, busobj, watcher); ++ wl_list_insert(&watcher->items, &item->link); ++ watcher_update_trays(watcher); ++ ++ reply = dbus_message_new_method_return(msg); ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ res = DBUS_HANDLER_RESULT_NEED_MEMORY; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return res; ++} ++ ++static int ++get_registered_items(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter names = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ Item *item; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &names)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!dbus_message_iter_append_basic(&names, DBUS_TYPE_STRING, ++ &item->busname)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ } ++ ++ dbus_message_iter_close_container(iter, &names); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &names); ++ return r; ++} ++ ++static int ++get_registered_items_variant(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", ++ &variant) || ++ get_registered_items(watcher, &variant) < 0) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_isregistered(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_bool_t is_registered = TRUE; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_BOOLEAN_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, ++ &is_registered)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_version(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_int32_t protovers = 0; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_INT32_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32, ++ &protovers)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static DBusHandlerResult ++respond_get_prop(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *iface, *prop; ++ ++ if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, err.name, err.message); ++ dbus_error_free(&err); ++ goto send; ++ } ++ ++ if (strcmp(iface, SNW_IFACE) != 0) { ++ reply = dbus_message_new_error_printf( ++ msg, DBUS_ERROR_UNKNOWN_INTERFACE, ++ "Unknown interface \"%s\"", iface); ++ goto send; ++ } ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (strcmp(prop, "ProtocolVersion") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_version(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "IsStatusNotifierHostRegistered") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_isregistered(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "RegisteredStatusNotifierItems") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_registered_items_variant(watcher, &iter) < 0) ++ goto fail; ++ ++ } else { ++ dbus_message_unref(reply); ++ reply = dbus_message_new_error_printf( ++ reply, DBUS_ERROR_UNKNOWN_PROPERTY, ++ "Property \"%s\" does not exist", prop); ++ } ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ goto fail; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_all_props(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ DBusMessageIter array = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter dict = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *prop; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ dbus_message_iter_init_append(reply, &iter); ++ ++ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", ++ &array)) ++ goto fail; ++ ++ prop = "ProtocolVersion"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_version(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "IsStatusNotifierHostRegistered"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_isregistered(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "RegisteredStatusNotifierItems"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_registered_items_variant(watcher, &dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ if (!dbus_message_iter_close_container(&iter, &array) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&array, &dict); ++ dbus_message_iter_abandon_container_if_open(&iter, &array); ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_introspect(DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &snw_xml, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++snw_message_handler(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, ++ "Introspect")) ++ return respond_introspect(conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "GetAll")) ++ return respond_all_props(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "Get")) ++ return respond_get_prop(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, SNW_IFACE, ++ "RegisterStatusNotifierItem")) ++ return respond_register_item(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static const DBusObjectPathVTable snw_vtable = { .message_function = ++ snw_message_handler }; ++ ++void ++watcher_start(Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ int r, flags; ++ ++ wl_list_init(&watcher->items); ++ wl_list_init(&watcher->trays); ++ watcher->conn = conn; ++ watcher->loop = loop; ++ ++ flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; ++ r = dbus_bus_request_name(conn, SNW_NAME, ++ flags, NULL); ++ if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) ++ goto fail; ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, watcher, NULL)) { ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ dbus_bus_add_match(conn, match_rule, &err); ++ if (dbus_error_is_set(&err)) { ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ if (!dbus_connection_register_object_path(conn, SNW_OPATH, &snw_vtable, ++ watcher)) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ watcher->running = 1; ++ return; ++ ++fail: ++ fprintf(stderr, "Couldn't start watcher, systray not available\n"); ++ dbus_error_free(&err); ++ return; ++} ++ ++void ++watcher_stop(Watcher *watcher) ++{ ++ dbus_connection_unregister_object_path(watcher->conn, SNW_OPATH); ++ dbus_bus_remove_match(watcher->conn, match_rule, NULL); ++ dbus_connection_remove_filter(watcher->conn, filter_bus, watcher); ++ dbus_bus_release_name(watcher->conn, SNW_NAME, NULL); ++ watcher->running = 0; ++} ++ ++int ++watcher_get_n_items(const Watcher *watcher) ++{ ++ return wl_list_length(&watcher->items); ++} ++ ++void ++watcher_update_trays(Watcher *watcher) ++{ ++ Tray *tray; ++ ++ wl_list_for_each(tray, &watcher->trays, link) ++ tray_update(tray); ++} +diff --git a/systray/watcher.h b/systray/watcher.h +new file mode 100644 +index 0000000..127eb64 +--- /dev/null ++++ b/systray/watcher.h +@@ -0,0 +1,35 @@ ++#ifndef WATCHER_H ++#define WATCHER_H ++ ++#include ++#include ++#include ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierWatcher"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierWatcher" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ */ ++#define SNW_NAME "org.kde.StatusNotifierWatcher" ++#define SNW_OPATH "/StatusNotifierWatcher" ++#define SNW_IFACE "org.kde.StatusNotifierWatcher" ++ ++typedef struct { ++ struct wl_list items; ++ struct wl_list trays; ++ struct wl_event_loop *loop; ++ DBusConnection *conn; ++ int running; ++} Watcher; ++ ++void watcher_start (Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop); ++void watcher_stop (Watcher *watcher); ++ ++int watcher_get_n_items (const Watcher *watcher); ++void watcher_update_trays (Watcher *watcher); ++ ++#endif /* WATCHER_H */ +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/bar/README.md b/dwl/dwl-patches/patches/bar/README.md new file mode 100644 index 0000000..4955753 --- /dev/null +++ b/dwl/dwl-patches/patches/bar/README.md @@ -0,0 +1,34 @@ +### Description + +Add a bar identical to dwm's bar. + +To use a status-bar, you can pass in status text via stdin: +``` +slstatus -s | dwl +``` + +### Dependencies +* tllist (build dependency, required & pulled automatically by fcft) +* fcft +* pixman + +### Download +- [main 2025-07-29](/dwl/dwl-patches/raw/branch/main/patches/bar/bar.patch) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar/bar-0.7.patch) +- [0.6](/dwl/dwl-patches/raw/branch/main/patches/bar/bar-0.6.patch) + +It is required to remove, regenerate or update `config.h` after applying the patch, +since it makes changes to the configuration structure. +For example, in the `pertag` patch, `TAGCOUNT` must be replaced with `LENGTH(tags)`. + +Below is a preview of the patch. + +![bar patch preview](bar.png) + +### Authors +- [sewn](https://codeberg.org/sewn) + +### Credits +- [MadcowOG](https://github.com/MadcowOG) +- [kolumni](https://github.com/kolunmi/dwlb) + diff --git a/dwl/dwl-patches/patches/bar/bar-0.6.patch b/dwl/dwl-patches/patches/bar/bar-0.6.patch new file mode 100644 index 0000000..70c9f12 --- /dev/null +++ b/dwl/dwl-patches/patches/bar/bar-0.6.patch @@ -0,0 +1,1245 @@ +From 85e40afc2ad8acba453ce8c57233542e340c1c2b Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:42:04 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 31 +++- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 442 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 691 insertions(+), 95 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 0d651e7..2a11396 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5d1dc2b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..b06a736 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 145fd01..ab1bc31 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -57,6 +58,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +69,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -75,14 +78,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -96,6 +102,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -185,10 +192,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -196,6 +212,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -207,6 +228,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -249,6 +273,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +313,8 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -309,7 +342,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +363,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +376,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -408,6 +443,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -548,6 +592,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -590,17 +639,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WLR_BUTTON_PRESSED: + cursor_mode = CurPressed; +@@ -608,17 +742,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -689,6 +840,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -704,6 +857,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -714,6 +873,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -743,7 +903,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -980,8 +1140,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1312,6 +1479,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1347,13 +1588,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1364,12 +1605,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1666,7 +1906,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1689,7 +1929,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -1982,46 +2222,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- const char *appid, *title; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- title = client_get_title(c); +- appid = client_get_appid(c); +- printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +- printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} + + void + powermgrsetmode(struct wl_listener *listener, void *data) +@@ -2175,30 +2375,17 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + +- /* Mark stdout as non-blocking to avoid people who does not close stdin +- * nor consumes it in their startup script getting dwl frozen */ +- if (fd_set_nonblock(STDOUT_FILENO) < 0) +- close(STDOUT_FILENO); +- +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2262,7 +2449,7 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2285,7 +2472,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2310,7 +2497,7 @@ setlayout(const Arg *arg) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2383,6 +2570,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2569,6 +2757,11 @@ setup(void) + + wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2593,6 +2786,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2611,6 +2805,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[1024]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2621,7 +2839,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2666,6 +2884,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2694,7 +2920,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2707,7 +2933,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2755,7 +2981,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2855,6 +3081,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2865,12 +3098,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2883,10 +3149,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2899,7 +3165,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2940,6 +3206,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -2948,9 +3215,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3089,10 +3359,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/bar/bar-0.7.patch b/dwl/dwl-patches/patches/bar/bar-0.7.patch new file mode 100644 index 0000000..523ca36 --- /dev/null +++ b/dwl/dwl-patches/patches/bar/bar-0.7.patch @@ -0,0 +1,1245 @@ +From 1431cf1e9e03c8e59050af3b37514a6a2293d71d Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:42:04 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 31 +++- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 442 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 691 insertions(+), 95 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 3358bae..9bc67db 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5d1dc2b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..b06a736 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index a2711f6..ece537a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +59,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -68,6 +70,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -76,14 +79,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -97,6 +103,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -186,10 +193,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -197,6 +213,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -208,6 +229,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -250,6 +274,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -285,6 +316,8 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -313,7 +346,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -335,9 +367,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -346,6 +380,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -413,6 +448,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -553,6 +597,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -595,17 +644,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -613,17 +747,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -697,6 +848,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -712,6 +865,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -722,6 +881,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -751,7 +911,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1022,8 +1182,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1336,6 +1503,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1371,13 +1612,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1388,12 +1629,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1715,7 +1955,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1738,7 +1978,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2032,46 +2272,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- const char *appid, *title; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- title = client_get_title(c); +- appid = client_get_appid(c); +- printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +- printf("%s appid %s\n", m->wlr_output->name, appid ? appid : broken); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} + + void + powermgrsetmode(struct wl_listener *listener, void *data) +@@ -2226,30 +2426,17 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + +- /* Mark stdout as non-blocking to avoid people who does not close stdin +- * nor consumes it in their startup script getting dwl frozen */ +- if (fd_set_nonblock(STDOUT_FILENO) < 0) +- close(STDOUT_FILENO); +- +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2315,7 +2502,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2338,7 +2525,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2363,7 +2550,7 @@ setlayout(const Arg *arg) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2436,6 +2623,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2625,6 +2813,11 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2649,6 +2842,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2667,6 +2861,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[1024]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2677,7 +2895,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2722,6 +2940,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2750,7 +2976,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2763,7 +2989,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2811,7 +3037,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2911,6 +3137,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2921,12 +3154,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2939,10 +3205,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2955,7 +3221,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2996,6 +3262,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3004,9 +3271,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3145,10 +3415,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/bar/bar.patch b/dwl/dwl-patches/patches/bar/bar.patch new file mode 100644 index 0000000..c9527c1 --- /dev/null +++ b/dwl/dwl-patches/patches/bar/bar.patch @@ -0,0 +1,1266 @@ +From 8906a73dbc8996dd1bfff15f5b26aaee6d45fd61 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Tue, 29 Jul 2025 15:21:19 +0300 +Subject: [PATCH] Implement dwm bar clone + +Signed-off-by: sewn +--- + Makefile | 2 +- + config.def.h | 33 ++-- + drwl.h | 311 ++++++++++++++++++++++++++++++++++++ + dwl.c | 441 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 695 insertions(+), 92 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 95c2afa..1b7472d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const int showbar = 1; /* 0 means no bar */ ++static const int topbar = 1; /* 0 means bottom bar */ ++static const char *fonts[] = {"monospace:size=10"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + +-/* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++/* tagging */ ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -127,6 +133,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -170,7 +177,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..90cc35b +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,311 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2025 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 12f441e..bf340d8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +60,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +71,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -77,14 +80,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ + + typedef union { + int i; +@@ -94,6 +100,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -183,10 +190,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -194,6 +210,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -205,6 +226,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -247,6 +271,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +313,8 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -310,7 +343,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +363,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +376,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -406,6 +441,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -521,7 +565,7 @@ arrange(Monitor *m) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ +@@ -576,6 +620,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -618,17 +667,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -636,17 +770,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && ++ (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { ++ cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ arg.ui = 1 << i; ++ } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) ++ click = ClkLtSymbol; ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); ++ if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { ++ b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } +@@ -721,6 +872,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -736,6 +889,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -748,6 +907,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -814,7 +974,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1066,7 +1226,7 @@ createmon(struct wl_listener *listener, void *data) + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; +@@ -1088,8 +1248,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1399,6 +1566,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* draw status first so it can be overdrawn by tags later */ ++ if (m == selmon) { /* status is only drawn on selected monitor */ ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ ++ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ ++ if ((w = m->b.width - tw - x) > m->b.height) { ++ if (c) { ++ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ wlr_scene_buffer_set_dest_size(m->scene_buffer, ++ m->b.real_width, m->b.real_height); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1433,13 +1674,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1450,12 +1691,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1768,7 +2008,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1791,7 +2031,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2084,44 +2324,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} +- + void + powermgrsetmode(struct wl_listener *listener, void *data) + { +@@ -2250,22 +2452,14 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + + /* Mark stdout as non-blocking to avoid the startup script +@@ -2275,7 +2469,7 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2341,7 +2535,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2364,7 +2558,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2376,9 +2570,9 @@ setlayout(const Arg *arg) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; +- strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2451,6 +2645,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2645,6 +2840,11 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2669,6 +2869,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2687,6 +2888,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[256]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2697,7 +2922,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2742,6 +2967,14 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2770,7 +3003,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2783,7 +3016,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2831,7 +3064,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2931,6 +3164,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2941,12 +3181,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ m->lrpad = m->drw->font->height; ++ m->b.height = m->drw->font->height + 2; ++ m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2959,10 +3232,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2975,7 +3248,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -3016,6 +3289,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3024,9 +3298,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3159,10 +3436,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.50.0 + diff --git a/dwl/dwl-patches/patches/bar/bar.png b/dwl/dwl-patches/patches/bar/bar.png new file mode 100644 index 0000000..276256b Binary files /dev/null and b/dwl/dwl-patches/patches/bar/bar.png differ diff --git a/dwl/dwl-patches/patches/barborder/README.md b/dwl/dwl-patches/patches/barborder/README.md new file mode 100644 index 0000000..6eef3ba --- /dev/null +++ b/dwl/dwl-patches/patches/barborder/README.md @@ -0,0 +1,10 @@ +### Description + +Add a border around the [bar](/dwl/dwl-patches/wiki/bar) similar to how a client is given a border. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barborder/barborder.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl/dwl-patches/patches/barborder/barborder.patch b/dwl/dwl-patches/patches/barborder/barborder.patch new file mode 100644 index 0000000..106a2ac --- /dev/null +++ b/dwl/dwl-patches/patches/barborder/barborder.patch @@ -0,0 +1,136 @@ +From 7d95ce0fba8f172748bbd71b4c03ce12acd54eea Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 14:11:37 +0300 +Subject: [PATCH] add border to bar + +--- + config.def.h | 3 ++- + dwl.c | 32 +++++++++++++++++++------------- + 2 files changed, 21 insertions(+), 14 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..4763482 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +-static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int borderpx = 1; /* border pixel of windows & bar */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; +@@ -18,6 +18,7 @@ static uint32_t colors[][3] = { + [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, + [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, + [SchemeUrg] = { 0, 0, 0x770000ff }, ++ [SchemeBar] = { 0, 0, 0x557700ff }, + }; + + /* tagging - TAGCOUNT must be no greater than 31 */ +diff --git a/dwl.c b/dwl.c +index ece537a..c637da4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -85,7 +85,7 @@ + #define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ +-enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ ++enum { SchemeNorm, SchemeSel, SchemeUrg, SchemeBar }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +@@ -750,7 +750,7 @@ buttonpress(struct wl_listener *listener, void *data) + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { +- cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ cx = (cursor->x - selmon->m.x - borderpx) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -1506,10 +1506,12 @@ dirtomon(enum wlr_direction dir) + void + drawbar(Monitor *m) + { +- int x, w, tw = 0; ++ int x, y = borderpx, w, tw = 0; ++ int mh = m->b.height - borderpx * 2, mw = m->b.width - borderpx * 2; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; ++ uint32_t borderscm[] = { colors[SchemeBar][ColBorder] }; + Client *c; + Buffer *buf; + +@@ -1518,11 +1520,15 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ drwl_setscheme(m->drw, borderscm); ++ drwl_rect(m->drw, 0, 0, m->b.width, m->b.height, 1, 0); ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ drwl_text(m->drw, borderpx + mw - tw, y, tw, mh, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { +@@ -1532,31 +1538,31 @@ drawbar(Monitor *m) + if (c->isurgent) + urg |= c->tags; + } +- x = 0; ++ x = borderpx; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ drwl_rect(m->drw, x + boxs, y + boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); +- x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); ++ x = drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->b.width - tw - x) > m->b.height) { ++ if ((w = mw - tw - x + borderpx) > mh) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ drwl_text(m->drw, x, y, w, mh, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ drwl_rect(m->drw, x + boxs, y + boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); +- drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ drwl_rect(m->drw, x, y, w, mh, 1, 1); + } + } + +@@ -3183,7 +3189,7 @@ updatebar(Monitor *m) + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; +- m->b.height = m->drw->font->height + 2; ++ m->b.height = m->drw->font->height + 2 + borderpx * 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); + } + +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/barcolors/README.md b/dwl/dwl-patches/patches/barcolors/README.md new file mode 100644 index 0000000..640961b --- /dev/null +++ b/dwl/dwl-patches/patches/barcolors/README.md @@ -0,0 +1,9 @@ +### Description +Add support for colored status text to the [bar](/dwl/dwl-patches/src/branch/main/patches/bar). Text can be colored in the same manner as with dwlb, namely by wrapping it between `^fg(color)` and `^fg()` or `^bg(color)` and `^bg()`, where `color` is a 6-digit hexadecimal value. + +### Download +- [git branch](/kerberoge/dwl/src/branch/barcolors) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barcolors/barcolors.patch) + +### Authors +- [kerberoge](https://codeberg.org/kerberoge) diff --git a/dwl/dwl-patches/patches/barcolors/barcolors.patch b/dwl/dwl-patches/patches/barcolors/barcolors.patch new file mode 100644 index 0000000..b95f78e --- /dev/null +++ b/dwl/dwl-patches/patches/barcolors/barcolors.patch @@ -0,0 +1,144 @@ +From d2b529d9ebee6b2e625afd5c89c2ede5bb0ca91b Mon Sep 17 00:00:00 2001 +From: Kerberoge +Date: Sun, 25 Aug 2024 22:41:55 +0200 +Subject: [PATCH 1/1] updated barcolors + +--- + dwl.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 87 insertions(+), 6 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ece537a..6663399 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -83,6 +83,7 @@ + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + #define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) ++#define PREFIX(str, prefix) !strncmp(str, prefix, strlen(prefix)) + + /* enums */ + enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +@@ -318,6 +319,7 @@ static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void drawbar(Monitor *m); + static void drawbars(void); ++static int drawstatus(Monitor *m); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -448,7 +450,7 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + +-static char stext[256]; ++static char stext[512]; + static struct wl_event_source *status_event_source; + + static const struct wlr_buffer_impl buffer_impl = { +@@ -1519,11 +1521,8 @@ drawbar(Monitor *m) + return; + + /* draw status first so it can be overdrawn by tags later */ +- if (m == selmon) { /* status is only drawn on selected monitor */ +- drwl_setscheme(m->drw, colors[SchemeNorm]); +- tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); +- } ++ if (m == selmon) /* status is only drawn on selected monitor */ ++ tw = drawstatus(m); + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) +@@ -1577,6 +1576,88 @@ drawbars(void) + drawbar(m); + } + ++int ++drawstatus(Monitor *m) ++{ ++ int x, tw, iw; ++ char rstext[512] = ""; ++ char *p, *argstart, *argend, *itext; ++ uint32_t scheme[3], *color; ++ ++ /* calculate real width of stext */ ++ for (p = stext; *p; p++) { ++ if (PREFIX(p, "^^")) { ++ strncat(rstext, p, 2); ++ p++; ++ } else if (PREFIX(p, "^fg(") || PREFIX(p, "^bg(")) { ++ argend = strchr(p, ')'); ++ if (!argend) { /* ignore this command */ ++ argstart = strchr(p, '(') + 1; ++ strncat(rstext, p, argstart - p); ++ p = argstart - 1; ++ } else { ++ p = argend; ++ } ++ } else { ++ strncat(rstext, p, 1); ++ } ++ } ++ tw = TEXTW(m, rstext) - m->lrpad; ++ ++ x = m->b.width - tw; ++ itext = stext; ++ scheme[0] = colors[SchemeNorm][0]; ++ scheme[1] = colors[SchemeNorm][1]; ++ drwl_setscheme(m->drw, scheme); ++ for (p = stext; *p; p++) { ++ if (PREFIX(p, "^^")) { ++ p++; ++ } else if (PREFIX(p, "^fg(") || PREFIX(p, "^bg(")) { ++ argstart = strchr(p, '(') + 1; ++ argend = strchr(argstart, ')'); ++ if (!argend) { /* ignore this command */ ++ p = argstart - 1; ++ continue; ++ } ++ ++ *p = '\0'; ++ iw = TEXTW(m, itext) - m->lrpad; ++ if (*itext) /* only draw text if there is something to draw */ ++ x = drwl_text(m->drw, x, 0, iw, m->b.height, 0, itext, 0); ++ *p = '^'; ++ ++ if (PREFIX(p, "^fg(")) ++ color = &scheme[0]; ++ else ++ color = &scheme[1]; ++ ++ if (argend != argstart) { ++ *argend = '\0'; ++ *color = strtoul(argstart, NULL, 16); ++ *color = *color << 8 | 0xff; /* add alpha channel */ ++ *argend = ')'; ++ } else { ++ *color = 0; /* reset */ ++ } ++ ++ /* reset color back to normal if none was provided */ ++ if (!scheme[0]) ++ scheme[0] = colors[SchemeNorm][0]; ++ if (!scheme[1]) ++ scheme[1] = colors[SchemeNorm][1]; ++ ++ itext = argend + 1; ++ drwl_setscheme(m->drw, scheme); ++ p = argend; ++ } ++ } ++ iw = TEXTW(m, itext) - m->lrpad; ++ if (*itext) ++ drwl_text(m->drw, x, 0, iw, m->b.height, 0, itext, 0); ++ ++ return tw; ++} ++ + void + focusclient(Client *c, int lift) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/barpadding/README.md b/dwl/dwl-patches/patches/barpadding/README.md new file mode 100644 index 0000000..1d0f17b --- /dev/null +++ b/dwl/dwl-patches/patches/barpadding/README.md @@ -0,0 +1,10 @@ +### Description + +Add vertical and horizontal space between the [bar](/dwl/dwl-patches/wiki/bar) and the edge of the screen. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/barpadding/barpadding.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl/dwl-patches/patches/barpadding/barpadding.patch b/dwl/dwl-patches/patches/barpadding/barpadding.patch new file mode 100644 index 0000000..e5cc185 --- /dev/null +++ b/dwl/dwl-patches/patches/barpadding/barpadding.patch @@ -0,0 +1,73 @@ +From f01cea73042155e856b2f41452724fe5c895eee4 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 23 Aug 2024 09:59:03 +0300 +Subject: [PATCH] add vertical and horizontal spacing to bar + +https://dwm.suckless.org/patches/barpadding/ +--- + config.def.h | 2 ++ + dwl.c | 14 +++++++------- + 2 files changed, 9 insertions(+), 7 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..756b1ae 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -9,6 +9,8 @@ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ ++static const int vertpad = 10; /* vertical padding of bar */ ++static const int sidepad = 10; /* horizontal padding of bar */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index ece537a..380549a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -598,8 +598,8 @@ arrangelayers(Monitor *m) + return; + + if (m->scene_buffer->node.enabled) { +- usable_area.height -= m->b.real_height; +- usable_area.y += topbar ? m->b.real_height : 0; ++ usable_area.height -= m->b.real_height + vertpad; ++ usable_area.y += topbar ? m->b.real_height + vertpad : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ +@@ -750,7 +750,7 @@ buttonpress(struct wl_listener *listener, void *data) + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { +- cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ cx = (cursor->x - selmon->m.x - sidepad) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -1562,8 +1562,8 @@ drawbar(Monitor *m) + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); +- wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, +- m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x + sidepad, ++ m->m.y + (topbar ? vertpad : m->m.height - m->b.real_height - vertpad)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); + } +@@ -3162,8 +3162,8 @@ updatebar(Monitor *m) + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); +- m->b.width = rw; +- m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ m->b.width = rw - (2 * sidepad); ++ m->b.real_width = (int)((float)rw / m->wlr_output->scale) - (2 * sidepad); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/bartruecenteredtitle/README.md b/dwl/dwl-patches/patches/bartruecenteredtitle/README.md new file mode 100644 index 0000000..94bfcd9 --- /dev/null +++ b/dwl/dwl-patches/patches/bartruecenteredtitle/README.md @@ -0,0 +1,11 @@ +### Description +A homegrown port of dwm's _truecenteredtitle_ patch, with the addition of a config option to toggle its effects.
Requires [the bar patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar) to be applied beforehand. + +![](centeredtitle.webp) + +### Download +- [v0.7/v0.6](/dwl/dwl-patches/raw/branch/main/patches/bartruecenteredtitle/bartruecenteredtitle.patch)
Works on both v0.7 and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) +- [sewn](https://codeberg.org/sewn) diff --git a/dwl/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch b/dwl/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch new file mode 100644 index 0000000..ce02a51 --- /dev/null +++ b/dwl/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch @@ -0,0 +1,46 @@ +From 17501c9f28226b1f332d6842be0d7f50ba618a29 Mon Sep 17 00:00:00 2001 +From: moonsabre +Date: Fri, 14 Mar 2025 16:04:25 -0700 +Subject: [PATCH] Bar title centering parameter. + +--- + config.def.h | 1 + + dwl.c | 9 +++++++-- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..8ac3a8b 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -9,6 +9,7 @@ static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ ++static const int centeredtitle = 1; /* 1 means centered title */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index ece537a..9eb816b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1551,9 +1551,14 @@ drawbar(Monitor *m) + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); +- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); ++ tw = TEXTW(selmon, client_get_title(c)); ++ drwl_text(m->drw, x, 0, w, m->b.height, ++ !centeredtitle || tw > w ? m->lrpad / 2 : (w - tw) / 2, ++ client_get_title(c), 0); + if (c && c->isfloating) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ drwl_rect(m->drw, ++ !centeredtitle || tw > w ? x + boxs : x + ((w - tw) / 2 - boxs * 8), ++ boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp b/dwl/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp new file mode 100644 index 0000000..b1ad696 Binary files /dev/null and b/dwl/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp differ diff --git a/dwl/dwl-patches/patches/better-resize/README.md b/dwl/dwl-patches/patches/better-resize/README.md new file mode 100644 index 0000000..9c43862 --- /dev/null +++ b/dwl/dwl-patches/patches/better-resize/README.md @@ -0,0 +1,48 @@ +### Description +This patch allows you to configure window resizing more flexibly. +It introduces three options with the following possible values: + +- warp_cursor: +``` + 0 - the mouse cursor remains in its original position at the start of the resize. + 1 - the cursor is automatically warped to the selected corner before resizing begins. +``` + +- lock_cursor: +``` + 0 - the cursor can move freely during the resize. + 1 - the cursor position is completely frozen for the entire duration of the resize. +``` + +- resize_corner: +``` + 0: top-left + 1: top-right + 2: bottom-left + 3: bottom-right + 4: selects the corner based on the current mouse quadrant +``` + +### Demos +All demos below use resize_corner = 4: +| no warp + no lock | warp + lock | +|-|-| +| ![demo-nowarp-nolock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-nowarp-nolock.gif?raw=true) | ![demo-warp-lock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-warp-lock.gif?raw=true) | + +| no warp + lock | warp + no lock | +|-|-| +| ![demo-nowarp-lock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-nowarp-lock.gif?raw=true) | ![demo-warp-nolock](https://github.com/mmistika/dwl-better-resize/blob/main/demo/demo-warp-nolock.gif?raw=true) | + +### Known Issues (warp + lock) +The combination of warp_cursor and lock_cursor is not recommended without outer gaps. +If the selected resize corner aligns exactly with a screen corner, the cursor gets locked there and cannot be moved outward, so resizing only works inward. +To resize outward, you must restart the resize operation with the cursor positioned somewhere away from the screen corner. +This happens because the locked cursor cannot move past the screen edge, and therefore cannot generate a non-zero delta to resize outward. + +On multihead setups, if the resize corner is near another screen, the window may switch monitors upon completion of the resize. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/better-resize/better-resize-0.7.patch) + +### Authors +- [mmistika](https://codeberg.org/mmistika) diff --git a/dwl/dwl-patches/patches/better-resize/better-resize-0.7.patch b/dwl/dwl-patches/patches/better-resize/better-resize-0.7.patch new file mode 100644 index 0000000..1269800 --- /dev/null +++ b/dwl/dwl-patches/patches/better-resize/better-resize-0.7.patch @@ -0,0 +1,107 @@ +From f109808140cd6323b3a100663a10e048ae32e3a0 Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Thu, 17 Jul 2025 11:59:18 +0200 +Subject: [PATCH] Add configurable window resize + +Signed-off-by: mmistika +--- + config.def.h | 12 ++++++++++++ + dwl.c | 45 +++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 49 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..e404549 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* window resizing */ ++/* resize_corner: ++ * 0: top-left ++ * 1: top-right ++ * 2: bottom-left ++ * 3: bottom-right ++ * 4: closest to the cursor ++ */ ++static const int resize_corner = 4; ++static const int warp_cursor = 1; /* 1: warp to corner, 0: don’t warp */ ++static const int lock_cursor = 0; /* 1: lock cursor, 0: don't lock */ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index c717c1d..aacd074 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -407,6 +407,7 @@ static KeyboardGroup *kb_group; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ ++static int rzcorner; + + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; +@@ -1873,8 +1874,24 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ int cdx = (int)round(cursor->x) - grabcx; ++ int cdy = (int)round(cursor->y) - grabcy; ++ ++ const struct wlr_box box = { ++ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), ++ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), ++ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), ++ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy) ++ }; ++ resize(grabc, box, 1); ++ ++ if (!lock_cursor) { ++ grabcx += cdx; ++ grabcy += cdy; ++ } else { ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ + return; + } + +@@ -1920,12 +1937,24 @@ moveresize(const Arg *arg) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ const char *cursors[] = { "nw-resize", "ne-resize", "sw-resize", "se-resize" }; ++ ++ rzcorner = resize_corner; ++ grabcx = (int)round(cursor->x); ++ grabcy = (int)round(cursor->y); ++ ++ if (rzcorner == 4) ++ /* identify the closest corner index */ ++ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 : 1) ++ + (grabcy - grabc->geom.y < grabc->geom.y + grabc->geom.height - grabcy ? 0 : 2); ++ ++ if (warp_cursor) { ++ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; ++ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height : grabc->geom.y; ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]); + break; + } + } +-- +2.50.1 + diff --git a/dwl/dwl-patches/patches/borders/README.md b/dwl/dwl-patches/patches/borders/README.md new file mode 100644 index 0000000..b046307 --- /dev/null +++ b/dwl/dwl-patches/patches/borders/README.md @@ -0,0 +1,37 @@ +### Description +Adds 2 more borders to each side (top, bottom, left, right) of every window. + + + +
+Preview +
+With the following config:
+
+```c
+static const unsigned int borderpx         = 9;  /* border pixel of windows */
+static const unsigned int borderspx        = 3;  /* width of the border that start from outside the windows */
+static const unsigned int borderepx        = 3;  /* width of the border that start from inside the windows */
+```
+
+and `border_color_type` set to `BrdOriginal`:
+
+
+and `border_color_type` set to `BrdStart`:
+
+
+and `border_color_type` set to `BrdEnd`:
+
+
+and `border_color_type` set to `BrdStartEnd`:
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/borders) +- [2024-06-04](https://codeberg.org/dwl/dwl-patches/raw/commit/1a6825f2b8cd23044312c8040d0bf63ee7f85bc5/patches/borders/borders.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/borders/borders.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/borders/borders.patch b/dwl/dwl-patches/patches/borders/borders.patch new file mode 100644 index 0000000..c8cc847 --- /dev/null +++ b/dwl/dwl-patches/patches/borders/borders.patch @@ -0,0 +1,213 @@ +From b12cfff672f0705d8259cf26b3a574faa5ca43ae Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 4 Jun 2024 16:02:25 -0500 +Subject: [PATCH] implement borders patch + +tihs patch adds 2 extra borders relative to the client, they don't +change the size of the client +--- + client.h | 16 +++++++++++++--- + config.def.h | 8 ++++++++ + dwl.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++---- + 3 files changed, 70 insertions(+), 7 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..33fd579 100644 +--- a/client.h ++++ b/client.h +@@ -325,11 +325,21 @@ client_send_close(Client *c) + } + + static inline void +-client_set_border_color(Client *c, const float color[static 4]) ++client_set_border_color(Client *c, const float color[static 4], const float colors[static 4], const float colore[static 4]) + { + int i; +- for (i = 0; i < 4; i++) +- wlr_scene_rect_set_color(c->border[i], color); ++ for (i = 0; i < 4; i++) { ++ if (border_color_type == BrdOriginal) { ++ wlr_scene_rect_set_color(c->border[i], color); ++ } else if (border_color_type == BrdStart) { ++ wlr_scene_rect_set_color(c->borders[i], colors); ++ } else if (border_color_type == BrdEnd) { ++ wlr_scene_rect_set_color(c->bordere[i], colore); ++ } else if (border_color_type == BrdStartEnd) { ++ wlr_scene_rect_set_color(c->borders[i], colors); ++ wlr_scene_rect_set_color(c->bordere[i], colore); ++ } ++ } + } + + static inline void +diff --git a/config.def.h b/config.def.h +index 8847e58..2d6bbe5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,8 +7,16 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int borderspx = 0; /* width of the border that start from outside the windows */ ++static const unsigned int borderepx = 0; /* width of the border that start from inside the windows */ ++static const unsigned int borderspx_offset = 0; /* offset of the border that start from outside the windows */ ++static const unsigned int borderepx_negative_offset = 0; /* offset of the border that start from inside the windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); ++static const float borderscolor[] = COLOR(0x444444ff); /* color of the border that start from outside the windows */ ++static const float borderecolor[] = COLOR(0x444444ff); /* color of the border that start from inside the windows */ ++static const int border_color_type = BrdOriginal; /* borders to be colored (focuscolor, urgentcolor) */ ++static const int borders_only_floating = 0; + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +diff --git a/dwl.c b/dwl.c +index bf763df..303832a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -86,6 +86,7 @@ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif ++enum { BrdOriginal, BrdStart, BrdEnd, BrdStartEnd }; + + typedef union { + int i; +@@ -109,6 +110,8 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *borders[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *bordere[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -136,6 +139,8 @@ typedef struct { + struct wl_listener set_hints; + #endif + unsigned int bw; ++ unsigned int bws; ++ unsigned int bwe; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +@@ -973,6 +978,8 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->bws = borders_only_floating ? 0 : borderspx; ++ c->bwe = borders_only_floating ? 0 : borderepx; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -1268,7 +1275,7 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, focuscolor, focuscolor, focuscolor); + } + + /* Deactivate old client if focus is changing */ +@@ -1285,7 +1292,7 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, bordercolor, borderscolor, borderecolor); + + client_activate_surface(old, 0); + } +@@ -1597,6 +1604,12 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + c->isurgent ? urgentcolor : bordercolor); + c->border[i]->node.data = c; ++ ++ c->borders[i] = wlr_scene_rect_create(c->scene, 0, 0, borderscolor); ++ c->borders[i]->node.data = c; ++ ++ c->bordere[i] = wlr_scene_rect_create(c->scene, 0, 0, borderecolor); ++ c->bordere[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ +@@ -1618,6 +1631,12 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ ++ if (borders_only_floating) { ++ c->bws = c->isfloating ? borderspx : 0; ++ c->bwe = c->isfloating ? borderepx : 0; ++ } ++ + printstatus(); + + unset_fullscreen: +@@ -2051,6 +2070,24 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + ++ wlr_scene_rect_set_size(c->borders[0], c->geom.width - 2 * borderspx_offset, c->bws); ++ wlr_scene_rect_set_size(c->borders[1], c->geom.width - 2 * borderspx_offset, c->bws); ++ wlr_scene_rect_set_size(c->borders[2], c->bws, c->geom.height - 2 * c->bws - 2 * borderspx_offset); ++ wlr_scene_rect_set_size(c->borders[3], c->bws, c->geom.height - 2 * c->bws - 2 * borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[0]->node, borderspx_offset, borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[1]->node, borderspx_offset, c->geom.height - c->bws - borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[2]->node, borderspx_offset, c->bws + borderspx_offset); ++ wlr_scene_node_set_position(&c->borders[3]->node, c->geom.width - c->bws - borderspx_offset, c->bws + borderspx_offset); ++ ++ wlr_scene_rect_set_size(c->bordere[0], c->geom.width - (c->bw - c->bwe) * 2 + borderepx_negative_offset * 2, c->bwe); ++ wlr_scene_rect_set_size(c->bordere[1], c->geom.width - (c->bw - c->bwe) * 2 + borderepx_negative_offset * 2, c->bwe); ++ wlr_scene_rect_set_size(c->bordere[2], c->bwe, c->geom.height - 2 * c->bw + 2 * borderepx_negative_offset); ++ wlr_scene_rect_set_size(c->bordere[3], c->bwe, c->geom.height - 2 * c->bw + 2 * borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[0]->node, c->bw - c->bwe - borderepx_negative_offset, c->bw - c->bwe - borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[1]->node, c->bw - c->bwe - borderepx_negative_offset, c->geom.height - c->bw + borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[2]->node, c->bw - c->bwe - borderepx_negative_offset, c->bw - borderepx_negative_offset); ++ wlr_scene_node_set_position(&c->bordere[3]->node, c->geom.width - c->bw + borderepx_negative_offset, c->bw - borderepx_negative_offset); ++ + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); +@@ -2151,6 +2188,12 @@ setfloating(Client *c, int floating) + c->isfloating = floating; + if (!c->mon) + return; ++ ++ if (borders_only_floating) { ++ c->bws = c->isfloating ? borderspx : 0; ++ c->bwe = c->isfloating ? borderepx : 0; ++ } ++ + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); +@@ -2165,6 +2208,8 @@ setfullscreen(Client *c, int fullscreen) + if (!c->mon) + return; + c->bw = fullscreen ? 0 : borderpx; ++ c->bws = fullscreen ? 0 : borderspx; ++ c->bwe = fullscreen ? 0 : borderepx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); +@@ -2819,7 +2864,7 @@ urgent(struct wl_listener *listener, void *data) + printstatus(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, urgentcolor, urgentcolor, urgentcolor); + } + + void +@@ -3023,7 +3068,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, urgentcolor, urgentcolor, urgentcolor); + } + + void +-- +2.44.1 diff --git a/dwl/dwl-patches/patches/bottomstack/README.md b/dwl/dwl-patches/patches/bottomstack/README.md new file mode 100644 index 0000000..fa9ede6 --- /dev/null +++ b/dwl/dwl-patches/patches/bottomstack/README.md @@ -0,0 +1,27 @@ +### Description +bstack and bstackhoriz are two stack layouts for dwl. +### Scheme +``` +bstack (TTT) bstackhoriz (===) ++-----------------+ +-----------------+ +| | | | +| | | | +| | | | ++-----+-----+-----+ +-----------------+ +| | | | +-----------------+ +| | | | +-----------------+ ++-----+-----+-----+ +-----------------+ +``` + + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6-b/bottomstack) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/20de07dc8759200c8a4c9651475acb331d245890/patches/bottomstack/bottomstack.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/bottomstack/bottomstack.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/5368aa392c7ebf8d7d24c232b80cfae1be457d41/bottomstack/bottomstack.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [DanielMowitz](https://github.com/DanielMowitz) +- [Abanoub8](https://github.com/Abanoub8) + diff --git a/dwl/dwl-patches/patches/bottomstack/bottomstack.patch b/dwl/dwl-patches/patches/bottomstack/bottomstack.patch new file mode 100644 index 0000000..c2b9331 --- /dev/null +++ b/dwl/dwl-patches/patches/bottomstack/bottomstack.patch @@ -0,0 +1,140 @@ +From b352fb08f40b1ee2d8c4748be4922df711e3aaa9 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 10:44:29 -0500 +Subject: [PATCH] implement bottomstack + +--- + config.def.h | 4 +++ + dwl.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 88 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5aac3e9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,8 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "TTT", bstack }, ++ { "===", bstackhoriz }, + }; + + /* monitors */ +@@ -139,6 +141,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_u, setlayout, {.v = &layouts[3]} }, ++ { MODKEY, XKB_KEY_o, setlayout, {.v = &layouts[4]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index dc0437e..5648d5f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -57,6 +57,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -351,6 +352,8 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void bstack(Monitor *m); ++static void bstackhoriz(Monitor *m); + + /* variables */ + static const char broken[] = "broken"; +@@ -3160,3 +3163,84 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++static void ++bstack(Monitor *m) ++{ ++ int w, h, mh, mx, tx, ty, tw; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) { ++ mh = (int)round(m->nmaster ? m->mfact * m->w.height : 0); ++ tw = m->w.width / (n - m->nmaster); ++ ty = m->w.y + mh; ++ } else { ++ mh = m->w.height; ++ tw = m->w.width; ++ ty = m->w.y; ++ } ++ ++ i = mx = 0; ++ tx = m-> w.x; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating) ++ continue; ++ if (i < m->nmaster) { ++ w = (m->w.width - mx) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box) { .x = m->w.x + mx, .y = m->w.y, .width = w, .height = mh }, 0); ++ mx += c->geom.width; ++ } else { ++ h = m->w.height - mh; ++ resize(c, (struct wlr_box) { .x = tx, .y = ty, .width = tw, .height = h }, 0); ++ if (tw != m->w.width) ++ tx += c->geom.width; ++ } ++ i++; ++ } ++} ++ ++static void ++bstackhoriz(Monitor *m) { ++ int w, mh, mx, tx, ty, th; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n ++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) { ++ mh = (int)round(m->nmaster ? m->mfact * m->w.height : 0); ++ th = (m->w.height - mh) / (n - m->nmaster); ++ ty = m->w.y + mh; ++ } else { ++ th = mh = m->w.height; ++ ty = m->w.y; ++ } ++ ++ i = mx = 0; ++ tx = m-> w.x; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c,m) || c->isfloating) ++ continue; ++ if (i < m->nmaster) { ++ w = (m->w.width - mx) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box) { .x = m->w.x + mx, .y = m->w.y, .width = w, .height = mh }, 0); ++ mx += c->geom.width; ++ } else { ++ resize(c, (struct wlr_box) { .x = tx, .y = ty, .width = m->w.width, .height = th }, 0); ++ if (th != m->w.height) ++ ty += c->geom.height; ++ } ++ i++; ++ } ++} +-- +2.45.1 diff --git a/dwl/dwl-patches/patches/btrtile/README.md b/dwl/dwl-patches/patches/btrtile/README.md new file mode 100644 index 0000000..4766aa7 --- /dev/null +++ b/dwl/dwl-patches/patches/btrtile/README.md @@ -0,0 +1,103 @@ +### Description + +# btrtile — A Focus-Driven Tiling Layout + +It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized. + +![btrtile in action](https://github.com/julmajustus/dwl-patches/blob/main/demos/btrtiledemo.gif?raw=true) + +--- + +# Why btrtile + +While dwl’s patches folder is full of different layouts, I couldn't find suitable layout that would work well with my workflow and single ultrawide monitor setup. btrtile aims to solve that by introducing a layout strategy that splits clients according to user focus and pointer position. + +--- + +# Features + +- **Combined Tiling and Management** + Combines tiling layout and management of clients under one patchset. + +- **Focus-Driven Splits** + When you add a new client, btrtile checks where your pointer is relative to the focused client’s geometry. + - If the pointer is on the left half (for a horizontally large client), the new client spawns on the left side, and vice versa. + - By default, new splits are 50/50. + +- **Adaptive Splitting** + - If the area to be split is wider than its height, btrtile does a vertical split. + - Otherwise, it does a horizontal split. + +- **Keyboard and Mouse Driven** + - Supports keyboard-based commands for quick ratio adjustments and client swapping. + - Mouse-based resizing and moving are integrated for more intuitive manipulation. + +--- + +# How It Works + +btrtile organizes clients using a binary tree data structure that represents splits either vertically or horizontally. + +When a new client appears: +1. **Focused Client Detection** + btrtile checks your pointer location to find which client (if any) you’re interacting with. +2. **Split Creation** + - If there’s a focused client, btrtile creates a split node around it, placing the new client on the side where your pointer is. + +3. **Ratio Management** + Each split node has a `split_ratio` (defaulting to 0.5). This ratio defines how much space is allocated to each child node. You can adjust this ratio using keyboard or mouse actions. + +--- + +# What It Doesn’t Handle + +- **Suckless philosophy** + - Yea, it's a bloat. I tried to hide the suck inside a single file as much I could. While this approach is not ideal, it's how it's at least for now. + +--- + +# Configuring btrtile + +btrtile adds couple variables to config.h to fine tune the mouse resizing of tiled clients. + +1. **resize_factor** + - A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensivity. + Defaults to 0.0002f. + +2. **resize_interval_ms** + - A time based resize call limiter. Depends on framerate and screen refresh rate. + Defaults to 16ms. (~60 resize updates per second) + +Fine tune these values to find the best values for your setup, smoother resizing can significally increase cpu overhead. +If mouse resizing feels sluggish, you can try compiling dwl with more aggressive optimization flags like -O2/-O3. + +--- + +# Patch recommendations + +1. **Patches that I use with my btrtile** + + - [focusdir](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/focusdir) + Great patch to move focus between clients. + + - [rotatetags](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/rotatetags) + Good patch to rotate the view or shift clients between tags. + + - [warpcursor](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/warpcursor) + Moves cursor location to focused client. + + - [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag) + Allows each tag to have individual layout setups. + + - [gaps](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) + Add gaps between clients. + +--- + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) +- [0.7 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gaps.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch b/dwl/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch new file mode 100644 index 0000000..90354d1 --- /dev/null +++ b/dwl/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch @@ -0,0 +1,922 @@ +From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 13 Feb 2025 23:25:20 +0200 +Subject: [PATCH] btrtile-gaps with multi-tag support + +--- + btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 152 +++++++++++--- + 3 files changed, 717 insertions(+), 29 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..650cab5 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,582 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/02/13 23:25:03 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid, e = m->gaps; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ if (is_root && e) { ++ area.x += gappx; ++ area.y += gappx; ++ area.width -= 2 * gappx; ++ area.height -= 2 * gappx; ++ } ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ ++ if (e) { ++ left_area.width -= gappx / 2; ++ right_area.x += gappx / 2; ++ right_area.width -= gappx / 2; ++ } ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ ++ if (e) { ++ left_area.height -= gappx / 2; ++ right_area.y += gappx / 2; ++ right_area.height -= gappx / 2; ++ } ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..e49a061 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -208,6 +211,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1809,7 +1845,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* 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. */ +@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.3 + diff --git a/dwl/dwl-patches/patches/btrtile/btrtile-v0.7.patch b/dwl/dwl-patches/patches/btrtile/btrtile-v0.7.patch new file mode 100644 index 0000000..b85473e --- /dev/null +++ b/dwl/dwl-patches/patches/btrtile/btrtile-v0.7.patch @@ -0,0 +1,903 @@ +From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 13 Feb 2025 23:23:40 +0200 +Subject: [PATCH] btrtile with multi-tag support + +--- + btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 152 +++++++++++--- + 3 files changed, 698 insertions(+), 29 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..03f4680 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,563 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..e49a061 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -208,6 +211,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1809,7 +1845,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* 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. */ +@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.3 + diff --git a/dwl/dwl-patches/patches/buttonbystate/README.md b/dwl/dwl-patches/patches/buttonbystate/README.md new file mode 100644 index 0000000..cbb0eee --- /dev/null +++ b/dwl/dwl-patches/patches/buttonbystate/README.md @@ -0,0 +1,10 @@ +### Description +Adds "state" (`enum wlr_button_state`) to configure a button action on either press or release. +This basically enables release to be used for button actions. + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_buttonbystate) +- [2024-04-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/buttonbystate/buttonbystate.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) diff --git a/dwl/dwl-patches/patches/buttonbystate/buttonbystate.patch b/dwl/dwl-patches/patches/buttonbystate/buttonbystate.patch new file mode 100644 index 0000000..d669aff --- /dev/null +++ b/dwl/dwl-patches/patches/buttonbystate/buttonbystate.patch @@ -0,0 +1,78 @@ +From 4141aa9455e4b4a5b4a235475c70e8c100ec663e Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 6 Apr 2024 02:03:49 +0100 +Subject: [PATCH] buttonbystate - allow config for release (and press) + +- Adds "state" (enum wlr_button_state) to configure a button action on + either press or release. This basically enables release to be used + for button actions. +--- + config.def.h | 6 +++--- + dwl.c | 22 ++++++++++++---------- + 2 files changed, 15 insertions(+), 13 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..cc989cf 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -165,7 +165,7 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove}, WLR_BUTTON_PRESSED }, ++ { MODKEY, BTN_MIDDLE, togglefloating, {0}, WLR_BUTTON_PRESSED }, ++ { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize}, WLR_BUTTON_PRESSED }, + }; +diff --git a/dwl.c b/dwl.c +index bf763df..6b60ccf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -99,6 +99,7 @@ typedef struct { + unsigned int button; + void (*func)(const Arg *); + const Arg arg; ++ enum wlr_button_state state; + } Button; + + typedef struct Monitor Monitor; +@@ -595,16 +596,6 @@ buttonpress(struct wl_listener *listener, void *data) + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); +- +- keyboard = wlr_seat_get_keyboard(seat); +- mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; +- for (b = buttons; b < END(buttons); b++) { +- if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { +- b->func(&b->arg); +- return; +- } +- } + break; + case WLR_BUTTON_RELEASED: + held_grab = NULL; +@@ -622,6 +613,17 @@ buttonpress(struct wl_listener *listener, void *data) + } + break; + } ++ ++ keyboard = wlr_seat_get_keyboard(seat); ++ mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; ++ for (b = buttons; b < END(buttons); b++) { ++ if (b->state == event->state && CLEANMASK(mods) == CLEANMASK(b->mod) && ++ event->button == b->button && b->func) { ++ b->func(&b->arg); ++ return; ++ } ++ } ++ + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/center-terminal/README.md b/dwl/dwl-patches/patches/center-terminal/README.md new file mode 100644 index 0000000..cfd70a2 --- /dev/null +++ b/dwl/dwl-patches/patches/center-terminal/README.md @@ -0,0 +1,13 @@ +### Description +Add a keybinding that toggles centering the terminally horizontally when +it's the only window, while still tiling multiple windows. + +This limits the width of long text making it easier to read, and avoids +covering the wallpaper more than necessary. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/center-terminal) +- [2024-02-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/center-terminal/center-terminal.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl/dwl-patches/patches/center-terminal/center-terminal.patch b/dwl/dwl-patches/patches/center-terminal/center-terminal.patch new file mode 100644 index 0000000..214eaf2 --- /dev/null +++ b/dwl/dwl-patches/patches/center-terminal/center-terminal.patch @@ -0,0 +1,103 @@ +From 340cc5ef90dfcc495bdad045f3f76ae07405cffd Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Tue, 6 Feb 2024 09:20:48 +0100 +Subject: [PATCH] add a keybinding to center the terminal + +Add a keybinding that toggles centering the terminally horizontally when +it's the only window, while still tiling multiple windows. + +This limits the width of long text making it easier to read, and avoids +covering the wallpaper more than necessary. +--- + config.def.h | 1 + + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8229fcc 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_v, togglecenter, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 12f441e..3b15748 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -138,6 +139,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ bool centered; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -334,6 +336,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglecenter(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -436,6 +439,8 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; + ++static bool center; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -499,6 +504,9 @@ applyrules(Client *c) + } + } + ++ if (!strcasecmp(appid, termcmd[0])) ++ c->centered = true; ++ + c->isfloating |= client_is_float_type(c); + setmon(c, mon, newtags); + } +@@ -2730,6 +2738,11 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { ++ if (n == 1 && center && c->centered) { ++ resize(c, (struct wlr_box){.x = m->w.width / 4, .y = m->w.y, ++ .width = m->w.width / 2, .height = m->w.height - 2 * c->bw}, 0); ++ return; ++ } + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; +@@ -2742,6 +2755,13 @@ tile(Monitor *m) + } + } + ++void ++togglecenter(const Arg *arg) ++{ ++ center = !center; ++ tile(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/centeredmaster/README.md b/dwl/dwl-patches/patches/centeredmaster/README.md new file mode 100644 index 0000000..2ba6dd9 --- /dev/null +++ b/dwl/dwl-patches/patches/centeredmaster/README.md @@ -0,0 +1,33 @@ +### Description +This is a port of centeredmaster patch for dwm: + +centeredmaster centers the nmaster area on screen, using mfact * monitor +width & height, with the stacked windows distributed to the left and +right. It can be selected with `Alt+c`. + +With one and two clients in master respectively this results in: + +``` ++------------------------------+ +------------------------------+ +|+--------++--------++--------+| |+--------++--------++--------+| +|| || || || || || || || +|| || || || || || M1 || || +|| || || || || || || || +|| S2 || M || S1 || || |+--------+| || +|| || || || || |+--------+| || +|| || || || || || || || +|| || || || || || M2 || || +|| || || || || || || || +|+--------++--------++--------+| |+--------++--------++--------+| ++------------------------------+ +------------------------------+ +``` + + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/centeredmaster/centeredmaster.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/b104a580a80ebaf9f7e8917fe574e3e97ddd019a/centeredmaster/centeredmaster.patch) +- [0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/centeredmaster/centeredmaster.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/centeredmaster/centeredmaster.patch b/dwl/dwl-patches/patches/centeredmaster/centeredmaster.patch new file mode 100644 index 0000000..c29bd5f --- /dev/null +++ b/dwl/dwl-patches/patches/centeredmaster/centeredmaster.patch @@ -0,0 +1,135 @@ +From b1ca46930756b59c1ebba0b8c7871b85ff84f62f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sat, 8 Feb 2025 16:10:25 +0100 +Subject: [PATCH] Add centeredmaster layout + +This is a port of centeredmaster patch for dwm: + https://dwm.suckless.org/patches/centeredmaster + +centeredmaster centers the nmaster area on screen, using mfact * monitor +width & height, with the stacked windows distributed to the left and +right. It can be selected with [Alt]+[c]. + +With one and two clients in master respectively this results in: + ++------------------------------+ +------------------------------+ +|+--------++--------++--------+| |+--------++--------++--------+| +|| || || || || || || || +|| || || || || || M1 || || +|| || || || || || || || +|| S2 || M || S1 || || |+--------+| || +|| || || || || |+--------+| || +|| || || || || || || || +|| || || || || || M2 || || +|| || || || || || || || +|+--------++--------++--------+| |+--------++--------++--------+| ++------------------------------+ +------------------------------+ +--- + config.def.h | 2 ++ + dwl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 65 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9a3b0c5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "|M|", centeredmaster }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..c2456dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -251,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static void centeredmaster(Monitor *m); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); +@@ -649,6 +650,68 @@ buttonpress(struct wl_listener *listener, void *data) + event->time_msec, event->button, event->state); + } + ++void ++centeredmaster(Monitor *m) ++{ ++ int i, n, h, mw, mx, my, oty, ety, tw; ++ Client *c; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* initialize areas */ ++ mw = m->w.width; ++ mx = 0; ++ my = 0; ++ tw = mw; ++ ++ if (n > m->nmaster) { ++ /* go mfact box in the center if more than nmaster clients */ ++ mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ tw = m->w.width - mw; ++ ++ if (n - m->nmaster > 1) { ++ /* only one client */ ++ mx = (m->w.width - mw) / 2; ++ tw = (m->w.width - mw) / 2; ++ } ++ } ++ ++ i = 0; ++ oty = 0; ++ ety = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) { ++ /* nmaster clients are stacked vertically, in the center ++ * of the screen */ ++ h = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + my, .width = mw, ++ .height = h}, 0); ++ my += c->geom.height; ++ } else { ++ /* stack clients are stacked vertically */ ++ if ((i - m->nmaster) % 2) { ++ h = (m->w.height - ety) / ( (1 + n - i) / 2); ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ety, .width = tw, ++ .height = h}, 0); ++ ety += c->geom.height; ++ } else { ++ h = (m->w.height - oty) / ((1 + n - i) / 2); ++ resize(c, (struct wlr_box){.x = m->w.x + mx + mw, .y = m->w.y + oty, .width = tw, ++ .height = h}, 0); ++ oty += c->geom.height; ++ } ++ } ++ i++; ++ } ++} ++ + void + chvt(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/cfact-snail/README.md b/dwl/dwl-patches/patches/cfact-snail/README.md new file mode 100644 index 0000000..488ab98 --- /dev/null +++ b/dwl/dwl-patches/patches/cfact-snail/README.md @@ -0,0 +1,13 @@ +### Description +This patch implements [cfact][cfact] for [snail][snail] layout. +This patch must be applied on top of cfact and snail patches. + +[cfact]: /dwl/dwl-patches/src/branch/main/patches/cfact +[snail]: /dwl/dwl-patches/src/branch/main/patches/snail + + +### Download + - [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact-snail/cfact-snail.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/cfact-snail/cfact-snail.patch b/dwl/dwl-patches/patches/cfact-snail/cfact-snail.patch new file mode 100644 index 0000000..213c3fd --- /dev/null +++ b/dwl/dwl-patches/patches/cfact-snail/cfact-snail.patch @@ -0,0 +1,68 @@ +From 9f8109182a7d173d2a2cb30c089a7e7b9ffe0a5e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 25 Mar 2025 02:24:32 +0100 +Subject: [PATCH] cfact patch for snail layout + +--- + dwl.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 4f8c493..37aa935 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2699,10 +2699,10 @@ snail(Monitor *m) + * Split the previous horizontally and put the current window on the right + */ + } else if (dir == WLR_DIRECTION_RIGHT) { +- c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ c->geom = (struct wlr_box){.x = prev->geom.x + (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .y = prev->geom.y, ++ .width = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .height = prev->geom.height}; + prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ .width = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .height = prev->geom.height}; + /* + * If it's a stack window or the first narrow window in the master + * area, put the next one below it +@@ -2713,28 +2713,28 @@ snail(Monitor *m) + * Split the previous vertically and put the current window below it + */ + } else if (dir == WLR_DIRECTION_DOWN) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height), ++ .width = prev->geom.width, .height = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height)}; + prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ .width = prev->geom.width, .height = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height)}; + dir = WLR_DIRECTION_LEFT; + /* + * Split the previous horizontally and put the current window on the left + */ + } else if (dir == WLR_DIRECTION_LEFT) { + c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ .width = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.width), .y = prev->geom.y, ++ .width = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.width), .height = prev->geom.height}; + dir = WLR_DIRECTION_UP; + /* + * Split the previous vertically and put the current window above it + */ + } else { + c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; ++ .width = prev->geom.width, .height = (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height)}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + (int)(c->cweight / (c->cweight + prev->cweight) * prev->geom.height), ++ .width = prev->geom.width, .height = (int)(prev->cweight / (prev->cweight + c->cweight) * prev->geom.height)}; + dir = WLR_DIRECTION_RIGHT; + } + i++; +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/cfact/README.md b/dwl/dwl-patches/patches/cfact/README.md new file mode 100644 index 0000000..166b4e2 --- /dev/null +++ b/dwl/dwl-patches/patches/cfact/README.md @@ -0,0 +1,25 @@ +### Description +A port of the [dwm cfacts patch](https://dwm.suckless.org/patches/cfacts/) (with the limits removed) + +Clients with higher weight are allocated more space! +``` ++---------------------+ +| | 0.5 | +| 1.0 +----------+ ++----------+ | +| | 1.0 | +| +----------+ +| 2.0 | | +| | 1.0 | ++----------+----------+` +``` +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/cfact) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.7.patch) +- [v0.7-gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.7-gaps.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact-v0.6.patch) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact/cfact.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/cfact/cfact-v0.6.patch b/dwl/dwl-patches/patches/cfact/cfact-v0.6.patch new file mode 100644 index 0000000..c1505fb --- /dev/null +++ b/dwl/dwl-patches/patches/cfact/cfact-v0.6.patch @@ -0,0 +1,121 @@ +From 52d8ed5ece7c96ea02441faaa4da2b0f51c8ebc4 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..d1bc596 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -133,6 +133,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..68e35d2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -140,6 +140,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -318,6 +319,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1041,6 +1043,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2218,6 +2221,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2637,6 +2653,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2649,17 +2666,27 @@ tile(Monitor *m) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -3047,6 +3074,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch b/dwl/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch new file mode 100644 index 0000000..e080238 --- /dev/null +++ b/dwl/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch @@ -0,0 +1,155 @@ +From 31d0ceb3f7dea83282e61e556d71d06b7f43d753 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Sat, 16 Nov 2024 08:47:02 -0500 +Subject: [PATCH] Add cfact to gaps + +--- + config.def.h | 3 +++ + dwl.c | 63 +++++++++++++++++++++++++++++++++++++++++----------- + 2 files changed, 53 insertions(+), 13 deletions(-) + +diff --git a/config.def.h b/config.def.h +index b388b4e..a871364 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -136,6 +136,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_g, togglegaps, {0} }, +diff --git a/dwl.c b/dwl.c +index dc851df..b5313c1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -140,6 +140,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -319,6 +320,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1045,6 +1047,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2223,6 +2226,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2642,38 +2658,58 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int h, r, e = m->gaps, mw, my, ty; ++ unsigned int e = m->gaps, mw, my, ty; + int i, n = 0; +- Client *c; ++ float mweight = 0, tweight = 0; ++ Client *c, *sel = NULL; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m) && !c->isfullscreen) { ++ if (!sel) ++ sel = c; ++ if (!c->isfloating) ++ n++; ++ } ++ } + +- wl_list_for_each(c, &clients, link) +- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) +- n++; + if (n == 0) + return; ++ + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else +- mw = m->w.width; ++ mw = m->w.width - gappx*e; ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } ++ + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- r = MIN(n, m->nmaster) - i; +- h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, +- .width = mw - 2*gappx*e, .height = h}, 0); ++ .width = mw - gappx*e, ++ .height = (int)((c->cweight / mweight) * (float)(m->w.height - gappx*e)) - gappx*e ++ }, 0); + my += c->geom.height + gappx*e; + } else { +- r = n - i; +- h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ resize(c, (struct wlr_box){.x = m->w.x + mw + gappx*e, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*gappx*e, ++ .height = (int)((c->cweight / tweight) * (float)(m->w.height - gappx*e)) - gappx*e ++ }, 0); + ty += c->geom.height + gappx*e; + } + i++; +@@ -3068,6 +3104,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.47.0 + diff --git a/dwl/dwl-patches/patches/cfact/cfact-v0.7.patch b/dwl/dwl-patches/patches/cfact/cfact-v0.7.patch new file mode 100644 index 0000000..7d28a8b --- /dev/null +++ b/dwl/dwl-patches/patches/cfact/cfact-v0.7.patch @@ -0,0 +1,121 @@ +From e82e15860c36a70539625b8fe7b4bd54d0721705 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..d1bc596 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -133,6 +133,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 5bf995e..5a17343 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -322,6 +323,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -1060,6 +1062,7 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->cweight = 1.0; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -2268,6 +2271,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2692,6 +2708,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2704,17 +2721,27 @@ tile(Monitor *m) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -3102,6 +3129,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/cfact/cfact.patch b/dwl/dwl-patches/patches/cfact/cfact.patch new file mode 100644 index 0000000..c91d3c0 --- /dev/null +++ b/dwl/dwl-patches/patches/cfact/cfact.patch @@ -0,0 +1,121 @@ +From 98fe302cd240b519c28c886250273854844ab2c7 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 24 Nov 2023 21:16:56 +0100 +Subject: [PATCH] cweights to allow different size clients in normal layouts + +--- + config.def.h | 3 +++ + dwl.c | 32 ++++++++++++++++++++++++++++++-- + 2 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..27ff521 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -128,6 +128,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, setcfact, {.f = +0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, setcfact, {.f = -0.25f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, setcfact, {.f = 0.0f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index fa76db2..9bdb438 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -136,6 +136,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ float cweight; + } Client; + + typedef struct { +@@ -301,6 +302,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcfact(const Arg *arg); + 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); +@@ -956,6 +958,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->cweight = 1.0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -2009,6 +2012,19 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++setcfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if(!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ sel->cweight = (float) (arg->f ? sel->cweight + arg->f : 1.0); ++ if (sel->cweight < 0) ++ sel->cweight = 0; ++ arrange(selmon); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2468,6 +2484,7 @@ tile(Monitor *m) + { + unsigned int mw, my, ty; + int i, n = 0; ++ float mweight = 0, tweight = 0; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2480,17 +2497,27 @@ tile(Monitor *m) + mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; + else + mw = m->w.width; ++ i = 0; ++ wl_list_for_each(c, &clients, link){ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) ++ mweight += c->cweight; ++ else ++ tweight += c->cweight; ++ i++; ++ } + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (int) ((c->cweight / mweight) * m->w.height)}, 0); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (int) ((c->cweight / tweight) * m->w.height) }, 0); + ty += c->geom.height; + } + i++; +@@ -2872,6 +2899,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = borderpx; ++ c->cweight = 1.0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.43.1 + diff --git a/dwl/dwl-patches/patches/chainkeys/README.md b/dwl/dwl-patches/patches/chainkeys/README.md new file mode 100644 index 0000000..fa6d185 --- /dev/null +++ b/dwl/dwl-patches/patches/chainkeys/README.md @@ -0,0 +1,15 @@ +### Description +Implements chained keybindings (like the dwm +[keychain](https://dwm.suckless.org/patches/keychain/) patch). + +Bindings can share a leading chain key. This chain key will be triggered when +Mod+chain is pressed. A subsequent keypress will be matched against bindings +for that chain key. If it is configured the action will be triggered, otherwise +the keypress will be ignored. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/chainkeys) +- [2024-05-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/chainkeys/chainkeys.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/chainkeys/chainkeys.patch b/dwl/dwl-patches/patches/chainkeys/chainkeys.patch new file mode 100644 index 0000000..a0daefd --- /dev/null +++ b/dwl/dwl-patches/patches/chainkeys/chainkeys.patch @@ -0,0 +1,157 @@ +From 226e204ec7fb6d6840a984ef8e8ec1d2514e985f Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Tue, 2 Jan 2024 10:33:59 +1000 +Subject: [PATCH] chainkeys + +--- + config.def.h | 62 ++++++++++++++++++++++++++-------------------------- + dwl.c | 23 ++++++++++++++++++- + 2 files changed, 53 insertions(+), 32 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8f498d2f..1c182547 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -105,10 +105,10 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + #define MODKEY WLR_MODIFIER_ALT + + #define TAGKEYS(KEY,SKEY,TAG) \ +- { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY, -1, KEY, view, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL, -1, KEY, toggleview, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, SKEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,-1,SKEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -119,30 +119,30 @@ static const char *menucmd[] = { "wmenu-run", NULL }; + + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ /* modifier chain, key function argument */ ++ { MODKEY, -1, XKB_KEY_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, -1, XKB_KEY_j, focusstack, {.i = +1} }, ++ { MODKEY, -1, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY, -1, XKB_KEY_i, incnmaster, {.i = +1} }, ++ { MODKEY, -1, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY, -1, XKB_KEY_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, -1, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, -1, XKB_KEY_Return, zoom, {0} }, ++ { MODKEY, -1, XKB_KEY_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_C, killclient, {0} }, ++ { MODKEY, -1, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, -1, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, -1, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, -1, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_space, togglefloating, {0} }, ++ { MODKEY, -1, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, -1, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_parenright, tag, {.ui = ~0} }, ++ { MODKEY, -1, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, -1, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -152,14 +152,14 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, -1, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,-1,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } ++#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,-1,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; +diff --git a/dwl.c b/dwl.c +index bf763dfc..05e667f8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,6 +143,7 @@ typedef struct { + + typedef struct { + uint32_t mod; ++ int chain; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +@@ -353,6 +354,7 @@ static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; ++static int chainkey = -1; + static struct wl_display *dpy; + static struct wlr_backend *backend; + static struct wlr_scene *scene; +@@ -1438,11 +1440,30 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && sym == k->keysym ++ && chainkey == -1 ++ && k->chain == -1 ++ && k->func) { + k->func(&k->arg); + return 1; + } ++ else if (sym == k->keysym ++ && chainkey != -1 ++ && k->chain == chainkey ++ && k->func) { ++ k->func(&k->arg); ++ chainkey = -1; ++ return 1; ++ } ++ else if (CLEANMASK(mods) == CLEANMASK(k->mod) ++ && k->chain == (int)sym ++ && chainkey == -1 ++ && k->func) { ++ chainkey = sym; ++ return 1; ++ } + } ++ chainkey = -1; + return 0; + } + +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/client-opacity/README.md b/dwl/dwl-patches/patches/client-opacity/README.md new file mode 100644 index 0000000..ed82d81 --- /dev/null +++ b/dwl/dwl-patches/patches/client-opacity/README.md @@ -0,0 +1,18 @@ +### Description +This patch adds default transparency parameters to config.h which specify the starting transparencies of all windows. + +It also adds opacities to the ruleset, enabling override of the opacities on a per client basis. + +Additionally, it adds some shortcuts: +``` +[MODKEY]+[o] -> increase focus opacity of currently focused window +[MODKEY]+[Shift]+[o] -> decrease focus opacity of currently focused window +``` + + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/client-opacity) +- [2025-01-20](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/client-opacity/client-opacity.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/client-opacity/client-opacity.patch b/dwl/dwl-patches/patches/client-opacity/client-opacity.patch new file mode 100644 index 0000000..da2faf7 --- /dev/null +++ b/dwl/dwl-patches/patches/client-opacity/client-opacity.patch @@ -0,0 +1,181 @@ +From ba3172875d379ff4f2db69753f50067cecfc8293 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 25 Jul 2023 12:48:22 -0600 +Subject: [PATCH] add default transparency for windows and rules for override + the transparency +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 9 ++++++--- + dwl.c | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0eb86874 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float default_opacity = 0.75; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +23,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating alpha monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, default_opacity, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 1.0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +@@ -133,6 +134,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, XKB_KEY_o, setopacity, {.f = +0.1f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, setopacity, {.f = -0.1f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index ad21e1ba..0554fcdf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,6 +138,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ float opacity; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ float opacity; + int monitor; + } Rule; + +@@ -319,6 +321,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void scenebuffersetopacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); + 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); +@@ -326,6 +329,7 @@ static void setfullscreen(Client *c, int fullscreen); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); ++static void setopacity(const Arg *arg); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); +@@ -491,6 +495,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->opacity = r->opacity; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -499,6 +504,8 @@ applyrules(Client *c) + } + } + } ++ if (c->scene_surface) ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); + setmon(c, mon, newtags); + } + +@@ -874,6 +881,9 @@ commitnotify(struct wl_listener *listener, void *data) + + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + ++ if (c->scene_surface) ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); ++ + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +@@ -1120,6 +1130,7 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->opacity = default_opacity; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -2285,6 +2296,15 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++scenebuffersetopacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *data) ++{ ++ Client *c = data; ++ /* xdg-popups are children of Client.scene, we do not have to worry about ++ messing with them. */ ++ wlr_scene_buffer_set_opacity(buffer, c->isfullscreen ? 1 : c->opacity); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2353,6 +2373,7 @@ setfullscreen(Client *c, int fullscreen) + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, scenebuffersetopacity, c); + arrange(c->mon); + printstatus(); + } +@@ -2409,6 +2430,23 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + focusclient(focustop(selmon), 1); + } + ++void ++setopacity(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (!sel) ++ return; ++ ++ sel->opacity += arg->f; ++ if (sel->opacity > 1.0) ++ sel->opacity = 1.0f; ++ ++ if (sel->opacity < 0.1) ++ sel->opacity = 0.1f; ++ ++ wlr_scene_node_for_each_buffer(&sel->scene_surface->node, scenebuffersetopacity, sel); ++} ++ + void + setpsel(struct wl_listener *listener, void *data) + { +@@ -3120,6 +3158,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->opacity = default_opacity; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/column/README.md b/dwl/dwl-patches/patches/column/README.md new file mode 100644 index 0000000..9d1b205 --- /dev/null +++ b/dwl/dwl-patches/patches/column/README.md @@ -0,0 +1,9 @@ +### Description +A column layout patch. This patch just puts the visible clients into equal-width columns on the screen. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/column) +- [2024-01-02](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/column/column.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/column/column.patch b/dwl/dwl-patches/patches/column/column.patch new file mode 100644 index 0000000..0f6a531 --- /dev/null +++ b/dwl/dwl-patches/patches/column/column.patch @@ -0,0 +1,79 @@ +From d1eb2061c619d0bbd7a0ecda0fe77409f3a6c399 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Fri, 29 Dec 2023 19:02:11 +1000 +Subject: [PATCH] column layout + +--- + config.def.h | 2 ++ + dwl.c | 28 ++++++++++++++++++++++++++++ + 2 files changed, 30 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a8ed61d9..edb30cae 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "||", col }, + }; + + /* monitors */ +@@ -134,6 +135,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 4d19357f..63d80da7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -243,6 +243,7 @@ static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); + static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); ++static void col(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void createdecoration(struct wl_listener *listener, void *data); +@@ -704,6 +705,33 @@ closemon(Monitor *m) + printstatus(); + } + ++void ++col(Monitor *m) ++{ ++ Client *c; ++ unsigned int n = 0, i = 0; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x + i * m->w.width / n, ++ .y = m->w.y, ++ .width = m->w.width / n, ++ .height = m->w.height ++ }, ++ 0 ++ ); ++ i++; ++ } ++} ++ + void + commitlayersurfacenotify(struct wl_listener *listener, void *data) + { +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/coredump/README.md b/dwl/dwl-patches/patches/coredump/README.md new file mode 100644 index 0000000..5300890 --- /dev/null +++ b/dwl/dwl-patches/patches/coredump/README.md @@ -0,0 +1,11 @@ +### Description +Generate a coredump if dwl exited abnormally (to be more usefull you need to +compile dwl and wlroots with debug symbols) + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/coredump) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/coredump/coredump.patch) +- [coredump-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/coredump/coredump-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/coredump/coredump-0.7.patch b/dwl/dwl-patches/patches/coredump/coredump-0.7.patch new file mode 100644 index 0000000..f39e7af --- /dev/null +++ b/dwl/dwl-patches/patches/coredump/coredump-0.7.patch @@ -0,0 +1,65 @@ +From 2abde87f9159ec3318a0489ac0ed512f166ef8c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 5 Oct 2022 23:07:13 -0500 +Subject: [PATCH] increase RLIMIT_CORE (generate a coredump) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f67..cfbb0bb8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -358,6 +359,8 @@ static void zoom(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; ++static struct rlimit oldrlimit; ++static struct rlimit newrlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2232,6 +2235,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2649,6 +2653,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -3189,6 +3194,10 @@ main(int argc, char *argv[]) + char *startup_cmd = NULL; + int c; + ++ getrlimit(RLIMIT_CORE, &oldrlimit); ++ newrlimit.rlim_cur = newrlimit.rlim_max = oldrlimit.rlim_max; ++ setrlimit(RLIMIT_CORE, &newrlimit); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/coredump/coredump.patch b/dwl/dwl-patches/patches/coredump/coredump.patch new file mode 100644 index 0000000..98ffbd8 --- /dev/null +++ b/dwl/dwl-patches/patches/coredump/coredump.patch @@ -0,0 +1,65 @@ +From 6d5017888891957615160fe7c015adf7a6f0fd45 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 5 Oct 2022 23:07:13 -0500 +Subject: [PATCH] increase RLIMIT_CORE (generate a coredump) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..940fbeff 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -353,6 +354,8 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + static void zoom(const Arg *arg); + + /* variables */ ++static struct rlimit oldrlimit; ++static struct rlimit newrlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2248,6 +2251,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2659,6 +2663,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ setrlimit(RLIMIT_CORE, &oldrlimit); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -3178,6 +3183,10 @@ main(int argc, char *argv[]) + char *startup_cmd = NULL; + int c; + ++ getrlimit(RLIMIT_CORE, &oldrlimit); ++ newrlimit.rlim_cur = newrlimit.rlim_max = oldrlimit.rlim_max; ++ setrlimit(RLIMIT_CORE, &newrlimit); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/cursortheme/README.md b/dwl/dwl-patches/patches/cursortheme/README.md new file mode 100644 index 0000000..ac94800 --- /dev/null +++ b/dwl/dwl-patches/patches/cursortheme/README.md @@ -0,0 +1,17 @@ +### Description +Adds ability to change cursor's theme and size. + +```c +static const char *cursor_theme = NULL; +static const char cursor_size[] = "24"; /* Make sure it's a valid integer, otherwise things will break */ +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/cursortheme) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/cursortheme/cursortheme.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/b828e21717fa584affeb3245359c3ab615759fa4/cursortheme/cursortheme.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/c676de59d51e613bd52ac46c77a24b1cac9a61a1/cursortheme/cursortheme.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [egorguslyan](https://github.com/egorguslyan) diff --git a/dwl/dwl-patches/patches/cursortheme/cursortheme.patch b/dwl/dwl-patches/patches/cursortheme/cursortheme.patch new file mode 100644 index 0000000..3c34222 --- /dev/null +++ b/dwl/dwl-patches/patches/cursortheme/cursortheme.patch @@ -0,0 +1,46 @@ +From f08376a2a04929a3907612e6c1f980ad3cdf939f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:10:39 -0500 +Subject: [PATCH] implement cursortheme + +--- + config.def.h | 2 ++ + dwl.c | 8 ++++++-- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..1f9ff56 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,8 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const char *cursor_theme = NULL; ++static const char cursor_size[] = "24"; /* Make sure it's a valid integer, otherwise things will break */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index dc0437e..a91d42b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2522,8 +2522,12 @@ setup(void) + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ +- cursor_mgr = wlr_xcursor_manager_create(NULL, 24); +- setenv("XCURSOR_SIZE", "24", 1); ++ cursor_mgr = wlr_xcursor_manager_create(cursor_theme, atoi(cursor_size)); ++ setenv("XCURSOR_SIZE", cursor_size, 1); ++ if (cursor_theme) ++ setenv("XCURSOR_THEME", cursor_theme, 1); ++ else ++ unsetenv("XCURSOR_THEME"); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around +-- +2.45.1 + + diff --git a/dwl/dwl-patches/patches/customfloat/README.md b/dwl/dwl-patches/patches/customfloat/README.md new file mode 100644 index 0000000..9f2bda5 --- /dev/null +++ b/dwl/dwl-patches/patches/customfloat/README.md @@ -0,0 +1,24 @@ +### Description +Rules for floating windows support default x, y, width, height. Defaults to the center of the screen and the client size. + +If the width or height is less than or equal to 1, then the value will be interpreted as a percentage. For example, 0.5 represents 50%, 0.25 represents 25%, and 1 represents 100%. **NOTE**: Some clients, like Thunar, have minimum width/height + +The variable `center_relative_to_monitor` allows the user to choose whether to center relative to the monitor or relative to the window area. + +
+Explanation of center_relative_to_monitor: +
+The "Monitor area" refers to the space enclosed by the green rectangle, while the "Window area" refers to the space enclosed by the red rectangle.
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/customfloat) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/customfloat/customfloat.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/98cba933c9f4099202e54f39acbf17e05bde828a/customfloat/customfloat.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/bf098459219e7a473d8edb4c0435aeb6a4b82e38/customfloat/customfloat.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Stivvo](https://github.com/Stivvo) diff --git a/dwl/dwl-patches/patches/customfloat/customfloat.patch b/dwl/dwl-patches/patches/customfloat/customfloat.patch new file mode 100644 index 0000000..d295571 --- /dev/null +++ b/dwl/dwl-patches/patches/customfloat/customfloat.patch @@ -0,0 +1,93 @@ +From 4f19f5499610d56f2616da5d44039403ac9d4c06 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 9 Jul 2024 10:52:37 -0500 +Subject: [PATCH] implement customfloat and generate patches + +--- + config.def.h | 7 ++++--- + dwl.c | 27 +++++++++++++++++++++++++++ + 2 files changed, 31 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..dee53f4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +23,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor x y width height */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, -1, 0, 0, 1000, 0.75 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1, 0, 0, 0, 0 },/* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0437e..be0340f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -230,6 +230,10 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int x; ++ int y; ++ float w; ++ float h; + } Rule; + + typedef struct { +@@ -454,6 +458,11 @@ applyrules(Client *c) + int i; + const Rule *r; + Monitor *mon = selmon, *m; ++ int newwidth; ++ int newheight; ++ int newx; ++ int newy; ++ int apply_resize = 0; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) +@@ -471,9 +480,27 @@ applyrules(Client *c) + if (r->monitor == i++) + mon = m; + } ++ if (c->isfloating || !mon->lt[mon->sellt]->arrange) { ++ /* client is floating or in floating layout */ ++ struct wlr_box b = respect_monitor_reserved_area ? mon->w : mon->m; ++ newwidth = (int)round(r->w ? (r->w <= 1 ? b.width * r->w : r->w) : c->geom.width); ++ newheight = (int)round(r->h ? (r->h <= 1 ? b.height * r->h : r->h) : c->geom.height); ++ newx = (int)round(r->x ? (r->x <= 1 ? b.width * r->x + b.x : r->x + b.x) : c->geom.x); ++ newy = (int)round(r->y ? (r->y <= 1 ? b.height * r->y + b.y : r->y + b.y) : c->geom.y); ++ apply_resize = 1; ++ ++ } + } + } + setmon(c, mon, newtags); ++ if (apply_resize) { ++ resize(c, (struct wlr_box){ ++ .x = newx, ++ .y = newy, ++ .width = newwidth, ++ .height = newheight, ++ }, 1); ++ } + } + + void +-- +2.45.1 diff --git a/dwl/dwl-patches/patches/dim-unfocused/README.md b/dwl/dwl-patches/patches/dim-unfocused/README.md new file mode 100644 index 0000000..dad3ef0 --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/README.md @@ -0,0 +1,19 @@ +### Description +Implements dimming of clients which are unfocused. + +The code also allows any color dimming. There is also an additional option in `Rule`, which allows you to keep the client `neverdim`, that is, as if it is focused. + +There are also two functions that can be bound to a `Key` or `Button`, +1. `toggledimming`: Which toggles dimming for all windows (except for `Rule`s) +2. `toggledimmingclient`: Which toggles dimming for the focused window, as if the client had `neverdim` applied to it. This overwrites an applied `Rule`. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused.patch) +- [2024-09-03](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240903.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240714.patch) +- [2024-05-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240516.patch) +- [2024-04-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240416.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/dim-unfocused) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) diff --git a/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch new file mode 100644 index 0000000..88504cd --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch @@ -0,0 +1,213 @@ +From cd3b5580dbf38f54b54d5bfb6039e0039cbd6b21 Mon Sep 17 00:00:00 2001 +From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> +Date: Thu, 16 May 2024 12:26:05 +0200 +Subject: [PATCH] clean git history + +--- + client.h | 6 ++++++ + config.def.h | 13 ++++++++----- + dwl.c | 40 +++++++++++++++++++++++++++++++++++----- + 3 files changed, 49 insertions(+), 10 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..4d83248 100644 +--- a/client.h ++++ b/client.h +@@ -332,6 +332,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 8f498d2..d3950f9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -21,10 +22,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -135,8 +137,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index bf763df..ca88ad0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -109,6 +109,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -137,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -330,6 +332,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -462,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1267,8 +1272,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1286,7 +1293,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1566,7 +1573,7 @@ mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1599,6 +1606,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1617,6 +1628,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2040,7 +2055,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2050,6 +2065,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width, c-> geom.height); ++ wlr_scene_node_set_position(&c->dimmer->node, 0, 0); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2603,6 +2620,19 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ + void + togglefloating(const Arg *arg) + { +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch new file mode 100644 index 0000000..701c450 --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch @@ -0,0 +1,177 @@ +diff --git a/client.h b/client.h +index 800b867..4d83248 100644 +--- a/client.h ++++ b/client.h +@@ -332,6 +332,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 8847e58..36ac2a1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -21,10 +22,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -135,8 +137,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index bf763df..abd7112 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -109,6 +109,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -137,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -227,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -330,6 +332,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -462,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1267,8 +1272,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1286,7 +1293,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1599,6 +1606,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -2040,7 +2051,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2050,6 +2061,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width, c-> geom.height); ++ wlr_scene_node_set_position(&c->dimmer->node, 0, 0); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2603,6 +2616,17 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ client_set_dimmer_state(focustop(selmon), 0); ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch new file mode 100644 index 0000000..0b60d60 --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch @@ -0,0 +1,199 @@ +diff --git a/client.h b/client.h +index f0e5445..04b8d31 100644 +--- a/client.h ++++ b/client.h +@@ -320,6 +320,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..e1b2bf0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..6c65860 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -110,6 +110,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -138,7 +139,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -229,6 +230,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -334,6 +336,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -407,6 +410,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -465,6 +469,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1346,8 +1351,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1365,7 +1372,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1638,7 +1645,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p, *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1670,6 +1677,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1688,6 +1699,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2141,7 +2156,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2151,6 +2166,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2666,6 +2683,19 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch new file mode 100644 index 0000000..fc48b0b --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch @@ -0,0 +1,216 @@ +diff --git a/client.h b/client.h +index dabea35..3a31c25 100644 +--- a/client.h ++++ b/client.h +@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..4ca21c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +@@ -172,5 +175,6 @@ static const Key keys[] = { + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 9021e44..e1f32e9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -112,6 +112,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -141,7 +142,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +232,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -466,6 +471,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1366,8 +1372,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1385,7 +1393,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1682,8 +1690,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1717,6 +1724,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1735,6 +1746,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2161,7 +2176,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2171,6 +2186,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2682,6 +2699,27 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ ++void ++toggledimmingclient(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ sel -> neverdim ^= 1; ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused.patch b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused.patch new file mode 100644 index 0000000..f73d886 --- /dev/null +++ b/dwl/dwl-patches/patches/dim-unfocused/dim-unfocused.patch @@ -0,0 +1,216 @@ +diff --git a/client.h b/client.h +index dabea35..3a31c25 100644 +--- a/client.h ++++ b/client.h +@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4]) + wlr_scene_rect_set_color(c->border[i], color); + } + ++static inline void ++client_set_dimmer_state(Client *c, const int dim) ++{ ++ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim); ++} ++ + static inline void + client_set_fullscreen(Client *c, int fullscreen) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..4ca21c9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); ++static const float unfocuseddim[] = COLOR(0x00000088); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ +- /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ /* app_id title tags mask isfloating neverdim monitor */ ++ /* examples: ++ { "Gimp_example", NULL, 0, 1, 0, -1 }, ++ */ ++ { "firefox_example", NULL, 1 << 8, 0, 1, -1 }, + }; + + /* layout(s) */ +@@ -140,8 +142,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_apostrophe, toggledimming, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +@@ -172,5 +175,6 @@ static const Key keys[] = { + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index dc0c861..dcc3ece 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -112,6 +112,7 @@ typedef struct { + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ ++ struct wlr_scene_rect *dimmer; + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; +@@ -141,7 +142,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, neverdim; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +232,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int neverdim; + int monitor; + } Rule; + +@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void toggledimming(const Arg *arg); ++static void toggledimmingclient(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int DIMOPT = 1; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -466,6 +471,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->neverdim = r-> neverdim; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1365,8 +1371,10 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ client_set_dimmer_state(c, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1384,7 +1392,7 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ client_set_dimmer_state(old_c, 1); + client_activate_surface(old, 0); + } + } +@@ -1681,8 +1689,7 @@ void + mapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is mapped, or ready to display on-screen. */ +- Client *p = NULL; +- Client *w, *c = wl_container_of(listener, c, map); ++ Client *p, *w, *d, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + +@@ -1716,6 +1723,10 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim); ++ c->dimmer->node.data = c; ++ client_set_dimmer_state(c, 1); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1734,6 +1745,10 @@ mapnotify(struct wl_listener *listener, void *data) + setmon(c, p->mon, p->tags); + } else { + applyrules(c); ++ d = focustop(selmon); ++ if (d) { ++ client_set_dimmer_state(d, 0); ++ } + } + printstatus(); + +@@ -2160,7 +2175,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom = geo; + applybounds(c, bbox); + +- /* Update scene-graph, including borders */ ++ /* Update scene-graph, including borders and dimmer*/ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +@@ -2170,6 +2185,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw); ++ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2681,6 +2698,27 @@ tile(Monitor *m) + } + } + ++void toggledimming(const Arg *arg) ++{ ++ Client *c; ++ DIMOPT ^= 1; ++ wl_list_for_each(c, &clients, link) ++ { ++ client_set_dimmer_state(c, 1); ++ } ++ c = focustop(selmon); ++ if (c) ++ client_set_dimmer_state(c, 0); ++} ++ ++void ++toggledimmingclient(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ sel -> neverdim ^= 1; ++} ++ + void + togglefloating(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md new file mode 100644 index 0000000..c6588ee --- /dev/null +++ b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/README.md @@ -0,0 +1,11 @@ +### Description +This patch changes the default behavior of the [disable-keybindings-on-fullscreen](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/disable-keybindings-on-fullscreen) patch by only taking effect when you explicitly toggle the functionality. +You must apply that patch prior to applying this one. + +### Download +- [git branch](https://github.com/Shringe/dwl/tree/disable-keybindings-on-fullscreen-toggle) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch) + +### Authors +- [Shringe](https://codeberg.org/Shringe) +- shringe_ at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch new file mode 100644 index 0000000..272d906 --- /dev/null +++ b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen-toggle/disable-keybindings-on-fullscreen-toggle-0.7.patch @@ -0,0 +1,71 @@ +From 71809cee0e27f1b3620773e1745afed8023f71c9 Mon Sep 17 00:00:00 2001 +From: Shringe +Date: Mon, 23 Jun 2025 18:50:40 -0500 +Subject: [PATCH] Implemented functionality for patch + +--- + config.def.h | 1 + + dwl.c | 14 ++++++++++++-- + 2 files changed, 13 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..dda4ad0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_G, togglefullscreenkeyinhibit, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index f11de4b..5deb9c7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -341,6 +341,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefullscreenkeyinhibit(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -414,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int fullscreen_key_inhibit_enabled = 0; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1583,8 +1586,9 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- if (c && c->isfullscreen) { +- if (k->func == togglefullscreen) { ++ if (fullscreen_key_inhibit_enabled ++ && c && c->isfullscreen) { ++ if (k->func == togglefullscreenkeyinhibit) { + k->func(&k->arg); + return 1; + } +@@ -2763,6 +2767,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefullscreenkeyinhibit(const Arg *arg) ++{ ++ fullscreen_key_inhibit_enabled = !fullscreen_key_inhibit_enabled; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md new file mode 100644 index 0000000..9c438bb --- /dev/null +++ b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md @@ -0,0 +1,9 @@ +### Description +This patch disables all keybindings except `togglefullscreen` when the focused window is fullscreen. +Might help prevent fat-fingering. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/disable-keybindings-on-fullscreen) +- [2025-02-09](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch new file mode 100644 index 0000000..57a18e1 --- /dev/null +++ b/dwl/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch @@ -0,0 +1,82 @@ +From 2d6b845701091d3238774747c718df7fef135986 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Sun, 9 Feb 2025 14:59:33 +0200 +Subject: [PATCH] disable keybindings on fullscreen + +--- + dwl.c | 23 ++++++++++++++++++++++- + 1 file changed, 22 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index ec4ca86..8c771e8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -282,6 +282,7 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static Client *firstfocused(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -620,11 +621,15 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *focused; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ focused = firstfocused(); ++ if (focused && focused->isfullscreen) ++ goto skip_click; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -664,6 +669,7 @@ buttonpress(struct wl_listener *listener, void *data) + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ ++skip_click: + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); + } +@@ -1393,6 +1399,13 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++Client * ++firstfocused(void) ++{ ++ Client *c = wl_container_of(fstack.next, c, flink); ++ return c; ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1607,10 +1620,18 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + * processing keys, rather than passing them on to the client for its own + * processing. + */ ++ Client *c = firstfocused(); + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { ++ if (c && c->isfullscreen) { ++ if (k->func == togglefullscreen) { ++ k->func(&k->arg); ++ return 1; ++ } ++ return 0; ++ } + k->func(&k->arg); + return 1; + } +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/dragmfact/README.md b/dwl/dwl-patches/patches/dragmfact/README.md new file mode 100644 index 0000000..3112d44 --- /dev/null +++ b/dwl/dwl-patches/patches/dragmfact/README.md @@ -0,0 +1,11 @@ +### Description +Change mfact by dragging the mouse. + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/dragmfact) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact-v0.6.patch) +- [2024-02-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragmfact/dragmfact.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch b/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch new file mode 100644 index 0000000..2435cbf --- /dev/null +++ b/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch @@ -0,0 +1,59 @@ +From aeee958aec3e0774f3ea8baefe028e1a8cc2d2ce Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..c9071a5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -171,6 +171,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 145fd01..0a3d140 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -80,7 +80,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1823,6 +1823,9 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1874,6 +1877,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch b/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch new file mode 100644 index 0000000..f0ea5d3 --- /dev/null +++ b/dwl/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch @@ -0,0 +1,59 @@ +From ae44bfc181fc1532d2f0c4deb20e20634050a661 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..c9071a5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -171,6 +171,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index 5bf995e..dfb0754 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -81,7 +81,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1872,6 +1872,9 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1923,6 +1926,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/dragmfact/dragmfact.patch b/dwl/dwl-patches/patches/dragmfact/dragmfact.patch new file mode 100644 index 0000000..e2fdbac --- /dev/null +++ b/dwl/dwl-patches/patches/dragmfact/dragmfact.patch @@ -0,0 +1,59 @@ +From 435cdf673e5a8080123109dbf874aac2ccef1498 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 25 Mar 2022 23:45:10 +0100 +Subject: [PATCH] Change mfact using Middle mouse + +--- + config.def.h | 2 +- + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..3c26522 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -166,6 +166,6 @@ static const Key keys[] = { + + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { MODKEY, BTN_MIDDLE, moveresize, {.ui = Curmfact} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; +diff --git a/dwl.c b/dwl.c +index fa76db2..528e102 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -76,7 +76,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurMove, CurResize, Curmfact }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrFS, LyrTop, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -1639,6 +1639,9 @@ motionnotify(uint32_t time) + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = ROUND(cursor->x) - grabc->geom.x, .height = ROUND(cursor->y) - grabc->geom.y}, 1); + return; ++ } else if (cursor_mode == Curmfact && time) { ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + + /* Find the client under the pointer and send the event along. */ +@@ -1701,6 +1704,10 @@ moveresize(const Arg *arg) + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; ++ case Curmfact: ++ setfloating(grabc, 0); ++ selmon->mfact = (float) (cursor->x / selmon->m.width); ++ arrange(selmon); + } + } + +-- +2.43.2 + diff --git a/dwl/dwl-patches/patches/dragresize/README.md b/dwl/dwl-patches/patches/dragresize/README.md new file mode 100644 index 0000000..c890ce7 --- /dev/null +++ b/dwl/dwl-patches/patches/dragresize/README.md @@ -0,0 +1,12 @@ +### Description +implement rio-like window resizing + +select window to resize (mod+middleclick by default) then drag out an area for it to occupy + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/dragresize) +- [2024-07-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dragresize/dragresize.patch) +- [2024-06-19](https://codeberg.org/dwl/dwl-patches/raw/commit/8c75e6dbc1728bf70d42547222464f496d9ea613/patches/dragresize/dragresize.patch) + +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/dragresize/dragresize.patch b/dwl/dwl-patches/patches/dragresize/dragresize.patch new file mode 100644 index 0000000..1e4cfe0 --- /dev/null +++ b/dwl/dwl-patches/patches/dragresize/dragresize.patch @@ -0,0 +1,210 @@ +From 83ae7d4816a49f46063ab16ffecd32b2e7e61784 Mon Sep 17 00:00:00 2001 +From: choc +Date: Fri, 15 Sep 2023 11:45:16 +0800 +Subject: [PATCH] dragresize: implement rio-like window resizing + +select window to resize then drag out an area for it to occupy +--- + dwl.c | 132 +++++++++++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 98 insertions(+), 34 deletions(-) + +diff --git a/dwl.c b/dwl.c +index dc0437e..843aa7a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -80,7 +80,7 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ +-enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ ++enum { CurNormal, CurPressed, CurSelect, CurMove, CurResize, CurBind }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND +@@ -603,15 +603,21 @@ buttonpress(struct wl_listener *listener, void *data) + + switch (event->state) { + case WLR_BUTTON_PRESSED: +- cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); +- if (locked) ++ if (locked) { ++ cursor_mode = CurPressed; + break; ++ } + +- /* Change focus if the button was _pressed_ over a client */ +- xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) +- focusclient(c, 1); ++ if (cursor_mode == CurNormal) ++ cursor_mode = CurPressed; ++ ++ if (cursor_mode != CurResize) { ++ /* Change focus if the button was _pressed_ over a client */ ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ focusclient(c, 1); ++ } + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; +@@ -624,17 +630,42 @@ buttonpress(struct wl_listener *listener, void *data) + } + break; + case WLR_BUTTON_RELEASED: ++ if (locked) { ++ cursor_mode = CurNormal; ++ break; ++ } + /* If you released any buttons, we exit interactive move/resize mode. */ +- /* TODO should reset to the pointer focus's current setcursor */ +- if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ switch (cursor_mode) { ++ case CurPressed: + cursor_mode = CurNormal; +- /* Drop the window off on its new monitor */ +- selmon = xytomon(cursor->x, cursor->y); +- setmon(grabc, selmon, 0); ++ case CurNormal: ++ break; ++ case CurSelect: + return; +- } else { ++ case CurResize: ++ /* a label can only be part of a statement - Wpedantic */ ++ { ++ int nw = abs((int) cursor->x - grabcx); ++ int nh = abs((int) cursor->y - grabcy); ++ if (nw > 1 && nh > 1) { ++ setfloating(grabc, 1); ++ resize(grabc, (struct wlr_box){.x = MIN(ROUND(cursor->x), grabcx), ++ .y = MIN(ROUND(cursor->y), grabcy), ++ .width = nw, .height = nh}, 1); ++ } ++ } ++ /* fallthrough */ ++ default: ++ /* TODO should reset to the pointer focus's current setcursor */ ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ /* Drop the window off on its new monitor */ ++ if (grabc && cursor_mode != CurBind) { ++ selmon = xytomon(cursor->x, cursor->y); ++ setmon(grabc, selmon, 0); ++ grabc = NULL; ++ } + cursor_mode = CurNormal; ++ return; + } + break; + } +@@ -1815,15 +1846,33 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ +- if (cursor_mode == CurMove) { ++ switch (cursor_mode) { ++ case CurSelect: ++ return; ++ case CurMove: + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; +- } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ case CurResize: ++ { ++ int w, h, x, y; ++ if (!grabc) ++ return; ++ w = abs(grabcx - (int)round(cursor->x)); ++ h = abs(grabcy - (int)round(cursor->y)); ++ x = MIN(grabcx, (int)round(cursor->x)) - grabc->geom.x; ++ y = MIN(grabcy, (int)round(cursor->y)) - grabc->geom.y; ++ wlr_scene_rect_set_size(grabc->border[0], w, grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[1], w, grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[2], grabc->bw, h - 2 * grabc->bw); ++ wlr_scene_rect_set_size(grabc->border[3], grabc->bw, h - 2 * grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[0]->node, x, y); ++ wlr_scene_node_set_position(&grabc->border[1]->node, x, y + h - grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[2]->node, x, y + grabc->bw); ++ wlr_scene_node_set_position(&grabc->border[3]->node, x + w - grabc->bw, y + grabc->bw); ++ return; ++ } + } + + /* If there's no client surface under the cursor, set the cursor image to a +@@ -1853,29 +1902,43 @@ motionrelative(struct wl_listener *listener, void *data) + void + moveresize(const Arg *arg) + { +- if (cursor_mode != CurNormal && cursor_mode != CurPressed) +- return; +- xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); +- if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) ++ /* Consider global select bool instead of this + CurSelect enum */ ++ bool selected = (cursor_mode == CurSelect); ++ if (!selected) { ++ if (cursor_mode != CurNormal && cursor_mode != CurPressed) ++ return; ++ xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); ++ } ++ ++ if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) { ++ grabc = NULL; ++ cursor_mode = CurNormal; + return; ++ } + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); ++ /* TODO: factor out selected bool */ + switch (cursor_mode = arg->ui) { ++ case CurResize: ++ if (!selected) break; ++ grabcx = ROUND(cursor->x); ++ grabcy = ROUND(cursor->y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "tcross"); ++ return; + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; ++ setfloating(grabc, 1); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ return; ++ default: ++ grabc = NULL; ++ cursor_mode = CurNormal; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ return; + } ++ ++ cursor_mode = CurSelect; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "crosshair"); + } + + void +@@ -2149,6 +2212,7 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->border[0]->node, 0, 0); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/en-keycodes/README.md b/dwl/dwl-patches/patches/en-keycodes/README.md new file mode 100644 index 0000000..ee3aae4 --- /dev/null +++ b/dwl/dwl-patches/patches/en-keycodes/README.md @@ -0,0 +1,11 @@ +### Description +Always use the English keymap to get keycodes, so key bindings work even when using a non-English keyboard layout. + +### Download +- [git branch](https://codeberg.org/ForzCross/dwl/src/branch/en-keycodes.patch) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/en-keycodes/en-keycodes.patch) + +### Authors +- [ForzCross](https://codeberg.org/ForzCross) +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [dimkr](https://codeberg.org/dimkr) () diff --git a/dwl/dwl-patches/patches/en-keycodes/en-keycodes.patch b/dwl/dwl-patches/patches/en-keycodes/en-keycodes.patch new file mode 100644 index 0000000..445d900 --- /dev/null +++ b/dwl/dwl-patches/patches/en-keycodes/en-keycodes.patch @@ -0,0 +1,73 @@ +From cd61fac9cb6e9d0172e2f7a01e6a514d676ba5f0 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 23:53:11 +0100 +Subject: [PATCH] Always use the English keymap to get keycodes + +--- + dwl.c | 23 +++++++++++++++++++---- + 1 file changed, 19 insertions(+), 4 deletions(-) + +diff --git a/dwl.c b/dwl.c +index def2562..c299365 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -413,6 +413,11 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct xkb_rule_names en_rules = {.layout = "us"}; ++static struct xkb_context *en_context; ++static struct xkb_keymap *en_keymap; ++static struct xkb_state *en_state; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -694,6 +699,9 @@ cleanup(void) + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); ++ xkb_state_unref(en_state); ++ xkb_keymap_unref(en_keymap); ++ xkb_context_unref(en_context); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); +@@ -1582,16 +1590,19 @@ keypress(struct wl_listener *listener, void *data) + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; ++ int nsyms, handled; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); +- +- int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); ++ xkb_state_update_key(en_state, keycode, ++ (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ ? XKB_KEY_DOWN : XKB_KEY_UP); ++ nsyms = xkb_state_key_get_syms(en_state, keycode, &syms); ++ ++ handled = 0; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + +@@ -2607,6 +2618,10 @@ setup(void) + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ ++ en_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ en_keymap = xkb_keymap_new_from_names(en_context, &en_rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ en_state = xkb_state_new(en_keymap); + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/envcfg/README.md b/dwl/dwl-patches/patches/envcfg/README.md new file mode 100644 index 0000000..07e5547 --- /dev/null +++ b/dwl/dwl-patches/patches/envcfg/README.md @@ -0,0 +1,8 @@ +### Description +Input device configuration (click method, tap-and-drag, acceleration, etc), border size and colors via environment variables. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/envcfg/envcfg.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl/dwl-patches/patches/envcfg/envcfg.patch b/dwl/dwl-patches/patches/envcfg/envcfg.patch new file mode 100644 index 0000000..ca9328d --- /dev/null +++ b/dwl/dwl-patches/patches/envcfg/envcfg.patch @@ -0,0 +1,302 @@ +From 3c3ea42cd50bfa5111be69b3c1f71afa0443bb53 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sat, 30 Dec 2023 10:49:48 +0200 +Subject: [PATCH] allow environment variables to override config.h + +--- + Makefile | 1 + + config.def.h | 10 +-- + dwl.c | 5 ++ + env.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 228 insertions(+), 5 deletions(-) + create mode 100644 env.c + +diff --git a/Makefile b/Makefile +index 9308656..c66d376 100644 +--- a/Makefile ++++ b/Makefile +@@ -22,6 +22,7 @@ dwl: dwl.o util.o + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.o: env.c + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +diff --git a/config.def.h b/config.def.h +index a784eb4..e0f10de 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,11 +6,11 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ +-static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static unsigned int borderpx = 1; /* border pixel of windows */ ++static float rootcolor[] = COLOR(0x222222ff); ++static float bordercolor[] = COLOR(0x444444ff); ++static float focuscolor[] = COLOR(0x005577ff); ++static float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + +diff --git a/dwl.c b/dwl.c +index d48bf40..de33dfe 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -429,6 +429,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "env.c" ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1082,6 +1084,8 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } ++ ++ inputconfig(device); + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +@@ -3141,6 +3145,7 @@ main(int argc, char *argv[]) + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); ++ loadtheme(); + setup(); + run(startup_cmd); + cleanup(); +diff --git a/env.c b/env.c +new file mode 100644 +index 0000000..618f81e +--- /dev/null ++++ b/env.c +@@ -0,0 +1,217 @@ ++static int ++isenabled(const char *val, int def) ++{ ++ return ((def && (!val || !val[0] || (val[0] != '0'))) || (!def && (val && val[0] && (val[0] != '0')))); ++} ++ ++static void ++setclickmethod(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ long l; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_CLICK_METHOD"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ l = strtol(val, &end, 10); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_click_set_method(libinput_device, ++ (enum libinput_config_click_method)l); ++} ++ ++static void ++settap(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_TAP"); ++ if (val) { ++ if (!val[0]) ++ return; ++ ++ libinput_device_config_tap_set_enabled(libinput_device, ++ isenabled(val, 1) ? LIBINPUT_CONFIG_TAP_ENABLED : ++ LIBINPUT_CONFIG_TAP_DISABLED); ++ } else if (tap_to_click && libinput_device_config_tap_get_finger_count(libinput_device)) ++ libinput_device_config_tap_set_enabled(libinput_device, ++ LIBINPUT_CONFIG_TAP_ENABLED); ++} ++ ++static void ++settapanddrag(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_DRAG"); ++ if (val && val[0]) ++ libinput_device_config_tap_set_drag_enabled(libinput_device, ++ isenabled(val, 1) ? LIBINPUT_CONFIG_DRAG_ENABLED : ++ LIBINPUT_CONFIG_DRAG_DISABLED); ++} ++ ++static void ++setnaturalscroll(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_NATURAL_SCROLL"); ++ if (val && val[0]) ++ libinput_device_config_scroll_set_natural_scroll_enabled( ++ libinput_device, isenabled(val, 0)); ++ else if (!val && libinput_device_config_scroll_has_natural_scroll(libinput_device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled( ++ libinput_device, natural_scrolling); ++} ++ ++static void ++setaccelprofile(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ double profile; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_ACCELERATION_PROFILE"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ profile = strtod(val, &end); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_accel_set_profile(libinput_device, ++ (enum libinput_config_accel_profile)profile); ++} ++ ++static void ++setaccelspeed(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ double accel = 0; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_ACCELERATION"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ accel = strtod(val, &end); ++ if (errno || (end && *end) || (accel < -1) || (accel > 1)) ++ return; ++ ++ libinput_device_config_accel_set_speed(libinput_device, accel); ++} ++ ++static void ++setscrollmethod(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ long l; ++ char *end = NULL; ++ ++ val = getenv("LIBINPUT_DEFAULT_SCROLL_METHOD"); ++ if (!val || !val[0]) ++ return; ++ ++ errno = 0; ++ l = strtol(val, &end, 10); ++ if (errno || (end && *end)) ++ return; ++ ++ libinput_device_config_scroll_set_method(libinput_device, ++ (enum libinput_config_scroll_method)l); ++} ++ ++static void ++setdwt(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_DISABLE_WHILE_TYPING"); ++ if (val && val[0]) ++ libinput_device_config_dwt_set_enabled(libinput_device, ++ isenabled(val, false) ? LIBINPUT_CONFIG_DWT_ENABLED : ++ LIBINPUT_CONFIG_DWT_DISABLED); ++} ++ ++static void ++setmiddleemul(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_MIDDLE_EMULATION"); ++ if (val && val[0]) ++ libinput_device_config_middle_emulation_set_enabled(libinput_device, ++ isenabled(val, false) ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : ++ LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); ++} ++ ++static void ++setlefthanded(struct libinput_device *libinput_device) ++{ ++ const char *val; ++ ++ val = getenv("LIBINPUT_DEFAULT_LEFT_HANDED"); ++ if (val && val[0]) ++ libinput_device_config_left_handed_set(libinput_device, ++ isenabled(val, 0)); ++} ++ ++static void ++inputconfig(struct libinput_device *libinput_device) ++{ ++ setclickmethod(libinput_device); ++ settap(libinput_device); ++ settapanddrag(libinput_device); ++ setnaturalscroll(libinput_device); ++ setaccelprofile(libinput_device); ++ setaccelspeed(libinput_device); ++ setscrollmethod(libinput_device); ++ setdwt(libinput_device); ++ setmiddleemul(libinput_device); ++ setlefthanded(libinput_device); ++} ++ ++static void ++parsecolor(const char *val, float color[4]) ++{ ++ uint8_t r, g, b; ++ if (sscanf(val, "#%02hhx%02hhx%02hhx", &r, &g, &b) == 3) { ++ color[0] = (float)r / 0xFF; ++ color[1] = (float)g / 0xFF; ++ color[2] = (float)b / 0xFF; ++ color[3] = 1.0; ++ } ++} ++ ++static void ++loadtheme(void) ++{ ++ const char *val; ++ unsigned int tmp; ++ ++ val = getenv("DWL_ROOT_COLOR"); ++ if (val) ++ parsecolor(val, rootcolor); ++ ++ val = getenv("DWL_BORDER_COLOR"); ++ if (val) ++ parsecolor(val, bordercolor); ++ ++ val = getenv("DWL_FOCUS_COLOR"); ++ if (val) ++ parsecolor(val, focuscolor); ++ ++ val = getenv("DWL_URGENT_COLOR"); ++ if (val) ++ parsecolor(val, urgentcolor); ++ ++ val = getenv("DWL_BORDER"); ++ if (val && sscanf(val, "%u", &tmp) == 1) ++ borderpx = tmp; ++} +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/fakefullscreenclient/README.md b/dwl/dwl-patches/patches/fakefullscreenclient/README.md new file mode 100644 index 0000000..6a98f0d --- /dev/null +++ b/dwl/dwl-patches/patches/fakefullscreenclient/README.md @@ -0,0 +1,8 @@ +### Description +Allow setting fake fullscreen per client + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/fakefullscreenclient) +- [2024-03-29](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fakefullscreenclient/fakefullscreenclient.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch b/dwl/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch new file mode 100644 index 0000000..848f350 --- /dev/null +++ b/dwl/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch @@ -0,0 +1,87 @@ +From 2ec6d0c668b4daee601337f8da45ccfa3a7d5fc6 Mon Sep 17 00:00:00 2001 +From: choc +Date: Fri, 29 Mar 2024 22:50:00 +0800 +Subject: [PATCH] implement fakefullscreenclient + +--- + config.def.h | 1 + + dwl.c | 23 ++++++++++++++++++++++- + 2 files changed, 23 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..8c220eb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -137,6 +137,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, togglefakefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 5867b0c..1e78491 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -137,7 +137,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, isfakefullscreen; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -318,6 +318,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 setfakefullscreen(Client *c, int fullscreen); + static void setgamma(struct wl_listener *listener, void *data); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); +@@ -332,6 +333,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefakefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2181,6 +2183,17 @@ setfullscreen(Client *c, int fullscreen) + printstatus(); + } + ++void ++setfakefullscreen(Client *c, int fullscreen) ++{ ++ c->isfakefullscreen = fullscreen; ++ if (!c->mon) ++ return; ++ if (c->isfullscreen) ++ setfullscreen(c, 0); ++ client_set_fullscreen(c, fullscreen); ++} ++ + void + setgamma(struct wl_listener *listener, void *data) + { +@@ -2620,6 +2633,14 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefakefullscreen(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ if (sel) ++ setfakefullscreen(sel, !sel->isfakefullscreen); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/fallback/README.md b/dwl/dwl-patches/patches/fallback/README.md new file mode 100644 index 0000000..e9de8f4 --- /dev/null +++ b/dwl/dwl-patches/patches/fallback/README.md @@ -0,0 +1,8 @@ +### Description +Tries a different display mode if the preferred mode doesn't work. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fallback/fallback.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl/dwl-patches/patches/fallback/fallback.patch b/dwl/dwl-patches/patches/fallback/fallback.patch new file mode 100644 index 0000000..b734625 --- /dev/null +++ b/dwl/dwl-patches/patches/fallback/fallback.patch @@ -0,0 +1,44 @@ +From a94c52af879027e654f67c36621a8c9b2f338f56 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sat, 2 Dec 2023 10:36:35 +0200 +Subject: [PATCH] fall back to a lower output mode if needed + (swaywm/sway@4cdc4ac) + +--- + dwl.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index d48bf40..7719f7e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -920,6 +920,7 @@ createmon(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; ++ struct wlr_output_mode *preferred_mode, *mode; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +@@ -956,7 +957,17 @@ createmon(struct wl_listener *listener, void *data) + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ +- wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); ++ preferred_mode = wlr_output_preferred_mode(wlr_output); ++ wlr_output_state_set_mode(&state, preferred_mode); ++ if (!wlr_output_test_state(wlr_output, &state) && !wl_list_empty(&wlr_output->modes)) { ++ wl_list_for_each(mode, &wlr_output->modes, link) { ++ if (mode != preferred_mode) { ++ wlr_output_state_set_mode(&state, mode); ++ if (wlr_output_test_state(wlr_output, &state)) ++ break; ++ } ++ } ++ } + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/focusdir/README.md b/dwl/dwl-patches/patches/focusdir/README.md new file mode 100644 index 0000000..a2ed478 --- /dev/null +++ b/dwl/dwl-patches/patches/focusdir/README.md @@ -0,0 +1,9 @@ +### Description +Focus the window to the left, right, above or below the current focused window + +### Download +- [git branch](https://codeberg.org/ldev105/dwl/src/branch/focusdir) +- [2023-01-22](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/focusdir/focusdir.patch) + +### Authors +- [ldev105](https://codeberg.org/ldev105) diff --git a/dwl/dwl-patches/patches/focusdir/focusdir.patch b/dwl/dwl-patches/patches/focusdir/focusdir.patch new file mode 100644 index 0000000..72b8c5a --- /dev/null +++ b/dwl/dwl-patches/patches/focusdir/focusdir.patch @@ -0,0 +1,98 @@ +From a0e71a687b7fcaebdaf1da80c09bf5563bff46b1 Mon Sep 17 00:00:00 2001 +From: ldev +Date: Mon, 12 Feb 2024 21:50:24 +0100 +Subject: [PATCH] focusdir + +--- + config.def.h | 4 ++++ + dwl.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..2a1a82e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -124,6 +124,10 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_h, focusdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_l, focusdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_k, focusdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_j, focusdir, {.ui = 3} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index bf02a6d..64d5de7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -268,6 +269,7 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); ++static void focusdir(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -1271,6 +1273,49 @@ focusstack(const Arg *arg) + focusclient(c, 1); + } + ++void focusdir(const Arg *arg) ++{ ++ /* Focus the left, right, up, down client relative to the current focused client on selmon */ ++ Client *c, *sel = focustop(selmon); ++ if (!sel || sel->isfullscreen) ++ return; ++ ++ int dist=INT_MAX; ++ Client *newsel = NULL; ++ int newdist=INT_MAX; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon)) ++ continue; /* skip non visible windows */ ++ ++ if (arg->ui == 0 && sel->geom.x <= c->geom.x) { ++ /* Client isn't on our left */ ++ continue; ++ } ++ if (arg->ui == 1 && sel->geom.x >= c->geom.x) { ++ /* Client isn't on our right */ ++ continue; ++ } ++ if (arg->ui == 2 && sel->geom.y <= c->geom.y) { ++ /* Client isn't above us */ ++ continue; ++ } ++ if (arg->ui == 3 && sel->geom.y >= c->geom.y) { ++ /* Client isn't below us */ ++ continue; ++ } ++ ++ dist=abs(sel->geom.x-c->geom.x)+abs(sel->geom.y-c->geom.y); ++ if (dist < newdist){ ++ newdist = dist; ++ newsel=c; ++ } ++ } ++ if (newsel != NULL){ ++ focusclient(newsel, 1); ++ } ++} ++ ++ + /* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/follow/README.md b/dwl/dwl-patches/patches/follow/README.md new file mode 100644 index 0000000..d2597cd --- /dev/null +++ b/dwl/dwl-patches/patches/follow/README.md @@ -0,0 +1,10 @@ +### Description +An extremely simple patch that adds the option to change DWL's window sending behavior; when active, sent windows will be followed, i.e. when a window is sent to another tag, the view changes to that tag.
No dependencies. + +![](follow.webp) + +### Download +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/follow/follow.patch)
Targets tag v0.7, but also works on git master and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) diff --git a/dwl/dwl-patches/patches/follow/follow.patch b/dwl/dwl-patches/patches/follow/follow.patch new file mode 100644 index 0000000..d260a75 --- /dev/null +++ b/dwl/dwl-patches/patches/follow/follow.patch @@ -0,0 +1,38 @@ +From c4de366aa5e2a465f5e2963f4ffed7adde77929a Mon Sep 17 00:00:00 2001 +From: moonsabre +Date: Thu, 13 Mar 2025 14:36:49 -0700 +Subject: [PATCH] Add parameter for following sent windows. + +--- + config.def.h | 1 + + dwl.c | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..cdbdc72 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int follow = 1; /* 1 means follow windows when sent to another tag */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index a2711f6..d2cf8ba 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2677,6 +2677,8 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ if (follow == 1) ++ view(arg); + printstatus(); + } + +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/follow/follow.webp b/dwl/dwl-patches/patches/follow/follow.webp new file mode 100644 index 0000000..b629315 Binary files /dev/null and b/dwl/dwl-patches/patches/follow/follow.webp differ diff --git a/dwl/dwl-patches/patches/foreign-toplevel-management/README.md b/dwl/dwl-patches/patches/foreign-toplevel-management/README.md new file mode 100644 index 0000000..6e30328 --- /dev/null +++ b/dwl/dwl-patches/patches/foreign-toplevel-management/README.md @@ -0,0 +1,9 @@ +### Description +Implement `foreign-toplevel-management`, it add handlers for activate, close, fullscreen and destroy request events, it's missing minimize and maximize request handlers. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6-a/foreign-toplevel-management) +- [2024-05-02](https://codeberg.org/dwl/dwl-patches/raw/commit/e58c3ec41a39df934d2998161d7187ac965ea77a/foreign-toplevel-management/foreign-toplevel-management.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch b/dwl/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch new file mode 100644 index 0000000..3dfa14f --- /dev/null +++ b/dwl/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch @@ -0,0 +1,507 @@ +From dc63d64cd69f515013464b3feb8acb411f5a348f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 2 May 2024 17:25:45 -0500 +Subject: [PATCH] implement foreign toplevel management + +--- + Makefile | 5 +- + dwl.c | 93 +++++- + ...oreign-toplevel-management-unstable-v1.xml | 270 ++++++++++++++++++ + 3 files changed, 366 insertions(+), 2 deletions(-) + create mode 100644 protocols/wlr-foreign-toplevel-management-unstable-v1.xml + +diff --git a/Makefile b/Makefile +index a67fdd3..e0e1260 100644 +--- a/Makefile ++++ b/Makefile +@@ -16,7 +16,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ +-dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.o: dwl.c config.mk config.h client.h cursor-shape-v1-protocol.h pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h wlr-foreign-toplevel-management-unstable-v1-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -37,6 +37,9 @@ wlr-layer-shell-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++wlr-foreign-toplevel-management-unstable-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ protocols/wlr-foreign-toplevel-management-unstable-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/dwl.c b/dwl.c +index bf763df..648616d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -126,6 +127,11 @@ typedef struct { + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; ++ struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; ++ struct wl_listener factivate; ++ struct wl_listener fclose; ++ struct wl_listener ffullscreen; ++ struct wl_listener fdestroy; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; + #ifdef XWAYLAND +@@ -347,6 +353,11 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void createforeigntoplevel(Client *c); ++static void factivatenotify(struct wl_listener *listener, void *data); ++static void fclosenotify(struct wl_listener *listener, void *data); ++static void fdestroynotify(struct wl_listener *listener, void *data); ++static void ffullscreennotify(struct wl_listener *listener, void *data); + + /* variables */ + static const char broken[] = "broken"; +@@ -392,6 +403,8 @@ static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + ++static struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_mgr; ++ + static struct wlr_seat *seat; + static KeyboardGroup kb_group = {0}; + static KeyboardGroup vkb_group = {0}; +@@ -458,6 +471,11 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ if (c->foreign_toplevel) { ++ wlr_foreign_toplevel_handle_v1_set_app_id(c->foreign_toplevel, appid); ++ wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); ++ } ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { +@@ -1288,6 +1306,8 @@ focusclient(Client *c, int lift) + client_set_border_color(old_c, bordercolor); + + client_activate_surface(old, 0); ++ if (old_c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_set_activated(old_c->foreign_toplevel, 0); + } + } + printstatus(); +@@ -1306,6 +1326,8 @@ focusclient(Client *c, int lift) + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_set_activated(c->foreign_toplevel, 1); + } + + void +@@ -1599,6 +1621,8 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ createforeigntoplevel(c); ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -2232,12 +2256,17 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ +- if (oldmon) ++ if (oldmon) { ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_output_leave(c->foreign_toplevel, oldmon->wlr_output); + arrange(oldmon); ++ } + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ ++ if (c->foreign_toplevel) ++ wlr_foreign_toplevel_handle_v1_output_enter(c->foreign_toplevel, m->wlr_output); + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +@@ -2351,6 +2380,9 @@ setup(void) + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + ++ /* Initializes foreign toplevel management */ ++ foreign_toplevel_mgr = wlr_foreign_toplevel_manager_v1_create(dpy); ++ + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(); +@@ -2691,6 +2723,11 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (c->foreign_toplevel) { ++ wlr_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel); ++ c->foreign_toplevel = NULL; ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2802,6 +2839,12 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); ++ if (c->foreign_toplevel) { ++ const char *title; ++ if (!(title = client_get_title(c))) ++ title = broken; ++ wlr_foreign_toplevel_handle_v1_set_title(c->foreign_toplevel, title); ++ } + if (c == focustop(c->mon)) + printstatus(); + } +@@ -2929,6 +2972,54 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++createforeigntoplevel(Client *c) ++{ ++ c->foreign_toplevel = wlr_foreign_toplevel_handle_v1_create(foreign_toplevel_mgr); ++ ++ LISTEN(&c->foreign_toplevel->events.request_activate, &c->factivate, factivatenotify); ++ LISTEN(&c->foreign_toplevel->events.request_close, &c->fclose, fclosenotify); ++ LISTEN(&c->foreign_toplevel->events.request_fullscreen, &c->ffullscreen, ffullscreennotify); ++ LISTEN(&c->foreign_toplevel->events.destroy, &c->fdestroy, fdestroynotify); ++} ++ ++void ++factivatenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, factivate); ++ if (c->mon == selmon) { ++ c->tags = c->mon->tagset[c->mon->seltags]; ++ } else { ++ setmon(c, selmon, 0); ++ } ++ focusclient(c, 1); ++ arrange(c->mon); ++} ++ ++void ++fclosenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, fclose); ++ client_send_close(c); ++} ++ ++void ++ffullscreennotify(struct wl_listener *listener, void *data) { ++ Client *c = wl_container_of(listener, c, ffullscreen); ++ struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; ++ setfullscreen(c, event->fullscreen); ++} ++ ++void ++fdestroynotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, fdestroy); ++ wl_list_remove(&c->factivate.link); ++ wl_list_remove(&c->fclose.link); ++ wl_list_remove(&c->ffullscreen.link); ++ wl_list_remove(&c->fdestroy.link); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +diff --git a/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml +new file mode 100644 +index 0000000..44505bb +--- /dev/null ++++ b/protocols/wlr-foreign-toplevel-management-unstable-v1.xml +@@ -0,0 +1,270 @@ ++ ++ ++ ++ Copyright © 2018 Ilia Bozhinov ++ ++ Permission to use, copy, modify, distribute, and sell this ++ software and its documentation for any purpose is hereby granted ++ without fee, provided that the above copyright notice appear in ++ all copies and that both that copyright notice and this permission ++ notice appear in supporting documentation, and that the name of ++ the copyright holders not be used in advertising or publicity ++ pertaining to distribution of the software without specific, ++ written prior permission. The copyright holders make no ++ representations about the suitability of this software for any ++ purpose. It is provided "as is" without express or implied ++ warranty. ++ ++ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS ++ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND ++ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY ++ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ++ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ++ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF ++ THIS SOFTWARE. ++ ++ ++ ++ ++ The purpose of this protocol is to enable the creation of taskbars ++ and docks by providing them with a list of opened applications and ++ letting them request certain actions on them, like maximizing, etc. ++ ++ After a client binds the zwlr_foreign_toplevel_manager_v1, each opened ++ toplevel window will be sent via the toplevel event ++ ++ ++ ++ ++ This event is emitted whenever a new toplevel window is created. It ++ is emitted for all toplevels, regardless of the app that has created ++ them. ++ ++ All initial details of the toplevel(title, app_id, states, etc.) will ++ be sent immediately after this event via the corresponding events in ++ zwlr_foreign_toplevel_handle_v1. ++ ++ ++ ++ ++ ++ ++ Indicates the client no longer wishes to receive events for new toplevels. ++ However the compositor may emit further toplevel_created events, until ++ the finished event is emitted. ++ ++ The client must not send any more requests after this one. ++ ++ ++ ++ ++ ++ This event indicates that the compositor is done sending events to the ++ zwlr_foreign_toplevel_manager_v1. The server will destroy the object ++ immediately after sending this request, so it will become invalid and ++ the client should free any resources associated with it. ++ ++ ++ ++ ++ ++ ++ A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel ++ window. Each app may have multiple opened toplevels. ++ ++ Each toplevel has a list of outputs it is visible on, conveyed to the ++ client with the output_enter and output_leave events. ++ ++ ++ ++ ++ This event is emitted whenever the title of the toplevel changes. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the app-id of the toplevel changes. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the toplevel becomes visible on ++ the given output. A toplevel may be visible on multiple outputs. ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the toplevel stops being visible on ++ the given output. It is guaranteed that an entered-output event ++ with the same output has been emitted before this event. ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be maximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be unmaximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be minimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Requests that the toplevel be unminimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ Request that this toplevel be activated on the given seat. ++ There is no guarantee the toplevel will be actually activated. ++ ++ ++ ++ ++ ++ ++ The different states that a toplevel can have. These have the same meaning ++ as the states with the same names defined in xdg-toplevel ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 ++ is created and each time the toplevel state changes, either because of a ++ compositor action or because of a request in this protocol. ++ ++ ++ ++ ++ ++ ++ ++ This event is sent after all changes in the toplevel state have been ++ sent. ++ ++ This allows changes to the zwlr_foreign_toplevel_handle_v1 properties ++ to be seen as atomic, even if they happen via multiple events. ++ ++ ++ ++ ++ ++ Send a request to the toplevel to close itself. The compositor would ++ typically use a shell-specific method to carry out this request, for ++ example by sending the xdg_toplevel.close event. However, this gives ++ no guarantees the toplevel will actually be destroyed. If and when ++ this happens, the zwlr_foreign_toplevel_handle_v1.closed event will ++ be emitted. ++ ++ ++ ++ ++ ++ The rectangle of the surface specified in this request corresponds to ++ the place where the app using this protocol represents the given toplevel. ++ It can be used by the compositor as a hint for some operations, e.g ++ minimizing. The client is however not required to set this, in which ++ case the compositor is free to decide some default value. ++ ++ If the client specifies more than one rectangle, only the last one is ++ considered. ++ ++ The dimensions are given in surface-local coordinates. ++ Setting width=height=0 removes the already-set rectangle. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ This event means the toplevel has been destroyed. It is guaranteed there ++ won't be any more events for this zwlr_foreign_toplevel_handle_v1. The ++ toplevel itself becomes inert so any requests will be ignored except the ++ destroy request. ++ ++ ++ ++ ++ ++ Destroys the zwlr_foreign_toplevel_handle_v1 object. ++ ++ This request should be called either when the client does not want to ++ use the toplevel anymore or after the closed event to finalize the ++ destruction of the object. ++ ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be fullscreened on the given output. If the ++ fullscreen state and/or the outputs the toplevel is visible on actually ++ change, this will be indicated by the state and output_enter/leave ++ events. ++ ++ The output parameter is only a hint to the compositor. Also, if output ++ is NULL, the compositor should decide which output the toplevel will be ++ fullscreened on, if at all. ++ ++ ++ ++ ++ ++ ++ Requests that the toplevel be unfullscreened. If the fullscreen state ++ actually changes, this will be indicated by the state event. ++ ++ ++ ++ ++ ++ ++ ++ This event is emitted whenever the parent of the toplevel changes. ++ ++ No event is emitted when the parent handle is destroyed by the client. ++ ++ ++ ++ ++ +-- +2.43.2 + diff --git a/dwl/dwl-patches/patches/fullscreenadaptivesync/README.md b/dwl/dwl-patches/patches/fullscreenadaptivesync/README.md new file mode 100644 index 0000000..6f7118c --- /dev/null +++ b/dwl/dwl-patches/patches/fullscreenadaptivesync/README.md @@ -0,0 +1,29 @@ +### Description + +# fullscreenadaptivesync — Enables adaptive sync/VRR when a client is fullscreen. + +Adds a function that automatically enables adaptive sync/VRR when a fullscreen client is detected, and disables it once the client exits fullscreen. + +--- + +1. **Why** + - Some VRR implementations can introduce distracting flickering when the display’s refresh rate is synced with the application’s framerate. While VRR is useful for some applications (especially fullscreen games), this patch automates the toggling of VRR whenever a client enters or exits fullscreen. + +2. **Requirements** + - A FreeSync/G-Sync capable monitor + - GPU/driver support for adaptive sync + +3. **How it works** + - When a client enters fullscreen, adaptive sync is automatically enabled. + - When the client exits fullscreen, adaptive sync is disabled again. + +4. **togglefullscreenadaptivesync** + - Adds a switch to enable or disable the fullscreenadaptivesync behavior. + - Enabled by default. + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/fullscreenadaptivesync) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch b/dwl/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch new file mode 100644 index 0000000..ccc012b --- /dev/null +++ b/dwl/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch @@ -0,0 +1,122 @@ +From c003f450c197a0c960bbb355511f8dca7a35e3c3 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sat, 4 Jan 2025 14:24:59 +0200 +Subject: [PATCH] add fullscreenadaptivesync + +--- + config.def.h | 1 + + dwl.c | 40 ++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..886f1ab 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -142,6 +142,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_F5, togglefullscreenadaptivesync, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..7be05ef 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -322,6 +322,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void set_adaptive_sync(Monitor *m, int enabled); + 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); +@@ -340,6 +341,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglefullscreenadaptivesync(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int fullscreen_adaptive_sync_enabled = 1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -2269,6 +2273,31 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++set_adaptive_sync(Monitor *m, int enable) ++{ ++ struct wlr_output_state state; ++ struct wlr_output_configuration_v1 *config; ++ struct wlr_output_configuration_head_v1 *config_head; ++ ++ if (!m || !m->wlr_output || !m->wlr_output->enabled ++ || !fullscreen_adaptive_sync_enabled) ++ return; ++ ++ config = wlr_output_configuration_v1_create(); ++ config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); ++ ++ /* Set and commit the adaptive sync state change */ ++ wlr_output_state_init(&state); ++ wlr_output_state_set_adaptive_sync_enabled(&state, enable); ++ wlr_output_commit_state(m->wlr_output, &state); ++ wlr_output_state_finish(&state); ++ ++ /* Broadcast the adaptive sync state change to output_mgr */ ++ config_head->state.adaptive_sync_enabled = enable; ++ wlr_output_manager_v1_set_configuration(output_mgr, config); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2332,10 +2361,12 @@ setfullscreen(Client *c, int fullscreen) + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); ++ set_adaptive_sync(c->mon, 1); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); ++ set_adaptive_sync(c->mon, 0); + } + arrange(c->mon); + printstatus(); +@@ -2739,6 +2770,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglefullscreenadaptivesync(const Arg *arg) ++{ ++ fullscreen_adaptive_sync_enabled = !fullscreen_adaptive_sync_enabled; ++} ++ + void + toggletag(const Arg *arg) + { +@@ -2809,6 +2846,9 @@ unmapnotify(struct wl_listener *listener, void *data) + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } ++ /* Toggle adaptive sync off when fullscreen client is unmapped */ ++ if (c->isfullscreen) ++ set_adaptive_sync(selmon, 0); + + wlr_scene_node_destroy(&c->scene->node); + printstatus(); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/gaplessgrid/README.md b/dwl/dwl-patches/patches/gaplessgrid/README.md new file mode 100644 index 0000000..6921d54 --- /dev/null +++ b/dwl/dwl-patches/patches/gaplessgrid/README.md @@ -0,0 +1,19 @@ +### Description +Arranges windows in a grid. Except it adjusts the number of windows in the first few columns to avoid empty cells. + +On widescreens (w > 2*h), it splits to three columns before splitting rows. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20240714.patch) +- [2023-08-01](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20230801.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/gaplessgrid) + +## Pre-codeberg +- [2023-11-14](https://github.com/djpohly/dwl/compare/main...Sneethe:gaplessgrid.patch) +- [2021-07-27](https://github.com/djpohly/dwl/compare/main...vnepogodin:gaplessgrid.patch) + +### Authors +- [Sneethe](https://github.com/Sneethe/) +- [Vladislav Nepogodin](https://github.com/vnepogodin) +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani/) (Revived to codeberg) diff --git a/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch new file mode 100644 index 0000000..2ade7da --- /dev/null +++ b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch @@ -0,0 +1,97 @@ +From 958cf2c2415927e2f7b471da9ada7c6e7d169a63 Mon Sep 17 00:00:00 2001 +From: Dhruva Sambrani <44899822+DhruvaSambrani@users.noreply.github.com> +Date: Mon, 8 Jan 2024 16:11:30 +0100 +Subject: [PATCH] revive gaplessgrid + +--- + config.def.h | 2 ++ + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 48 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a8ed61d..7400b7f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -134,6 +135,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 632dabf..4d810f7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -268,6 +268,7 @@ static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static void gaplessgrid(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -1311,6 +1312,51 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch new file mode 100644 index 0000000..cf430b9 --- /dev/null +++ b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch @@ -0,0 +1,89 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..014a909 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..dae1d1a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -287,6 +287,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static void gaplessgrid(Monitor *m); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -1467,6 +1468,56 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ ++ /* widescreen is better if 3 columns */ ++ if (n >= 3 && n <= 6 && (m->w.width / m->w.height) > 1) ++ cols = 3; ++ ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid.patch b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid.patch new file mode 100644 index 0000000..2af1072 --- /dev/null +++ b/dwl/dwl-patches/patches/gaplessgrid/gaplessgrid.patch @@ -0,0 +1,89 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..014a909 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "###", gaplessgrid }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_g, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index dc0c861..875d8cd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -292,6 +292,7 @@ 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 gaplessgrid(Monitor *m); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -1510,6 +1511,56 @@ handlesig(int signo) + } + } + ++void ++gaplessgrid(Monitor *m) ++{ ++ unsigned int n = 0, i = 0, ch, cw, cn, rn, rows, cols; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating) ++ n++; ++ if (n == 0) ++ return; ++ ++ /* grid dimensions */ ++ for (cols = 0; cols <= (n / 2); cols++) ++ if ((cols * cols) >= n) ++ break; ++ ++ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */ ++ cols = 2; ++ ++ /* widescreen is better if 3 columns */ ++ if (n >= 3 && n <= 6 && (m->w.width / m->w.height) > 1) ++ cols = 3; ++ ++ rows = n / cols; ++ ++ /* window geometries */ ++ cw = cols ? m->w.width / cols : m->w.width; ++ cn = 0; /* current column number */ ++ rn = 0; /* current row number */ ++ wl_list_for_each(c, &clients, link) { ++ unsigned int cx, cy; ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ if ((i / rows + 1) > (cols - n % cols)) ++ rows = n / cols + 1; ++ ch = rows ? m->w.height / rows : m->w.height; ++ cx = m->w.x + cn * cw; ++ cy = m->w.y + rn * ch; ++ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0); ++ rn++; ++ if (rn >= rows) { ++ rn = 0; ++ cn++; ++ } ++ i++; ++ } ++} ++ + void + incnmaster(const Arg *arg) + { diff --git a/dwl/dwl-patches/patches/gaps/README.md b/dwl/dwl-patches/patches/gaps/README.md new file mode 100644 index 0000000..4925a47 --- /dev/null +++ b/dwl/dwl-patches/patches/gaps/README.md @@ -0,0 +1,14 @@ +### Description +Adds gaps between clients, providing the ability to disable them at run-time. + +`smartgaps` can also be changed to remove gaps when there is only one client present. + +### Download +- [git branch](https://codeberg.org/bigman/dwl/src/branch/gaps) +- [2024-07-12](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaps/gaps.patch) + +### Authors +- [peesock](https://codeberg.org/bigman) +- [sewn](https://codeberg.org/sewn) +- [Serene Void](https://github.com/serenevoid) +- [Rayan Nakib](https://nakibrayan2.pages.dev) diff --git a/dwl/dwl-patches/patches/gaps/gaps.patch b/dwl/dwl-patches/patches/gaps/gaps.patch new file mode 100644 index 0000000..c025baf --- /dev/null +++ b/dwl/dwl-patches/patches/gaps/gaps.patch @@ -0,0 +1,127 @@ +From 50e3dd4746b6cb719efb9f8213b94ac52a5320d9 Mon Sep 17 00:00:00 2001 +From: peesock +Date: Mon, 24 Jun 2024 20:06:42 -0700 +Subject: [PATCH] gaps! + +Co-authored-by: sewn +Co-authored-by: serenevoid +--- + config.def.h | 4 ++++ + dwl.c | 34 ++++++++++++++++++++++++++-------- + 2 files changed, 30 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..b388b4e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,9 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 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 = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +@@ -135,6 +138,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, ++ { MODKEY, XKB_KEY_g, togglegaps, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, 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 dc0437e..dc851df 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -199,6 +199,7 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -336,6 +337,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -949,6 +951,8 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ ++ m->gaps = gaps; ++ + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { +@@ -2638,7 +2642,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + +@@ -2647,23 +2651,30 @@ tile(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; + + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; +- i = my = ty = 0; ++ i = 0; ++ my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, ++ .width = mw - 2*gappx*e, .height = h}, 0); ++ my += c->geom.height + gappx*e; + } else { ++ r = n - i; ++ h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ ty += c->geom.height + gappx*e; + } + i++; + } +@@ -2686,6 +2697,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ selmon->gaps = !selmon->gaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/genericgaps/README.md b/dwl/dwl-patches/patches/genericgaps/README.md new file mode 100644 index 0000000..f27897d --- /dev/null +++ b/dwl/dwl-patches/patches/genericgaps/README.md @@ -0,0 +1,25 @@ +### Description + +This patch adds gaps around windows and works with any layout (the layout code +does not need to know about the gaps). This patch is a modified version of +[vanitygaps][vanitygaps] patch. + +This works by allowing layout code to place clients normally without gaps, and +then correcting positions and sizes of clients afterwards to add gaps around +them. This approach is very flexible but there is one small downside: you will +always have "outer" gaps (between edges of a monitor and a window) if "inner" +gaps are non-zero. But for me it's not a problem because I always want "outer" +gaps to be as big or bigger than "inner" gaps anyway. + +[vanitygaps]: /dwl/dwl-patches/src/branch/main/patches/vanitygaps + +### Download + +- [0.7 2025-02-11](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps-0.7.patch): + added support for `smartgaps` and `monoclegaps` settings and removed the suck + from `arrange()` function +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/genericgaps/genericgaps-0.7.patch b/dwl/dwl-patches/patches/genericgaps/genericgaps-0.7.patch new file mode 100644 index 0000000..ff27889 --- /dev/null +++ b/dwl/dwl-patches/patches/genericgaps/genericgaps-0.7.patch @@ -0,0 +1,375 @@ +From a11488df97c0592486c3ce86d9c5dbddb0f88524 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Mon, 10 Feb 2025 23:32:59 +0100 +Subject: [PATCH] Add gaps to windows regardless of layout + +--- + config.def.h | 23 ++++++ + dwl.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 221 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..7b50d9d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,14 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int enablegaps = 1; /* 1 means gaps are enabled */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +140,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..9bf4651 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -200,6 +200,11 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int enablegaps; /* enable gaps, used by togglegaps */ ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -246,6 +251,7 @@ typedef struct { + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); + static void arrange(Monitor *m); ++void arrangegaps(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); +@@ -273,6 +279,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +300,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -313,6 +327,7 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); ++static void preparegaps(Monitor *m); + static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); +@@ -327,6 +342,7 @@ 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +356,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +430,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int resizelock = 0; /* do not actually resize during arrange */ ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -515,12 +534,52 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) ++ if (m->lt[m->sellt]->arrange) { ++ preparegaps(m); + m->lt[m->sellt]->arrange(m); ++ arrangegaps(m); ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } + ++void ++arrangegaps(Monitor *m) ++{ ++ Client *c; ++ int n, gaps; ++ ++ if (!m->enablegaps) ++ return; ++ ++ resizelock = 0; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ n++; ++ } ++ ++ gaps = !(smartgaps && n == 1) && ++ (monoclegaps || m->lt[m->sellt]->arrange != monocle); ++ if (gaps) { ++ m->w.width += m->gappih + 2 * m->gappoh; ++ m->w.height += m->gappiv + 2 * m->gappov; ++ } ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (gaps) { ++ c->geom.x += c->mon->gappih + c->mon->gappoh; ++ c->geom.y += c->mon->gappiv + c->mon->gappov; ++ c->geom.width -= c->mon->gappih; ++ c->geom.height -= c->mon->gappiv; ++ } ++ resize(c, c->geom, 0); ++ } ++} ++ + void + arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) + { +@@ -991,6 +1050,12 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->enablegaps = enablegaps; ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1173,6 +1238,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1526,6 +1597,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2034,6 +2182,31 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + ++void ++preparegaps(Monitor *m) ++{ ++ Client *c; ++ int n; ++ ++ if (!m->enablegaps) ++ return; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ n++; ++ } ++ ++ resizelock = 1; ++ ++ if ((smartgaps && n == 1) || (!monoclegaps && m->lt[m->sellt]->arrange == monocle)) ++ return; ++ ++ m->w.width -= m->gappih + 2 * m->gappoh; ++ m->w.height -= m->gappiv + 2 * m->gappov; ++} ++ + void + printstatus(void) + { +@@ -2185,6 +2358,11 @@ resize(Client *c, struct wlr_box geo, int interact) + struct wlr_box *bbox; + struct wlr_box clip; + ++ if (resizelock) { ++ c->geom = geo; ++ return; ++ } ++ + if (!c->mon || !client_surface(c)->mapped) + return; + +@@ -2354,6 +2532,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2741,6 +2929,15 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ if (!selmon) ++ return; ++ selmon->enablegaps = !selmon->enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/genericgaps/genericgaps.patch b/dwl/dwl-patches/patches/genericgaps/genericgaps.patch new file mode 100644 index 0000000..aa62b92 --- /dev/null +++ b/dwl/dwl-patches/patches/genericgaps/genericgaps.patch @@ -0,0 +1,350 @@ +From f2a1b84369266d252fbea57c6f1eb64253617452 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Thu, 12 Dec 2024 22:44:53 +0100 +Subject: [PATCH] Add genericgaps + +--- + config.def.h | 22 +++++++ + dwl.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 190 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..930af12 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,12 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +139,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index def2562..c14ae43 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -107,6 +107,7 @@ typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ ++ int interact; + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; +@@ -200,6 +201,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -273,6 +278,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +299,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -320,13 +333,15 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resizeapply(Client *c, struct wlr_box geo, int interact); ++static void resizenoapply(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); + 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +355,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +429,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ ++static void (*resize)(Client *c, struct wlr_box geo, int interact) = resizeapply; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -481,9 +500,25 @@ applyrules(Client *c) + setmon(c, mon, newtags); + } + ++void ++applygaps(Client *c) ++{ ++ struct wlr_box geom = c->geom; ++ ++ if (!c->mon) ++ return; ++ ++ geom.x += c->mon->gappih + c->mon->gappoh; ++ geom.y += c->mon->gappiv + c->mon->gappov; ++ geom.width -= c->mon->gappih; ++ geom.height -= c->mon->gappiv; ++ resize(c, geom, 0); ++} ++ + void + arrange(Monitor *m) + { ++ int save_width, save_height; + Client *c; + + if (!m->wlr_output->enabled) +@@ -515,8 +550,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) ++ if (m->lt[m->sellt]->arrange) { ++ save_width = m->w.width; ++ save_height = m->w.height; ++ if (enablegaps) { ++ m->w.width -= m->gappih + 2 * m->gappoh; ++ m->w.height -= m->gappiv + 2 * m->gappov; ++ } ++ resize = resizenoapply; + m->lt[m->sellt]->arrange(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (enablegaps) ++ applygaps(c); ++ resizeapply(c, c->geom, c->interact); ++ } ++ m->w.width = save_width; ++ m->w.height = save_height; ++ resize = resizeapply; ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -993,6 +1046,11 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ ++ ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { +@@ -1173,6 +1231,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1526,6 +1590,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2180,7 +2321,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resizeapply(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2212,6 +2353,13 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++void ++resizenoapply(Client *c, struct wlr_box geo, int interact) ++{ ++ c->geom = geo; ++ c->interact = interact; ++} ++ + void + run(char *startup_cmd) + { +@@ -2354,6 +2502,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2741,6 +2899,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.47.1 + diff --git a/dwl/dwl-patches/patches/gestures/README.md b/dwl/dwl-patches/patches/gestures/README.md new file mode 100644 index 0000000..0fbe418 --- /dev/null +++ b/dwl/dwl-patches/patches/gestures/README.md @@ -0,0 +1,23 @@ +### Description +Add swipe gestures to trigger functions, similar to [libinput-gestures](https://github.com/bulletmark/libinput-gestures/tree/master). It supports the following gestures: `SWIPE_UP`, `SWIPE_DOWN`, `SWIPE_LEFT` and `SWIPE_RIGHT` + +> NOTE: It requires that you have previously applied [pointer-gestures-unstable-v1](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pointer-gestures-unstable-v1) + +```c +static const Gesture gestures[] = { + /* modifier gesture fingers_count function argument */ + { MODKEY, SWIPE_LEFT, 4, shiftview, { .i = 1 } }, + { 0, SWIPE_RIGHT, 4, shiftview, { .i = -1 } }, +}; +``` + +**NOTE:** the example above requires the following patch [shiftview](https://codeberg.org/dwl/dwl-patches/wiki/shiftview) + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/gestures) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/gestures/gestures.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/be3735bc6a5c64ff76c200a8679453bd179be456/gestures/gestures.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/655fd2916c1bcaa022ce6dcdfb370051cf64df66/gestures/gestures.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/gestures/gestures.patch b/dwl/dwl-patches/patches/gestures/gestures.patch new file mode 100644 index 0000000..4f74325 --- /dev/null +++ b/dwl/dwl-patches/patches/gestures/gestures.patch @@ -0,0 +1,169 @@ +From 42f97e88bd901d81b81da61c44a790b583706308 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:18:49 -0500 +Subject: [PATCH] implement gestures + +--- + config.def.h | 9 +++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 77 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8b75564 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,6 +14,8 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static const unsigned int swipe_min_threshold = 0; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -174,3 +176,10 @@ static const Button buttons[] = { + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, + { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, + }; ++ ++static const Gesture gestures[] = { ++ // { MODKEY, SWIPE_LEFT, 4, shiftview, { .i = 1 } }, ++ // { MODKEY, SWIPE_RIGHT, 4, shiftview, { .i = -1 } }, ++ { MODKEY, SWIPE_UP, 3, focusstack, {.i = 1} }, ++ { MODKEY, SWIPE_DOWN, 3, focusstack, {.i = -1} }, ++}; +diff --git a/dwl.c b/dwl.c +index ded83e2..5d861e8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -88,6 +88,7 @@ enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ + #endif ++enum { SWIPE_LEFT, SWIPE_RIGHT, SWIPE_DOWN, SWIPE_UP }; + + typedef union { + int i; +@@ -103,6 +104,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ unsigned int mod; ++ unsigned int motion; ++ unsigned int fingers_count; ++ void (*func)(const Arg *); ++ const Arg arg; ++} Gesture; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -251,6 +260,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static int ongesture(struct wlr_pointer_swipe_end_event *event); + static void swipe_begin(struct wl_listener *listener, void *data); + static void swipe_update(struct wl_listener *listener, void *data); + static void swipe_end(struct wl_listener *listener, void *data); +@@ -416,6 +426,10 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static uint32_t swipe_fingers = 0; ++static double swipe_dx = 0; ++static double swipe_dy = 0; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -435,6 +449,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++static const unsigned int abzsquare = swipe_min_threshold * swipe_min_threshold; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -657,6 +673,11 @@ swipe_begin(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_begin_event *event = data; + ++ swipe_fingers = event->fingers; ++ // Reset swipe distance at the beginning of a swipe ++ swipe_dx = 0; ++ swipe_dy = 0; ++ + // Forward swipe begin event to client + wlr_pointer_gestures_v1_send_swipe_begin( + pointer_gestures, +@@ -671,6 +692,11 @@ swipe_update(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_update_event *event = data; + ++ swipe_fingers = event->fingers; ++ // Accumulate swipe distance ++ swipe_dx += event->dx; ++ swipe_dy += event->dy; ++ + // Forward swipe update event to client + wlr_pointer_gestures_v1_send_swipe_update( + pointer_gestures, +@@ -681,11 +707,53 @@ swipe_update(struct wl_listener *listener, void *data) + ); + } + ++int ++ongesture(struct wlr_pointer_swipe_end_event *event) ++{ ++ struct wlr_keyboard *keyboard; ++ uint32_t mods; ++ const Gesture *g; ++ unsigned int motion; ++ unsigned int adx = (int)round(fabs(swipe_dx)); ++ unsigned int ady = (int)round(fabs(swipe_dy)); ++ int handled = 0; ++ ++ if (event->cancelled) { ++ return handled; ++ } ++ ++ // Require absolute distance movement beyond a small thresh-hold ++ if (adx * adx + ady * ady < abzsquare) { ++ return handled; ++ } ++ ++ if (adx > ady) { ++ motion = swipe_dx < 0 ? SWIPE_LEFT : SWIPE_RIGHT; ++ } else { ++ motion = swipe_dy < 0 ? SWIPE_UP : SWIPE_DOWN; ++ } ++ ++ keyboard = wlr_seat_get_keyboard(seat); ++ mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; ++ for (g = gestures; g < END(gestures); g++) { ++ if (CLEANMASK(mods) == CLEANMASK(g->mod) && ++ swipe_fingers == g->fingers_count && ++ motion == g->motion && g->func) { ++ g->func(&g->arg); ++ handled = 1; ++ } ++ } ++ return handled; ++} ++ + void + swipe_end(struct wl_listener *listener, void *data) + { + struct wlr_pointer_swipe_end_event *event = data; + ++ // TODO: should we stop here if the event has been handled? ++ ongesture(event); ++ + // Forward swipe end event to client + wlr_pointer_gestures_v1_send_swipe_end( + pointer_gestures, +-- +2.45.1 diff --git a/dwl/dwl-patches/patches/globalkey/README.md b/dwl/dwl-patches/patches/globalkey/README.md new file mode 100644 index 0000000..d250f92 --- /dev/null +++ b/dwl/dwl-patches/patches/globalkey/README.md @@ -0,0 +1,33 @@ +### Description + +This patch adds the ability to pass keys that are specified in the config header globally, similar to Hyprland's approach. +This might deal with Wayland's lack of global shortcuts. + +Example: +``` +static const PassKeypressRule pass_rules[] = { + ADDPASSRULE("com.obsproject.Studio", XKB_KEY_Home), + ADDPASSRULE("WebCord", XKB_KEY_n), + /* xkb key is case-insensitive */ +}; +``` + +This will pass the `Home` key (alongside with mods) to OBS regardless of what client is currently focused, if any. +The string "com.obsproject.Studio" should match the exact appid of the client. + +To get the appid use [dwlmsg](https://codeberg.org/notchoc/dwlmsg) or run stock dwl from a terminal then launch the needed application inside, dwl will print all the info to the stdout. + +Note that if a popup (like [fuzzel](https://codeberg.org/dnkl/fuzzel)) is focused, no keys will be globally passed. +This is done so these menus don't get closed after hitting some of the global keys. + +## Warning +This patch is a stupid hack, it doesn't work all the time. +Examples: obs needs to be clicked on once before applying global hotkeys. +Electron (discord/webcord/chromium) with wayland backend ignores the very first press. +Other programs might not work at all. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/globalkey) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/globalkey/globalkey.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl/dwl-patches/patches/globalkey/globalkey.patch b/dwl/dwl-patches/patches/globalkey/globalkey.patch new file mode 100644 index 0000000..07d3856 --- /dev/null +++ b/dwl/dwl-patches/patches/globalkey/globalkey.patch @@ -0,0 +1,131 @@ +From f36e3f134c9f14a9821783d9908471ed0bdca0ed Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Fri, 14 Mar 2025 20:05:45 +0200 +Subject: [PATCH] implement globalkey patch + +--- + config.def.h | 8 +++++++ + dwl.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 69 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..25486c8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -122,6 +122,14 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#define ADDPASSRULE(S, K) {.appid = S, .len = LENGTH(S), .key = K} ++static const PassKeypressRule pass_rules[] = { ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_Home), ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_End), ++ ADDPASSRULE("com.obsproject.Studio", XKB_KEY_F12), ++ ADDPASSRULE("WebCord", XKB_KEY_n), ++}; ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ +diff --git a/dwl.c b/dwl.c +index 4816159..9ad64dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -217,6 +217,12 @@ typedef struct { + int x, y; + } MonitorRule; + ++typedef struct { ++ const char* appid; ++ size_t len; ++ uint32_t key; ++} PassKeypressRule; ++ + typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +@@ -293,6 +299,7 @@ static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); ++static void keypressglobal(struct wlr_surface *last_surface, struct wlr_keyboard *keyboard, struct wlr_keyboard_key_event *event, uint32_t mods, xkb_keysym_t keysym); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); + static void killclient(const Arg *arg); +@@ -1628,6 +1635,12 @@ keypress(struct wl_listener *listener, void *data) + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; ++ struct wlr_surface *last_surface = seat->keyboard_state.focused_surface; ++ struct wlr_xdg_surface *xdg_surface = last_surface ? wlr_xdg_surface_try_from_wlr_surface(last_surface) : NULL; ++ int pass = 0; ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = last_surface ? wlr_xwayland_surface_try_from_wlr_surface(last_surface) : NULL; ++#endif + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +@@ -1662,12 +1675,60 @@ keypress(struct wl_listener *listener, void *data) + if (handled) + return; + ++ /* don't pass when popup is focused ++ * this is better than having popups (like fuzzel or wmenu) closing while typing in a passed keybind */ ++ pass = (xdg_surface && xdg_surface->role != WLR_XDG_SURFACE_ROLE_POPUP) || !last_surface ++#ifdef XWAYLAND ++ || xsurface ++#endif ++ ; ++ /* passed keys don't get repeated */ ++ if (!locked && pass) { ++ for (i = 0; i < nsyms; ++i) ++ keypressglobal(last_surface, &group->wlr_group->keyboard, event, mods, syms[i]); ++ } ++ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); + } + ++void ++keypressglobal(struct wlr_surface *last_surface, struct wlr_keyboard *keyboard, struct wlr_keyboard_key_event *event, uint32_t mods, xkb_keysym_t keysym) ++{ ++ Client *c = NULL, *lastc = focustop(selmon); ++ int reset = false; ++ const char *appid = NULL; ++ ++ for (size_t r = 0; r < LENGTH(pass_rules); r++) { ++ uint32_t rcode = xkb_keysym_to_upper(pass_rules[r].key); ++ uint32_t pcode = xkb_keysym_to_upper(keysym); ++ ++ /* match key only (case insensitive) ignoring mods */ ++ if (rcode == pcode) { ++ wl_list_for_each(c, &clients, link) { ++ if (c && c != lastc) { ++ appid = client_get_appid(c); ++ if (appid && strncmp(appid, pass_rules[r].appid, pass_rules[r].len) == 0) { ++ reset = true; ++ ++ client_notify_enter(client_surface(c), keyboard); ++ client_activate_surface(client_surface(c), 1); ++ wlr_seat_keyboard_send_key(seat, event->time_msec, event->keycode, event->state); ++ ++ goto done; ++ } ++ } ++ } ++ } ++ } ++ ++done: ++ if (reset && last_surface) ++ client_notify_enter(last_surface, keyboard); ++} ++ + void + keypressmod(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/headless/README.md b/dwl/dwl-patches/patches/headless/README.md new file mode 100644 index 0000000..990a5d9 --- /dev/null +++ b/dwl/dwl-patches/patches/headless/README.md @@ -0,0 +1,11 @@ +### Description +Implements `swaymsg create_output` command, it allows you to create virtual/headless outputs. But in combination with a VNC server (for example wayvnc), this allows you to essentially have additional monitors, by connecting to the VNC server with an appropiate client (for example on an tablet or laptop). + +If you plan to use wayvnc, you'll need [virtual-pointer](https://codeberg.org/dwl/dwl-patches/wiki/virtual-pointer.-) patch as well + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/v0.5/headless) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0096e49402bc59b4050e12cdb9befb79d0011006/headless/headless.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/headless/headless.patch b/dwl/dwl-patches/patches/headless/headless.patch new file mode 100644 index 0000000..fb01fda --- /dev/null +++ b/dwl/dwl-patches/patches/headless/headless.patch @@ -0,0 +1,130 @@ +From 2e1123af5c7ae4354ec997d59cb36143fb2fdd27 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Mon, 8 Apr 2024 10:23:40 -0500 +Subject: [PATCH] feat: implement headless backend + +--- + config.def.h | 1 + + dwl.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 59 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..f0a2080 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -141,6 +141,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, XKB_KEY_M, create_output, {0} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index ef27a1d..79a63b0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -12,7 +12,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -58,6 +61,9 @@ + #include + #include + #endif ++#if WLR_HAS_X11_BACKEND ++#include ++#endif + + #include "util.h" + +@@ -327,6 +333,8 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void _create_output(struct wlr_backend *backend, void *data); ++static void create_output(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -335,6 +343,7 @@ static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wlr_backend *backend; ++static struct wlr_backend *headless_backend; + static struct wlr_scene *scene; + static struct wlr_scene_tree *layers[NUM_LAYERS]; + static struct wlr_scene_tree *drag_icon; +@@ -2321,6 +2330,16 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + ++ /** ++ * Initialize headless backend ++ */ ++ headless_backend = wlr_headless_backend_create(dpy); ++ if (!headless_backend) { ++ die("Failed to create secondary headless backend"); ++ } else { ++ wlr_multi_backend_add(backend, headless_backend); ++ } ++ + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, +@@ -2746,6 +2765,45 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++_create_output(struct wlr_backend *_backend, void *data) ++{ ++ bool *done = data; ++ if (*done) { ++ return; ++ } ++ ++ if (wlr_backend_is_wl(_backend)) { ++ wlr_wl_output_create(_backend); ++ *done = true; ++ } else if (wlr_backend_is_headless(_backend)) { ++ wlr_headless_add_output(_backend, 1920, 1080); ++ *done = true; ++ } ++#if WLR_HAS_X11_BACKEND ++ else if (wlr_backend_is_x11(backend)) { ++ wlr_x11_output_create(backend); ++ *done = true; ++ } ++#endif ++} ++ ++void ++create_output(const Arg *arg) ++{ ++ bool done = false; ++ ++ if (!wlr_backend_is_multi(backend)) { ++ die("Expected a multi backend"); ++ } ++ ++ wlr_multi_for_each_backend(backend, _create_output, &done); ++ ++ if (!done) { ++ die("Can only create outputs for Wayland, X11 or headless backends"); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.2 + diff --git a/dwl/dwl-patches/patches/hide-behind-fullscreen/README.md b/dwl/dwl-patches/patches/hide-behind-fullscreen/README.md new file mode 100644 index 0000000..0b4bebe --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-fullscreen/README.md @@ -0,0 +1,12 @@ +### Description +Hide all clients (and layer surfaces) behind the current client if it is +fullscreen, only the background (layer surfaces at the background layer) will +be shown + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/hide-behind-fullscreen) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch) +- [hide-behind-fullscreen-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch b/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch new file mode 100644 index 0000000..de2dfc6 --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch @@ -0,0 +1,73 @@ +From 78e5c4b42cf125be70814fd65a09cbcf0a18aa7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 10 Apr 2022 22:38:53 -0500 +Subject: [PATCH] hide-behind-fullscreen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 2 +- + dwl.c | 24 +++++++++++++++++++++++- + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..1d5a4c84 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,7 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index a2711f67..56356f29 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -484,7 +484,9 @@ applyrules(Client *c) + void + arrange(Monitor *m) + { +- Client *c; ++ LayerSurface *l; ++ Client *c, *sel = focustop(m); ++ int i; + + if (!m->wlr_output->enabled) + return; +@@ -515,6 +517,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + ++ if (sel && sel->isfullscreen && VISIBLEON(sel, m)) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &sel->mon->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, (sel->isfullscreen && c == sel) ++ || !sel->isfullscreen); ++ } ++ } ++ if (!sel || (!sel->isfullscreen && VISIBLEON(sel, m))) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &m->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 1); ++ } ++ } ++ + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch b/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch new file mode 100644 index 0000000..f82d0e6 --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch @@ -0,0 +1,73 @@ +From ac1537f068ea626f1984803ed8db08faf7943b18 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 10 Apr 2022 22:38:53 -0500 +Subject: [PATCH] hide-behind-fullscreen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 2 +- + dwl.c | 24 +++++++++++++++++++++++- + 2 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..1d5a4c84 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,7 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index ad21e1ba..f5395fe6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -505,7 +505,9 @@ applyrules(Client *c) + void + arrange(Monitor *m) + { +- Client *c; ++ LayerSurface *l; ++ Client *c, *sel = focustop(m); ++ int i; + + if (!m->wlr_output->enabled) + return; +@@ -536,6 +538,26 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + ++ if (sel && sel->isfullscreen && VISIBLEON(sel, m)) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &sel->mon->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 0); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, (sel->isfullscreen && c == sel) ++ || !sel->isfullscreen); ++ } ++ } ++ if (!sel || (!sel->isfullscreen && VISIBLEON(sel, m))) { ++ for (i = 2; i > ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; i--) { ++ wl_list_for_each(l, &m->layers[i], link) ++ wlr_scene_node_set_enabled(&l->scene->node, 1); ++ } ++ } ++ + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/hide-behind-monocle/README.md b/dwl/dwl-patches/patches/hide-behind-monocle/README.md new file mode 100644 index 0000000..507d2e1 --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-monocle/README.md @@ -0,0 +1,10 @@ +### Description +Hide all clients behind the focused one in the monocle layout + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/hide-behind-monocle) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-monocle/hide-behind-monocle.patch) +- [hide-behind-monocle-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch b/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch new file mode 100644 index 0000000..76b6e96 --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch @@ -0,0 +1,332 @@ +From 9bb408e6921b582dbcf5adfc36a7618086219998 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:25:16 -0600 +Subject: [PATCH] hide behind monocle +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 55 insertions(+), 32 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a2711f67..70687178 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -288,10 +288,11 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +-static Client *focustop(Monitor *m); ++static Client *focustop(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void hidebehindmonocle(Monitor *m); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -497,7 +498,7 @@ arrange(Monitor *m) + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, +- (c = focustop(m)) && c->isfullscreen); ++ (c = focustop(m, 0)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + +@@ -750,7 +751,7 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + printstatus(); + } + +@@ -1185,7 +1186,7 @@ void + destroydragicon(struct wl_listener *listener, void *data) + { + /* Focus enter isn't sent during drag, so refocus the focused node. */ +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -1220,7 +1221,7 @@ destroylock(SessionLock *lock, int unlock) + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + +- focusclient(focustop(selmon), 0); ++ focusclient(focustop(selmon, 0), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +@@ -1249,7 +1250,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +@@ -1366,6 +1367,7 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ hidebehindmonocle(c->mon); + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are +@@ -1420,14 +1422,14 @@ focusmon(const Arg *arg) + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void + focusstack(const Arg *arg) + { + /* Focus the next or previous client (in tiling order) on selmon */ +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { +@@ -1453,12 +1455,15 @@ focusstack(const Arg *arg) + * will focus the topmost client of this mon, when actually will + * only return that client */ + Client * +-focustop(Monitor *m) ++focustop(Monitor *m, int onlytiled) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (VISIBLEON(c, m)) { ++ if (onlytiled && c->isfloating) ++ continue; + return c; ++ } + } + return NULL; + } +@@ -1515,6 +1520,25 @@ handlesig(int signo) + } + } + ++void ++hidebehindmonocle(Monitor *m) ++{ ++ Client *c; ++ if (m && m->lt[m->sellt]->arrange == monocle) { ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m) && c->isfloating); ++ } ++ ++ c = NULL; ++ ++ /* Enable top tiled client, fullscreen is considered tiled */ ++ if ((c = focustop(m, 1))) ++ wlr_scene_node_set_enabled(&c->scene->node, 1); ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1653,7 +1677,7 @@ keyrepeat(void *data) + void + killclient(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + client_send_close(sel); + } +@@ -1780,8 +1804,7 @@ monocle(Monitor *m) + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); ++ hidebehindmonocle(m); + } + + void +@@ -2049,7 +2072,7 @@ printstatus(void) + if (c->isurgent) + urg |= c->tags; + } +- if ((c = focustop(m))) { ++ if ((c = focustop(m, 0))) { + title = client_get_title(c); + appid = client_get_appid(c); + printf("%s title %s\n", m->wlr_output->name, title ? title : broken); +@@ -2401,7 +2424,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void +@@ -2670,12 +2693,12 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2683,7 +2706,7 @@ tag(const Arg *arg) + void + tagmon(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setmon(sel, dirtomon(arg->i), 0); + } +@@ -2725,7 +2748,7 @@ tile(Monitor *m) + void + togglefloating(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +@@ -2734,7 +2757,7 @@ togglefloating(const Arg *arg) + void + togglefullscreen(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setfullscreen(sel, !sel->isfullscreen); + } +@@ -2743,12 +2766,12 @@ void + toggletag(const Arg *arg) + { + uint32_t newtags; +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2761,7 +2784,7 @@ toggleview(const Arg *arg) + return; + + selmon->tagset[selmon->seltags] = newtagset; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2785,7 +2808,7 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data) + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2802,7 +2825,7 @@ unmapnotify(struct wl_listener *listener, void *data) + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + } else { + wl_list_remove(&c->link); +@@ -2883,7 +2906,7 @@ updatemons(struct wl_listener *listener, void *data) + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ +- if ((c = focustop(m)) && c->isfullscreen) ++ if ((c = focustop(m, 0)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, +@@ -2903,7 +2926,7 @@ updatemons(struct wl_listener *listener, void *data) + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); +@@ -2925,7 +2948,7 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); +- if (c == focustop(c->mon)) ++ if (c == focustop(c->mon, 0)) + printstatus(); + } + +@@ -2935,7 +2958,7 @@ urgent(struct wl_listener *listener, void *data) + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); +- if (!c || c == focustop(selmon)) ++ if (!c || c == focustop(selmon, 0)) + return; + + c->isurgent = 1; +@@ -2953,7 +2976,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(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -3024,7 +3047,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3141,7 +3164,7 @@ sethints(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); +- if (c == focustop(selmon)) ++ if (c == focustop(selmon, 0)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch b/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch new file mode 100644 index 0000000..dfe23dd --- /dev/null +++ b/dwl/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch @@ -0,0 +1,332 @@ +From 8022376ee59d616831271dbc9f289a8bfd4fedda Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sat, 8 Jul 2023 17:25:16 -0600 +Subject: [PATCH] hide behind monocle +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 55 insertions(+), 32 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..cad3b0b2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -285,10 +285,11 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +-static Client *focustop(Monitor *m); ++static Client *focustop(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void hidebehindmonocle(Monitor *m); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -518,7 +519,7 @@ arrange(Monitor *m) + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, +- (c = focustop(m)) && c->isfullscreen); ++ (c = focustop(m, 0)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + +@@ -807,7 +808,7 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + printstatus(); + } + +@@ -1243,7 +1244,7 @@ void + destroydragicon(struct wl_listener *listener, void *data) + { + /* Focus enter isn't sent during drag, so refocus the focused node. */ +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + wl_list_remove(&listener->link); + } +@@ -1280,7 +1281,7 @@ destroylock(SessionLock *lock, int unlock) + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + +- focusclient(focustop(selmon), 0); ++ focusclient(focustop(selmon, 0), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +@@ -1309,7 +1310,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +@@ -1420,6 +1421,7 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ hidebehindmonocle(c->mon); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +@@ -1473,14 +1475,14 @@ focusmon(const Arg *arg) + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void + focusstack(const Arg *arg) + { + /* Focus the next or previous client (in tiling order) on selmon */ +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { +@@ -1506,12 +1508,15 @@ focusstack(const Arg *arg) + * will focus the topmost client of this mon, when actually will + * only return that client */ + Client * +-focustop(Monitor *m) ++focustop(Monitor *m, int onlytiled) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (VISIBLEON(c, m)) { ++ if (onlytiled && c->isfloating) ++ continue; + return c; ++ } + } + return NULL; + } +@@ -1557,6 +1562,25 @@ handlesig(int signo) + quit(NULL); + } + ++void ++hidebehindmonocle(Monitor *m) ++{ ++ Client *c; ++ if (m && m->lt[m->sellt]->arrange == monocle) { ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m) && c->isfloating); ++ } ++ ++ c = NULL; ++ ++ /* Enable top tiled client, fullscreen is considered tiled */ ++ if ((c = focustop(m, 1))) ++ wlr_scene_node_set_enabled(&c->scene->node, 1); ++ } ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1695,7 +1719,7 @@ keyrepeat(void *data) + void + killclient(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + client_send_close(sel); + } +@@ -1824,8 +1848,7 @@ monocle(Monitor *m) + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); ++ hidebehindmonocle(m); + } + + void +@@ -2092,7 +2115,7 @@ printstatus(void) + if (c->isurgent) + urg |= c->tags; + } +- if ((c = focustop(m))) { ++ if ((c = focustop(m, 0))) { + printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); + printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); + printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +@@ -2406,7 +2429,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + + void +@@ -2680,12 +2703,12 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2693,7 +2716,7 @@ tag(const Arg *arg) + void + tagmon(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setmon(sel, dirtomon(arg->i), 0); + } +@@ -2735,7 +2758,7 @@ tile(Monitor *m) + void + togglefloating(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +@@ -2744,7 +2767,7 @@ togglefloating(const Arg *arg) + void + togglefullscreen(const Arg *arg) + { +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (sel) + setfullscreen(sel, !sel->isfullscreen); + } +@@ -2753,12 +2776,12 @@ void + toggletag(const Arg *arg) + { + uint32_t newtags; +- Client *sel = focustop(selmon); ++ Client *sel = focustop(selmon, 0); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2771,7 +2794,7 @@ toggleview(const Arg *arg) + return; + + selmon->tagset[selmon->seltags] = newtagset; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -2795,7 +2818,7 @@ unmaplayersurfacenotify(struct wl_listener *listener, void *data) + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2812,7 +2835,7 @@ unmapnotify(struct wl_listener *listener, void *data) + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + } + } else { + wl_list_remove(&c->link); +@@ -2893,7 +2916,7 @@ updatemons(struct wl_listener *listener, void *data) + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ +- if ((c = focustop(m)) && c->isfullscreen) ++ if ((c = focustop(m, 0)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, +@@ -2913,7 +2936,7 @@ updatemons(struct wl_listener *listener, void *data) + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } +- focusclient(focustop(selmon), 1); ++ focusclient(focustop(selmon, 0), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); +@@ -2935,7 +2958,7 @@ void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); +- if (c == focustop(c->mon)) ++ if (c == focustop(c->mon, 0)) + printstatus(); + } + +@@ -2945,7 +2968,7 @@ urgent(struct wl_listener *listener, void *data) + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); +- if (!c || c == focustop(selmon)) ++ if (!c || c == focustop(selmon, 0)) + return; + + c->isurgent = 1; +@@ -2963,7 +2986,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(focustop(selmon, 0), 1); + arrange(selmon); + printstatus(); + } +@@ -3034,7 +3057,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon, 0); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3145,7 +3168,7 @@ sethints(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); +- if (c == focustop(selmon)) ++ if (c == focustop(selmon, 0)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/hide_vacant_tags/README.md b/dwl/dwl-patches/patches/hide_vacant_tags/README.md new file mode 100644 index 0000000..fae2a4d --- /dev/null +++ b/dwl/dwl-patches/patches/hide_vacant_tags/README.md @@ -0,0 +1,11 @@ +### Description + +Prevent [bar](/dwl/dwl-patches/wiki/bar) from drawing tags with no clients (i.e. vacant). +It also stops drawing empty rectangles on the bar for non-vacant tags as there is no need anymore to distinguish vacant tags and it offers a more visible contrast than if there were filled/empty rectangles. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hide_vacant_tags/hide_vacant_tags.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch b/dwl/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch new file mode 100644 index 0000000..ae3966e --- /dev/null +++ b/dwl/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch @@ -0,0 +1,69 @@ +From 54b75630d9f93bd1a8ab5949df64f086043e96eb Mon Sep 17 00:00:00 2001 +From: sewn +Date: Fri, 13 Dec 2024 17:31:33 +0300 +Subject: [PATCH] hide vacant tags + +https://dwm.suckless.org/patches/hide_vacant_tags/ +--- + dwl.c | 21 +++++++++++++-------- + 1 file changed, 13 insertions(+), 8 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 1e199f3..24d4b20 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -721,7 +721,7 @@ bufrelease(struct wl_listener *listener, void *data) + void + buttonpress(struct wl_listener *listener, void *data) + { +- unsigned int i = 0, x = 0; ++ unsigned int i = 0, x = 0, occ = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; +@@ -751,9 +751,16 @@ buttonpress(struct wl_listener *listener, void *data) + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; +- do ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != selmon) ++ continue; ++ occ |= c->tags == TAGMASK ? 0 : c->tags; ++ } ++ do { ++ if (!(occ & 1 << i || selmon->tagset[selmon->seltags] & 1 << i)) ++ continue; + x += TEXTW(selmon, tags[i]); +- while (cx >= x && ++i < LENGTH(tags)); ++ } while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; +@@ -1530,20 +1537,18 @@ drawbar(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; +- occ |= c->tags; ++ occ |= c->tags == TAGMASK ? 0 : c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { ++ if(!(occ & 1 << i || m->tagset[m->seltags] & 1 << i)) ++ continue; + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); +- if (occ & 1 << i) +- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, +- m == selmon && c && c->tags & 1 << i, +- urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); +-- +2.47.1 + diff --git a/dwl/dwl-patches/patches/hiderule/README.md b/dwl/dwl-patches/patches/hiderule/README.md new file mode 100644 index 0000000..bed2475 --- /dev/null +++ b/dwl/dwl-patches/patches/hiderule/README.md @@ -0,0 +1,9 @@ +### Description +Adds a `ishidden` option to client rules, that allows hiding any matching clients entirely. + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/hiderule) +- [yyyy-mm-dd](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/hiderule/hiderule.patch) + +### Authors +- [minego](https://codeberg.org/minego) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/hiderule/hiderule.patch b/dwl/dwl-patches/patches/hiderule/hiderule.patch new file mode 100644 index 0000000..8ed1a84 --- /dev/null +++ b/dwl/dwl-patches/patches/hiderule/hiderule.patch @@ -0,0 +1,57 @@ +From fb48ec754d63b3d8e40fff2d047050675887d7f4 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 12:53:18 -0600 +Subject: [PATCH] hiderule + +--- + config.def.h | 9 ++++++--- + dwl.c | 4 ++++ + 2 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..c476057 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,11 +21,14 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor ishidden */ + /* examples: +- { "Gimp", NULL, 0, 1, -1 }, ++ { "Gimp", NULL, 0, 1, -1, 0 }, + */ +- { "firefox", NULL, 1 << 8, 0, -1 }, ++ { "firefox", NULL, 1 << 8, 0, -1, 0 }, ++ ++ { "firefox", "Sharing Indicator", ++ 0, 1, -1, 1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 5867b0c..799fd89 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -228,6 +228,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int ishidden; + } Rule; + + typedef struct { +@@ -464,6 +465,9 @@ applyrules(Client *c) + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; ++ if (r->ishidden) { ++ mon = NULL; ++ } + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/hot-reload/README.md b/dwl/dwl-patches/patches/hot-reload/README.md new file mode 100644 index 0000000..e387ca4 --- /dev/null +++ b/dwl/dwl-patches/patches/hot-reload/README.md @@ -0,0 +1,110 @@ +### Description +Enables hot-reloading of dwl; meaning almost all logic can be changed at runtime. +This obviously requires some black magic so for now there's a glibc 2.0 or later +dependency to this. +In particular this allows for every option in config.h to be changed at runtime. + +#### Reloading +To reload rebuild dwl.so, perhaps reinstall it and then run trigger reload function (bound to Mod+Shift+R by default). +This currently calls `notify-send` in order to inform you of a reloading taking place. +So in short: +1. make changes to `config.h` or `dwl.c` +2. run `make` to rebuild dwl.so +3. run `sudo make install` to reinstall dwl + + +#### Limitations +Reloading the compositor will replace all functionality except for `main`, `setup`, `run` and the reload logic. +Note that you're responsible yourself for reloading ressources like fonts, which may only get acquired once. +A lot of components of dwl will also only get run on a trigger (the tiling for example). +So not every change will be immediate. +Furthermore, any patch adding more global state to dwl cannot currently be reloaded properly since +we keep state in the cold part. These patches will still work and their functionality will (hopefully) be +reloadable but you will need to restart the compositor once. + +#### Notes +##### reduce compile errors +This patch triggers `-Wpedantic` a bunch (I don't think there's a way around this, `dlsym` yields `void*` pointers to functions). +This will show a lot of warnings but cause no errors. +So you may want to disable this compile option in order to get readable compiler output. +##### runtime dependencies +This does depend on you having a notification daemon like `dunst` or `mako` running as well as +having `notify-send` installed in order for the compositor to inform you of the reload. + + +#### How? +Most of all dwl functionality is moved into a shared object file `dwl.so`, which can be reloaded at runtime. + +#### How do I make this work with other patches? +Most patches should already put everything in more or less the correct place but if they don't, then here is +where you learn how to fix it. + +The concept itself is quite simple. We compile dwl.c twice once normally and once with the `HOT` macro defined. +The first run will yield the executable and the second will yield a shared object file to be reloaded at runtime. +From the cold part there are some newly available macros: +> symbol names are written as-is, never as string literals +* `TSYM(T, s)` dynamically loads the symbol `s` with type `T` from the shared object file use this if you need to call functions in the cold part (i.e. the `setup` function). +* `CSYM(T, v)` dynamically accesses the value of the symbol `v` of type `T` from the shared object. Use this to query values from config.h for example. +* `LISTEN_GLOBAL(E, L)` is similar to the `LISTEN` macro. `E` is an event and `L` the name of a global +listener. Current implementation is a bit messy and I may fix it if someone bothers me about it. +* `UNLISTEN(L)` takes a listener and unregisteres it. This is important for reloading. + +When adding new code there are some considerations to be made. Since dwl decorates all symbols with `static` by default, we cannot access them as-is. +C's macro system is a bit too powerful though and we use this to our advantage. We will repeatedly define and +undefine a macro called `static` in order to replace the `static` keyword inside some sections. +This allows us to do less refactoring and preserve a lot of the original patch compatability since we're only +strategically adding lines. We're tring to be as minimally invasive as we can. +As a general guide: +* global state should be global for the cold part and `extern` in the cold part meaning it should be inside a block like this: + ```C + #ifdef HOT + #define static extern + #else + #define static + #endif + ... // your global variables go here + #undef static + ``` +* function declarations should be visible in the hot part but not included in the cold part meaning they should be enclosed like this: + ```C + #ifdef HOT + #define static + ... // your function declarations go here + #undef static + #endif + ``` +* static data like the event handler structs in the current `main` branch are a bit more difficult but we will let them reside inside the hot part. +Thus, we enclose them the same way we do functions: + ```C + #ifdef HOT + #define static + ... // your struct wl_listener event handlers go here + #undef static + #endif + ``` +* function definitions should go in the hot part, so they need to be inside a big block like this: + ```C + #ifdef HOT + ... // function definitions here + #endif +* enfore use of the `LISTEN_GLOBAL` and `UNLISTEN` macros (I know this sucks but what can I do, I need to get +access to the callbacks somehow). So you want + * `wl_list_remove(listener.link)` to become `UNLISTEN(listener)` and + * `wl_signal_add(event, global_listener)` to become `LISTEN_GLOBAL(event, global_listener)`. +* Make sure that any patch you're using also uses static everywhere. +* If a patch adds any config variables that are accessed in the cold part (i.e. probably `setup`), +then you'll have to manually remove the `static` keyword from them. + +Note that usually you do not have to create the big `#ifdef` blocks yourself. +There is for example already a huge `#ifdef HOT`-delimited codeblock at the bottom +of dwl.c where all the function definitions go. + +If you have any troubles, feel free to reach out. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload-0.7.patch) +- [main 2025-05-30](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload.patch) +- find the repo for the patch [here](/Sivecano/dwl/src/branch/hot-reload) +### Authors +- [Sivecano](https://codeberg.org/Sivecano) +- Sérécano at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl/dwl-patches/patches/hot-reload/hot-reload-0.7.patch b/dwl/dwl-patches/patches/hot-reload/hot-reload-0.7.patch new file mode 100644 index 0000000..1611684 --- /dev/null +++ b/dwl/dwl-patches/patches/hot-reload/hot-reload-0.7.patch @@ -0,0 +1,770 @@ +From 559c635056f23d55df3f83c12d1201a7328f648f Mon Sep 17 00:00:00 2001 +From: Sivecano +Date: Sun, 26 Jan 2025 18:30:02 +0100 +Subject: [PATCH] redo hot-reloading in one file + +--- + Makefile | 19 ++- + config.def.h | 5 +- + dwl.c | 337 ++++++++++++++++++++++++++++++++++++++++++++------- + util.c | 34 ++++++ + util.h | 6 + + 5 files changed, 351 insertions(+), 50 deletions(-) + +diff --git a/Makefile b/Makefile +index 3358bae..e7ee9ff 100644 +--- a/Makefile ++++ b/Makefile +@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + + # CFLAGS / LDFLAGS + PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -fPIC -rdynamic + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +-all: dwl ++all: dwl dwl.so + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c config.mk cursor-shape-v1-protocol.h \ ++ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +@@ -49,7 +52,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h *.so + + dist: clean + mkdir -p dwl-$(VERSION) +@@ -63,6 +66,8 @@ install: dwl + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl ++ mkdir -p $(DESTDIR)$(PREFIX)/lib ++ install -m 744 dwl.so $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 +@@ -70,9 +75,13 @@ install: dwl + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + uninstall: +- rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ ++ rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(PREFIX)/lib/dwl.so $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + + .SUFFIXES: .c .o + .c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< ++ ++.SUFFIXES: .c .so ++.c.so: ++ $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -shared -DHOT $< +diff --git a/config.def.h b/config.def.h +index 22d2171..6e3dda1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,7 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); ++const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); +@@ -18,7 +18,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + #define TAGCOUNT (9) + + /* logging */ +-static int log_level = WLR_ERROR; ++int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, reload, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index def2562..1c9ab67 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,15 @@ + /* + * See LICENSE file for copyright and license details. + */ ++ ++/* stuff for hot-reload */ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++ + #include + #include + #include +@@ -67,6 +76,7 @@ + #include + #endif + ++ + #include "util.h" + + /* macros */ +@@ -77,8 +87,34 @@ + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +-#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++ ++#define SYM(a) dlsym(dwl_module, #a) ++#define TSYM(T, a) ((T)SYM(a)) ++#define CSYM(T, a) *(TSYM(T*, a)) ++ ++#define LISTEN(E, L, H) do { \ ++ (L)->notify = SYM(H); \ ++ listeners = append_listener((L), listeners); \ ++ wl_signal_add((E), (L)); \ ++ } while(0) ++ ++#define LISTEN_GLOBAL(E, L) do { \ ++ struct wl_listener* l = SYM(L); \ ++ listeners = append_listener(l, listeners); \ ++ wl_signal_add((E), l); \ ++ } while (0) ++ ++#define LISTEN_STATIC(E, H) do { \ ++ struct wl_listener* _l = malloc(sizeof(struct wl_listener)); \ ++ _l->notify = SYM(H); \ ++ listeners = append_listener(_l, listeners); \ ++ wl_signal_add((E), _l); \ ++ } while (0) ++ ++#define UNLISTEN(L) do { \ ++ wl_list_remove(&(L)->link); \ ++ listeners = remove_listener((L), listeners);\ ++ } while (0) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -242,6 +278,9 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++#define static ++ ++#ifdef HOT + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -253,7 +292,18 @@ static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void cleanup(void); ++ ++#undef static ++#define static ++#ifdef HOT ++ + static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +@@ -321,7 +371,18 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void run(char *startup_cmd); ++ ++#ifdef HOT ++#undef static ++#define static ++ + 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); +@@ -332,7 +393,18 @@ static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void setup(void); ++ ++#ifdef HOT ++#undef static ++#define static ++ + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -356,6 +428,16 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); + ++#endif ++ ++#ifdef HOT ++ #undef static ++ #define static extern ++#else ++ #undef static ++ #define static ++#endif ++ + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; +@@ -400,7 +482,9 @@ static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; ++#ifdef HOT + static struct wl_listener lock_listener = {.notify = locksession}; ++#endif + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; +@@ -426,6 +510,33 @@ static struct wlr_xwayland *xwayland; + static xcb_atom_t netatom[NetLast]; + #endif + ++/* undoes the shadowing of static from above */ ++#undef static ++ ++/* this is where we put global hot-reload state */ ++#ifdef HOT ++#define COLD extern ++#else ++#define COLD ++ ++static void* load(void); ++static const char* get_module_path(void); ++ ++#endif ++ ++COLD void * dwl_module = NULL; ++COLD void * last_module = NULL; ++COLD struct listens* listeners = NULL; ++COLD void reload(const Arg* arg); ++ ++#ifndef HOT ++static char* runpath; ++ ++#endif ++ ++ ++#ifdef HOT ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + +@@ -673,6 +784,8 @@ checkidleinhibitor(struct wlr_surface *exclude) + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); + } + ++#endif ++ + void + cleanup(void) + { +@@ -687,7 +800,7 @@ cleanup(void) + } + wlr_xcursor_manager_destroy(cursor_mgr); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ TSYM(void (*)(struct wl_listener*, void*), destroykeyboardgroup)(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ +@@ -699,6 +812,8 @@ cleanup(void) + wlr_scene_node_destroy(&scene->tree.node); + } + ++#ifdef HOT ++ + void + cleanupmon(struct wl_listener *listener, void *data) + { +@@ -712,10 +827,10 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + +- wl_list_remove(&m->destroy.link); +- wl_list_remove(&m->frame.link); ++ UNLISTEN(&m->destroy); ++ UNLISTEN(&m->frame); + wl_list_remove(&m->link); +- wl_list_remove(&m->request_state.link); ++ UNLISTEN(&m->request_state); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +@@ -848,7 +963,7 @@ commitpopup(struct wl_listener *listener, void *data) + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + } + + void +@@ -1179,8 +1294,8 @@ destroydecoration(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + +- wl_list_remove(&c->destroy_decoration.link); +- wl_list_remove(&c->set_decoration_mode.link); ++ UNLISTEN(&c->destroy_decoration); ++ UNLISTEN(&c->set_decoration_mode); + } + + void +@@ -1205,9 +1320,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); +- wl_list_remove(&l->destroy.link); +- wl_list_remove(&l->unmap.link); +- wl_list_remove(&l->surface_commit.link); ++ UNLISTEN(&l->destroy); ++ UNLISTEN(&l->unmap); ++ UNLISTEN(&l->surface_commit); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +@@ -1226,9 +1341,9 @@ destroylock(SessionLock *lock, int unlock) + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +- wl_list_remove(&lock->new_surface.link); +- wl_list_remove(&lock->unlock.link); +- wl_list_remove(&lock->destroy.link); ++ UNLISTEN(&lock->new_surface); ++ UNLISTEN(&lock->unlock); ++ UNLISTEN(&lock->destroy); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; +@@ -1242,7 +1357,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; +- wl_list_remove(&m->destroy_lock_surface.link); ++ UNLISTEN(&m->destroy_lock_surface); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; +@@ -1262,22 +1377,22 @@ destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); +- wl_list_remove(&c->destroy.link); +- wl_list_remove(&c->set_title.link); +- wl_list_remove(&c->fullscreen.link); ++ UNLISTEN(&c->destroy); ++ UNLISTEN(&c->set_title); ++ UNLISTEN(&c->fullscreen); + #ifdef XWAYLAND + if (c->type != XDGShell) { +- wl_list_remove(&c->activate.link); +- wl_list_remove(&c->associate.link); +- wl_list_remove(&c->configure.link); +- wl_list_remove(&c->dissociate.link); +- wl_list_remove(&c->set_hints.link); ++ UNLISTEN(&c->activate); ++ UNLISTEN(&c->associate); ++ UNLISTEN(&c->configure); ++ UNLISTEN(&c->dissociate); ++ UNLISTEN(&c->set_hints); + } else + #endif + { +- wl_list_remove(&c->commit.link); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->commit); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + free(c); + } +@@ -1292,7 +1407,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data) + active_constraint = NULL; + } + +- wl_list_remove(&pointer_constraint->destroy.link); ++ UNLISTEN(&pointer_constraint->destroy); + free(pointer_constraint); + } + +@@ -1306,8 +1421,8 @@ destroysessionlock(struct wl_listener *listener, void *data) + void + destroysessionmgr(struct wl_listener *listener, void *data) + { +- wl_list_remove(&lock_listener.link); +- wl_list_remove(&listener->link); ++ UNLISTEN(&lock_listener); ++ UNLISTEN(listener); + } + + void +@@ -1315,10 +1430,10 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + { + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); ++ UNLISTEN(&group->key); ++ UNLISTEN(&group->modifiers); ++ UNLISTEN(&group->destroy); + wlr_keyboard_group_destroy(group->wlr_group); +- wl_list_remove(&group->key.link); +- wl_list_remove(&group->modifiers.link); +- wl_list_remove(&group->destroy.link); + free(group); + } + +@@ -2212,6 +2327,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++#else /*HOT*/ ++ + void + run(char *startup_cmd) + { +@@ -2251,11 +2368,11 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ TSYM(void (*)(void), printstatus)(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +- selmon = xytomon(cursor->x, cursor->y); ++ selmon = TSYM(Monitor* (*)(double x, double y), xytomon)(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully +@@ -2271,6 +2388,9 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++#endif ++#ifdef HOT ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2428,17 +2548,19 @@ setsel(struct wl_listener *listener, void *data) + wlr_seat_set_selection(seat, event->source, event->serial); + } + ++#else /*HOT*/ ++ + void + setup(void) + { + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; +- struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = SYM(handlesig)}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +- wlr_log_init(log_level, NULL); ++ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ +@@ -2454,7 +2576,7 @@ setup(void) + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); +- root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); ++ root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, TSYM(float*, rootcolor)); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); +@@ -2550,7 +2672,7 @@ setup(void) + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); +- wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); ++ LISTEN_GLOBAL(&session_lock_mgr->events.new_lock, lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); +@@ -2620,7 +2742,7 @@ setup(void) + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +- kb_group = createkeyboardgroup(); ++ kb_group = TSYM(KeyboardGroup *(*)(void), createkeyboardgroup)(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); +@@ -2647,6 +2769,9 @@ setup(void) + #endif + } + ++#endif ++#ifdef HOT ++ + void + spawn(const Arg *arg) + { +@@ -3121,8 +3246,8 @@ void + dissociatex11(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, dissociate); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + + xcb_atom_t +@@ -3185,17 +3310,141 @@ xwaylandready(struct wl_listener *listener, void *data) + } + #endif + ++#else /* HOT */ ++void* ++load(void) ++{ ++ const char* path = get_module_path(); ++ char load[PATH_MAX] = "/tmp/dwl.soXXXXXX"; ++ void* new; ++ ++ if (!path) { ++ fprintf(stderr, "cannot find dwl.so\n"); ++ } ++ ++ do { ++ mktemp(load); ++ errno = 0; ++ symlink(path, load); ++ } while(errno == EEXIST); ++ ++ new = dlopen(load, RTLD_NOW|RTLD_LOCAL); ++ ++ unlink(load); ++ if (new == NULL) ++ fprintf(stderr, "error while loading %s: %s\n", path, dlerror()); ++ else ++ printf("loaded: %s\n", path); ++ ++ return new; ++} ++ ++const char * ++get_module_path(void) { ++ char home[PATH_MAX]; ++ strcpy(home, getenv("HOME")); ++ strcat(home, "/.local/lib"); ++ const char* abspaths[] = {".", home, "/usr/share/lib", "/usr/local/lib", "/usr/local/share/lib"}; ++ const char* relpaths[] = {"", "/../lib"}; ++ char paths[LENGTH(abspaths) + LENGTH(relpaths)][PATH_MAX]; ++ static char out[PATH_MAX] = "./"; ++ ++ for (size_t i = 0; i < LENGTH(abspaths); i++) ++ realpath(abspaths[i], paths[i]); ++ ++ for (size_t i = 0; i < LENGTH(relpaths); i++) ++ { ++ char tmp[PATH_MAX]; ++ strcpy(tmp, runpath); ++ strcat(tmp, relpaths[i]); ++ realpath(tmp, paths[LENGTH(abspaths) + i]); ++ } ++ ++ ++ ++ for (size_t i = 0; i < LENGTH(paths); i++) ++ { ++ char tmp[PATH_MAX]; ++ printf("checking path: %s\n", paths[i]); ++ strcpy(tmp, paths[i]); ++ strcat(tmp, "/dwl.so"); ++ if (access(tmp, F_OK|R_OK) == 0) ++ { ++ strcpy(out, tmp); ++ return out; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++reload(const Arg* arg) ++{ ++ char* error; ++ void* new; ++ size_t i = 0; ++ ++ // deinitialize previous module ++ if (last_module) { ++ // dlclose(last_module); ++ last_module = NULL; ++ } ++ ++ wlr_log(WLR_INFO, "reloading"); ++ ++ new = load(); ++ ++ if (new == NULL) ++ { ++ wlr_log(WLR_ERROR, "couldn't load new dwl module from %s", get_module_path()); ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "failed to reload dwl", NULL); ++ return; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- listens ---------"); ++ for(listens* a = listeners; a != NULL; a = a->next) ++ { ++ Dl_info info; ++ void* old = a->listen->notify; ++ dladdr(a->listen->notify, &info); ++ a->listen->notify = dlsym(new, info.dli_sname); ++ if ((error = dlerror()) != NULL){ ++ fprintf(stderr, "reload failure: %s", error); ++ a->listen->notify = old; ++ return; ++ } ++ wlr_log(WLR_DEBUG, "replaced listener: %s", info.dli_sname); ++ i++; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- done! ---------"); ++ wlr_log(WLR_DEBUG, "replaced %zu listeners", i); ++ ++ last_module = dwl_module; ++ dwl_module = new; ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "reloaded dwl", NULL); ++ ++} ++ + int + main(int argc, char *argv[]) + { + char *startup_cmd = NULL; + int c; + ++ runpath = dirname(argv[0]); ++ dwl_module = load(); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') +- log_level = WLR_DEBUG; ++ CSYM(enum wlr_log_importance, log_level) = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else +@@ -3215,3 +3464,5 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++#endif +diff --git a/util.c b/util.c +index 51130af..2000731 100644 +--- a/util.c ++++ b/util.c +@@ -49,3 +49,37 @@ fd_set_nonblock(int fd) { + + return 0; + } ++ ++struct listens* ++append_listener(struct wl_listener* new, struct listens* list) ++{ ++ struct listens* l = malloc(sizeof(struct listens)); ++ l->listen = new; ++ l->next = list; ++ return l; ++} ++ ++struct listens* ++remove_listener(struct wl_listener* l, struct listens* ls) ++{ ++ struct listens* out = ls; ++ struct listens* f = NULL; ++ for(struct listens* last = NULL; ls != NULL; ls = ls->next) ++ { ++ if (ls->listen == l) ++ { ++ if (last != NULL) ++ last->next = ls->next; ++ else ++ out = ls->next; ++ ++ f = ls; ++ } ++ else ++ last = ls; ++ } ++ ++ free(f); ++ ++ return out; ++} +diff --git a/util.h b/util.h +index 226980d..11aab34 100644 +--- a/util.h ++++ b/util.h +@@ -1,5 +1,11 @@ + /* See LICENSE.dwm file for copyright and license details. */ ++typedef struct listens { ++ struct wl_listener* listen; ++ struct listens* next; ++} listens; + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); + int fd_set_nonblock(int fd); ++struct listens* append_listener(struct wl_listener* l, struct listens* ls); ++struct listens* remove_listener(struct wl_listener* l, struct listens* ls); +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/hot-reload/hot-reload.patch b/dwl/dwl-patches/patches/hot-reload/hot-reload.patch new file mode 100644 index 0000000..627fa88 --- /dev/null +++ b/dwl/dwl-patches/patches/hot-reload/hot-reload.patch @@ -0,0 +1,1029 @@ +From 44ce31430dd7d2c0a6c1dc29534f22fb33b25bca Mon Sep 17 00:00:00 2001 +From: Sivecano +Date: Sun, 26 Jan 2025 18:30:02 +0100 +Subject: [PATCH] redo hot-reloading in one file + +--- + Makefile | 19 +- + config.def.h | 5 +- + dwl.c | 484 +++++++++++++++++++++++++++++++++++++++------------ + util.c | 34 ++++ + util.h | 6 + + 5 files changed, 433 insertions(+), 115 deletions(-) + +diff --git a/Makefile b/Makefile +index 578194f..69c1b54 100644 +--- a/Makefile ++++ b/Makefile +@@ -13,13 +13,16 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + + # CFLAGS / LDFLAGS + PKGS = wayland-server xkbcommon libinput $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -fPIC -rdynamic + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +-all: dwl ++all: dwl dwl.so + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c config.mk cursor-shape-v1-protocol.h \ ++ pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++dwl.so: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +@@ -49,7 +52,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h *.so + + dist: clean + mkdir -p dwl-$(VERSION) +@@ -64,6 +67,8 @@ install: dwl + rm -f $(DESTDIR)$(PREFIX)/bin/dwl + cp -f dwl $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dwl ++ mkdir -p $(DESTDIR)$(PREFIX)/lib ++ install -m 744 dwl.so $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(MANDIR)/man1 + cp -f dwl.1 $(DESTDIR)$(MANDIR)/man1 + chmod 644 $(DESTDIR)$(MANDIR)/man1/dwl.1 +@@ -71,9 +76,13 @@ install: dwl + cp -f dwl.desktop $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + chmod 644 $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + uninstall: +- rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(MANDIR)/man1/dwl.1 \ ++ rm -f $(DESTDIR)$(PREFIX)/bin/dwl $(DESTDIR)$(PREFIX)/lib/dwl.so $(DESTDIR)$(MANDIR)/man1/dwl.1 \ + $(DESTDIR)$(DATADIR)/wayland-sessions/dwl.desktop + + .SUFFIXES: .c .o + .c.o: + $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -c $< ++ ++.SUFFIXES: .c .so ++.c.so: ++ $(CC) $(CPPFLAGS) $(DWLCFLAGS) -o $@ -shared -DHOT $< +diff --git a/config.def.h b/config.def.h +index 22d2171..6e3dda1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,7 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); ++const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); +@@ -18,7 +18,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + #define TAGCOUNT (9) + + /* logging */ +-static int log_level = WLR_ERROR; ++int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, reload, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index 4816159..70e99be 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,15 @@ + /* + * See LICENSE file for copyright and license details. + */ ++ ++/* stuff for hot-reload */ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++ + #include + #include + #include +@@ -68,6 +77,7 @@ + #include + #endif + ++ + #include "util.h" + + /* macros */ +@@ -78,8 +88,33 @@ + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +-#define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) ++#define SYM(a) dlsym(dwl_module, #a) ++#define TSYM(T, a) ((T)SYM(a)) ++#define CSYM(T, a) *(TSYM(T*, a)) ++ ++#define LISTEN(E, L, H) do { \ ++ (L)->notify = SYM(H); \ ++ listeners = append_listener((L), listeners); \ ++ wl_signal_add((E), (L)); \ ++ } while(0) ++ ++#define LISTEN_GLOBAL(E, L) do { \ ++ struct wl_listener* l = SYM(L); \ ++ listeners = append_listener(l, listeners); \ ++ wl_signal_add((E), l); \ ++ } while (0) ++ ++#define LISTEN_STATIC(E, H) do { \ ++ struct wl_listener* _l = ecalloc(1, sizeof(struct wl_listener)); \ ++ _l->notify = SYM(H); \ ++ listeners = append_listener(_l, listeners); \ ++ wl_signal_add((E), _l); \ ++ } while (0) ++ ++#define UNLISTEN(L) do { \ ++ wl_list_remove(&(L)->link); \ ++ listeners = remove_listener((L), listeners);\ ++ } while (0) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -239,6 +274,9 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++#define static ++ ++#ifdef HOT + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -250,7 +288,18 @@ static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void cleanup(void); ++ ++#undef static ++#define static ++#ifdef HOT ++ + static void cleanupmon(struct wl_listener *listener, void *data); + static void cleanuplisteners(void); + static void closemon(Monitor *m); +@@ -318,7 +367,18 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void run(char *startup_cmd); ++ ++#ifdef HOT ++#undef static ++#define static ++ + 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); +@@ -328,7 +388,18 @@ static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); ++ ++#undef static ++#define static extern ++#endif ++ ++/* this is cold */ + static void setup(void); ++ ++#ifdef HOT ++#undef static ++#define static ++ + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -352,6 +423,16 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); + ++#endif ++ ++#ifdef HOT ++ #undef static ++ #define static extern ++#else ++ #undef static ++ #define static ++#endif ++ + /* variables */ + static pid_t child_pid = -1; + static int locked; +@@ -406,6 +487,10 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++#ifdef HOT ++#undef static ++#define static ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -429,6 +514,7 @@ static struct wl_listener output_mgr_test = {.notify = outputmgrtest}; + static struct wl_listener output_power_mgr_set_mode = {.notify = powermgrsetmode}; + static struct wl_listener request_activate = {.notify = urgent}; + static struct wl_listener request_cursor = {.notify = setcursor}; ++ + static struct wl_listener request_set_psel = {.notify = setpsel}; + static struct wl_listener request_set_sel = {.notify = setsel}; + static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; +@@ -436,7 +522,15 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; + ++/* undoes the shadowing of static from above */ ++#endif ++#undef static ++ ++ + #ifdef XWAYLAND ++#ifdef HOT ++#define static ++ + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); + static void configurex11(struct wl_listener *listener, void *data); +@@ -446,11 +540,45 @@ static void sethints(struct wl_listener *listener, void *data); + static void xwaylandready(struct wl_listener *listener, void *data); + static struct wl_listener new_xwayland_surface = {.notify = createnotifyx11}; + static struct wl_listener xwayland_ready = {.notify = xwaylandready}; ++ ++#define static extern ++#else ++#define static ++#endif ++ + static struct wlr_xwayland *xwayland; ++ ++#undef static ++#endif ++ ++/* this is where we put global hot-reload state */ ++#ifdef HOT ++#define COLD extern ++#else ++#define COLD ++ ++static void* load(void); ++static const char* get_module_path(void); ++ ++#endif ++ ++COLD void * dwl_module = NULL; ++COLD void * last_module = NULL; ++COLD struct listens* listeners = NULL; ++COLD void reload(const Arg* arg); ++ ++#ifndef HOT ++static char* runpath; ++ + #endif + ++ ++#ifdef HOT ++ ++#define static + /* configuration, allows nested code to access above variables */ + #include "config.h" ++#undef static + + /* attempt to encapsulate suck into one file */ + #include "client.h" +@@ -695,10 +823,12 @@ checkidleinhibitor(struct wlr_surface *exclude) + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); + } + ++#endif ++ + void + cleanup(void) + { +- cleanuplisteners(); ++ TSYM(void (*)(void), cleanuplisteners)(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -710,7 +840,7 @@ cleanup(void) + } + wlr_xcursor_manager_destroy(cursor_mgr); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ TSYM(void (*)(struct wl_listener*, void*), destroykeyboardgroup)(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ +@@ -722,6 +852,8 @@ cleanup(void) + wlr_scene_node_destroy(&scene->tree.node); + } + ++#ifdef HOT ++ + void + cleanupmon(struct wl_listener *listener, void *data) + { +@@ -735,10 +867,10 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + +- wl_list_remove(&m->destroy.link); +- wl_list_remove(&m->frame.link); ++ UNLISTEN(&m->destroy); ++ UNLISTEN(&m->frame); + wl_list_remove(&m->link); +- wl_list_remove(&m->request_state.link); ++ UNLISTEN(&m->request_state); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +@@ -751,37 +883,37 @@ cleanupmon(struct wl_listener *listener, void *data) + void + cleanuplisteners(void) + { +- wl_list_remove(&cursor_axis.link); +- wl_list_remove(&cursor_button.link); +- wl_list_remove(&cursor_frame.link); +- wl_list_remove(&cursor_motion.link); +- wl_list_remove(&cursor_motion_absolute.link); +- wl_list_remove(&gpu_reset.link); +- wl_list_remove(&new_idle_inhibitor.link); +- wl_list_remove(&layout_change.link); +- wl_list_remove(&new_input_device.link); +- wl_list_remove(&new_virtual_keyboard.link); +- wl_list_remove(&new_virtual_pointer.link); +- wl_list_remove(&new_pointer_constraint.link); +- wl_list_remove(&new_output.link); +- wl_list_remove(&new_xdg_toplevel.link); +- wl_list_remove(&new_xdg_decoration.link); +- wl_list_remove(&new_xdg_popup.link); +- wl_list_remove(&new_layer_surface.link); +- wl_list_remove(&output_mgr_apply.link); +- wl_list_remove(&output_mgr_test.link); +- wl_list_remove(&output_power_mgr_set_mode.link); +- wl_list_remove(&request_activate.link); +- wl_list_remove(&request_cursor.link); +- wl_list_remove(&request_set_psel.link); +- wl_list_remove(&request_set_sel.link); +- wl_list_remove(&request_set_cursor_shape.link); +- wl_list_remove(&request_start_drag.link); +- wl_list_remove(&start_drag.link); +- wl_list_remove(&new_session_lock.link); ++ UNLISTEN(&cursor_axis); ++ UNLISTEN(&cursor_button); ++ UNLISTEN(&cursor_frame); ++ UNLISTEN(&cursor_motion); ++ UNLISTEN(&cursor_motion_absolute); ++ UNLISTEN(&gpu_reset); ++ UNLISTEN(&new_idle_inhibitor); ++ UNLISTEN(&layout_change); ++ UNLISTEN(&new_input_device); ++ UNLISTEN(&new_virtual_keyboard); ++ UNLISTEN(&new_virtual_pointer); ++ UNLISTEN(&new_pointer_constraint); ++ UNLISTEN(&new_output); ++ UNLISTEN(&new_xdg_toplevel); ++ UNLISTEN(&new_xdg_decoration); ++ UNLISTEN(&new_xdg_popup); ++ UNLISTEN(&new_layer_surface); ++ UNLISTEN(&output_mgr_apply); ++ UNLISTEN(&output_mgr_test); ++ UNLISTEN(&output_power_mgr_set_mode); ++ UNLISTEN(&request_activate); ++ UNLISTEN(&request_cursor); ++ UNLISTEN(&request_set_psel); ++ UNLISTEN(&request_set_sel); ++ UNLISTEN(&request_set_cursor_shape); ++ UNLISTEN(&request_start_drag); ++ UNLISTEN(&start_drag); ++ UNLISTEN(&new_session_lock); + #ifdef XWAYLAND +- wl_list_remove(&new_xwayland_surface.link); +- wl_list_remove(&xwayland_ready.link); ++ UNLISTEN(&new_xwayland_surface); ++ UNLISTEN(&xwayland_ready); + #endif + } + +@@ -908,7 +1040,7 @@ commitpopup(struct wl_listener *listener, void *data) + box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x); + box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1239,8 +1371,8 @@ destroydecoration(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, destroy_decoration); + +- wl_list_remove(&c->destroy_decoration.link); +- wl_list_remove(&c->set_decoration_mode.link); ++ UNLISTEN(&c->destroy_decoration); ++ UNLISTEN(&c->set_decoration_mode); + } + + void +@@ -1249,7 +1381,7 @@ destroydragicon(struct wl_listener *listener, void *data) + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1259,7 +1391,7 @@ destroyidleinhibitor(struct wl_listener *listener, void *data) + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +- wl_list_remove(&listener->link); ++ UNLISTEN(listener); + free(listener); + } + +@@ -1269,9 +1401,9 @@ destroylayersurfacenotify(struct wl_listener *listener, void *data) + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); +- wl_list_remove(&l->destroy.link); +- wl_list_remove(&l->unmap.link); +- wl_list_remove(&l->surface_commit.link); ++ UNLISTEN(&l->destroy); ++ UNLISTEN(&l->unmap); ++ UNLISTEN(&l->surface_commit); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +@@ -1290,9 +1422,9 @@ destroylock(SessionLock *lock, int unlock) + motionnotify(0, NULL, 0, 0, 0, 0); + + destroy: +- wl_list_remove(&lock->new_surface.link); +- wl_list_remove(&lock->unlock.link); +- wl_list_remove(&lock->destroy.link); ++ UNLISTEN(&lock->new_surface); ++ UNLISTEN(&lock->unlock); ++ UNLISTEN(&lock->destroy); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; +@@ -1306,7 +1438,7 @@ destroylocksurface(struct wl_listener *listener, void *data) + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; +- wl_list_remove(&m->destroy_lock_surface.link); ++ UNLISTEN(&m->destroy_lock_surface); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; +@@ -1326,23 +1458,23 @@ destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); +- wl_list_remove(&c->destroy.link); +- wl_list_remove(&c->set_title.link); +- wl_list_remove(&c->fullscreen.link); ++ UNLISTEN(&c->destroy); ++ UNLISTEN(&c->set_title); ++ UNLISTEN(&c->fullscreen); + #ifdef XWAYLAND + if (c->type != XDGShell) { +- wl_list_remove(&c->activate.link); +- wl_list_remove(&c->associate.link); +- wl_list_remove(&c->configure.link); +- wl_list_remove(&c->dissociate.link); +- wl_list_remove(&c->set_hints.link); ++ UNLISTEN(&c->activate); ++ UNLISTEN(&c->associate); ++ UNLISTEN(&c->configure); ++ UNLISTEN(&c->dissociate); ++ UNLISTEN(&c->set_hints); + } else + #endif + { +- wl_list_remove(&c->commit.link); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); +- wl_list_remove(&c->maximize.link); ++ UNLISTEN(&c->commit); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); ++ UNLISTEN(&c->maximize); + } + free(c); + } +@@ -1357,7 +1489,7 @@ destroypointerconstraint(struct wl_listener *listener, void *data) + active_constraint = NULL; + } + +- wl_list_remove(&pointer_constraint->destroy.link); ++ UNLISTEN(&pointer_constraint->destroy); + free(pointer_constraint); + } + +@@ -1373,9 +1505,9 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + { + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); +- wl_list_remove(&group->key.link); +- wl_list_remove(&group->modifiers.link); +- wl_list_remove(&group->destroy.link); ++ UNLISTEN(&group->key); ++ UNLISTEN(&group->modifiers); ++ UNLISTEN(&group->destroy); + wlr_keyboard_group_destroy(group->wlr_group); + free(group); + } +@@ -1541,8 +1673,8 @@ gpureset(struct wl_listener *listener, void *data) + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + +- wl_list_remove(&gpu_reset.link); +- wl_signal_add(&drw->events.lost, &gpu_reset); ++ UNLISTEN(&gpu_reset); ++ LISTEN_GLOBAL(&drw->events.lost, gpu_reset); + + wlr_compositor_set_renderer(compositor, drw); + +@@ -2232,6 +2364,8 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++#else /*HOT*/ ++ + void + run(char *startup_cmd) + { +@@ -2271,11 +2405,11 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ TSYM(void (*)(void), printstatus)(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +- selmon = xytomon(cursor->x, cursor->y); ++ selmon = TSYM(Monitor* (*)(double x, double y), xytomon)(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully +@@ -2291,6 +2425,9 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++#endif ++#ifdef HOT ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2437,17 +2574,19 @@ setsel(struct wl_listener *listener, void *data) + wlr_seat_set_selection(seat, event->source, event->serial); + } + ++#else /*HOT*/ ++ + void + setup(void) + { + int drm_fd, i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; +- struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = SYM(handlesig)}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +- wlr_log_init(log_level, NULL); ++ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ +@@ -2463,7 +2602,7 @@ setup(void) + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); +- root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); ++ root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, TSYM(float*, rootcolor)); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); +@@ -2475,7 +2614,7 @@ setup(void) + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); +- wl_signal_add(&drw->events.lost, &gpu_reset); ++ LISTEN_GLOBAL(&drw->events.lost, gpu_reset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: +@@ -2522,24 +2661,24 @@ setup(void) + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); +- wl_signal_add(&activation->events.request_activate, &request_activate); ++ LISTEN_GLOBAL(&activation->events.request_activate, request_activate); + + wlr_scene_set_gamma_control_manager_v1(scene, wlr_gamma_control_manager_v1_create(dpy)); + + power_mgr = wlr_output_power_manager_v1_create(dpy); +- wl_signal_add(&power_mgr->events.set_mode, &output_power_mgr_set_mode); ++ LISTEN_GLOBAL(&power_mgr->events.set_mode, output_power_mgr_set_mode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); +- wl_signal_add(&output_layout->events.change, &layout_change); ++ LISTEN_GLOBAL(&output_layout->events.change, layout_change); + + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); +- wl_signal_add(&backend->events.new_output, &new_output); ++ LISTEN_GLOBAL(&backend->events.new_output, new_output); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more +@@ -2551,19 +2690,19 @@ setup(void) + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); +- wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); +- wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); ++ LISTEN_GLOBAL(&xdg_shell->events.new_toplevel, new_xdg_toplevel); ++ LISTEN_GLOBAL(&xdg_shell->events.new_popup, new_xdg_popup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); +- wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); ++ LISTEN_GLOBAL(&layer_shell->events.new_surface, new_layer_surface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); +- wl_signal_add(&idle_inhibit_mgr->events.new_inhibitor, &new_idle_inhibitor); ++ LISTEN_GLOBAL(&idle_inhibit_mgr->events.new_inhibitor, new_idle_inhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); +- wl_signal_add(&session_lock_mgr->events.new_lock, &new_session_lock); ++ LISTEN_GLOBAL(&session_lock_mgr->events.new_lock, new_session_lock); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); +@@ -2573,10 +2712,10 @@ setup(void) + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); +- wl_signal_add(&xdg_decoration_mgr->events.new_toplevel_decoration, &new_xdg_decoration); ++ LISTEN_GLOBAL(&xdg_decoration_mgr->events.new_toplevel_decoration, new_xdg_decoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); +- wl_signal_add(&pointer_constraints->events.new_constraint, &new_pointer_constraint); ++ LISTEN_GLOBAL(&pointer_constraints->events.new_constraint, new_pointer_constraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + +@@ -2604,14 +2743,14 @@ setup(void) + * + * And more comments are sprinkled throughout the notify functions above. + */ +- wl_signal_add(&cursor->events.motion, &cursor_motion); +- wl_signal_add(&cursor->events.motion_absolute, &cursor_motion_absolute); +- wl_signal_add(&cursor->events.button, &cursor_button); +- wl_signal_add(&cursor->events.axis, &cursor_axis); +- wl_signal_add(&cursor->events.frame, &cursor_frame); ++ LISTEN_GLOBAL(&cursor->events.motion, cursor_motion); ++ LISTEN_GLOBAL(&cursor->events.motion_absolute, cursor_motion_absolute); ++ LISTEN_GLOBAL(&cursor->events.button, cursor_button); ++ LISTEN_GLOBAL(&cursor->events.axis, cursor_axis); ++ LISTEN_GLOBAL(&cursor->events.frame, cursor_frame); + + 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); ++ LISTEN_GLOBAL(&cursor_shape_mgr->events.request_set_shape, request_set_cursor_shape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and +@@ -2619,27 +2758,27 @@ setup(void) + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ +- wl_signal_add(&backend->events.new_input, &new_input_device); ++ LISTEN_GLOBAL(&backend->events.new_input, new_input_device); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); +- wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard, +- &new_virtual_keyboard); ++ LISTEN_GLOBAL(&virtual_keyboard_mgr->events.new_virtual_keyboard, ++ new_virtual_keyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); +- wl_signal_add(&virtual_pointer_mgr->events.new_virtual_pointer, +- &new_virtual_pointer); ++ LISTEN_GLOBAL(&virtual_pointer_mgr->events.new_virtual_pointer, ++ new_virtual_pointer); + + seat = wlr_seat_create(dpy, "seat0"); +- wl_signal_add(&seat->events.request_set_cursor, &request_cursor); +- wl_signal_add(&seat->events.request_set_selection, &request_set_sel); +- wl_signal_add(&seat->events.request_set_primary_selection, &request_set_psel); +- wl_signal_add(&seat->events.request_start_drag, &request_start_drag); +- wl_signal_add(&seat->events.start_drag, &start_drag); ++ LISTEN_GLOBAL(&seat->events.request_set_cursor, request_cursor); ++ LISTEN_GLOBAL(&seat->events.request_set_selection, request_set_sel); ++ LISTEN_GLOBAL(&seat->events.request_set_primary_selection, request_set_psel); ++ LISTEN_GLOBAL(&seat->events.request_start_drag, request_start_drag); ++ LISTEN_GLOBAL(&seat->events.start_drag, start_drag); + +- kb_group = createkeyboardgroup(); ++ kb_group = TSYM(KeyboardGroup *(*)(void), createkeyboardgroup)(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); +- wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); +- wl_signal_add(&output_mgr->events.test, &output_mgr_test); ++ LISTEN_GLOBAL(&output_mgr->events.apply, output_mgr_apply); ++ LISTEN_GLOBAL(&output_mgr->events.test, output_mgr_test); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the +@@ -2651,8 +2790,8 @@ setup(void) + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { +- wl_signal_add(&xwayland->events.ready, &xwayland_ready); +- wl_signal_add(&xwayland->events.new_surface, &new_xwayland_surface); ++ LISTEN_GLOBAL(&xwayland->events.ready, xwayland_ready); ++ LISTEN_GLOBAL(&xwayland->events.new_surface, new_xwayland_surface); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { +@@ -2661,6 +2800,9 @@ setup(void) + #endif + } + ++#endif ++#ifdef HOT ++ + void + spawn(const Arg *arg) + { +@@ -3142,8 +3284,8 @@ void + dissociatex11(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, dissociate); +- wl_list_remove(&c->map.link); +- wl_list_remove(&c->unmap.link); ++ UNLISTEN(&c->map); ++ UNLISTEN(&c->unmap); + } + + void +@@ -3178,17 +3320,141 @@ xwaylandready(struct wl_listener *listener, void *data) + } + #endif + ++#else /* HOT */ ++void* ++load(void) ++{ ++ const char* path = get_module_path(); ++ char load[PATH_MAX] = "/tmp/dwl.soXXXXXX"; ++ void* new; ++ ++ if (!path) { ++ fprintf(stderr, "cannot find dwl.so\n"); ++ } ++ ++ do { ++ mktemp(load); ++ errno = 0; ++ symlink(path, load); ++ } while(errno == EEXIST); ++ ++ new = dlopen(load, RTLD_NOW|RTLD_LOCAL); ++ ++ unlink(load); ++ if (new == NULL) ++ fprintf(stderr, "error while loading %s: %s\n", path, dlerror()); ++ else ++ printf("loaded: %s\n", path); ++ ++ return new; ++} ++ ++const char * ++get_module_path(void) { ++ char home[PATH_MAX]; ++ strcpy(home, getenv("HOME")); ++ strcat(home, "/.local/lib"); ++ const char* abspaths[] = {".", home, "/usr/share/lib", "/usr/local/lib", "/usr/local/share/lib"}; ++ const char* relpaths[] = {"", "/../lib"}; ++ char paths[LENGTH(abspaths) + LENGTH(relpaths)][PATH_MAX]; ++ static char out[PATH_MAX] = "./"; ++ ++ for (size_t i = 0; i < LENGTH(abspaths); i++) ++ realpath(abspaths[i], paths[i]); ++ ++ for (size_t i = 0; i < LENGTH(relpaths); i++) ++ { ++ char tmp[PATH_MAX]; ++ strcpy(tmp, runpath); ++ strcat(tmp, relpaths[i]); ++ realpath(tmp, paths[LENGTH(abspaths) + i]); ++ } ++ ++ ++ ++ for (size_t i = 0; i < LENGTH(paths); i++) ++ { ++ char tmp[PATH_MAX]; ++ printf("checking path: %s\n", paths[i]); ++ strcpy(tmp, paths[i]); ++ strcat(tmp, "/dwl.so"); ++ if (access(tmp, F_OK|R_OK) == 0) ++ { ++ strcpy(out, tmp); ++ return out; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++reload(const Arg* arg) ++{ ++ char* error; ++ void* new; ++ size_t i = 0; ++ ++ // deinitialize previous module ++ if (last_module) { ++ // dlclose(last_module); ++ last_module = NULL; ++ } ++ ++ wlr_log(WLR_INFO, "reloading"); ++ ++ new = load(); ++ ++ if (new == NULL) ++ { ++ wlr_log(WLR_ERROR, "couldn't load new dwl module from %s", get_module_path()); ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "failed to reload dwl", NULL); ++ return; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- listens ---------"); ++ for(listens* a = listeners; a != NULL; a = a->next) ++ { ++ Dl_info info; ++ void* old = a->listen->notify; ++ dladdr(a->listen->notify, &info); ++ a->listen->notify = dlsym(new, info.dli_sname); ++ if ((error = dlerror()) != NULL){ ++ fprintf(stderr, "reload failure: %s", error); ++ a->listen->notify = old; ++ return; ++ } ++ wlr_log(WLR_DEBUG, "replaced listener: %s", info.dli_sname); ++ i++; ++ } ++ ++ wlr_log(WLR_DEBUG, "---------- done! ---------"); ++ wlr_log(WLR_DEBUG, "replaced %zu listeners", i); ++ ++ last_module = dwl_module; ++ dwl_module = new; ++ ++ if (fork() == 0) ++ execl("/bin/env", "--", "notify-send", "-u", "low", "reloaded dwl", NULL); ++ ++} ++ + int + main(int argc, char *argv[]) + { + char *startup_cmd = NULL; + int c; + ++ runpath = dirname(argv[0]); ++ dwl_module = load(); ++ + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') +- log_level = WLR_DEBUG; ++ CSYM(enum wlr_log_importance, log_level) = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else +@@ -3208,3 +3474,5 @@ main(int argc, char *argv[]) + usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); + } ++ ++#endif +diff --git a/util.c b/util.c +index b925987..8fb9b77 100644 +--- a/util.c ++++ b/util.c +@@ -49,3 +49,37 @@ fd_set_nonblock(int fd) { + + return 0; + } ++ ++struct listens* ++append_listener(struct wl_listener* new, struct listens* list) ++{ ++ struct listens* l = malloc(sizeof(struct listens)); ++ l->listen = new; ++ l->next = list; ++ return l; ++} ++ ++struct listens* ++remove_listener(struct wl_listener* l, struct listens* ls) ++{ ++ struct listens* out = ls; ++ struct listens* f = NULL; ++ for(struct listens* last = NULL; ls != NULL; ls = ls->next) ++ { ++ if (ls->listen == l) ++ { ++ if (last != NULL) ++ last->next = ls->next; ++ else ++ out = ls->next; ++ ++ f = ls; ++ } ++ else ++ last = ls; ++ } ++ ++ free(f); ++ ++ return out; ++} +diff --git a/util.h b/util.h +index 226980d..11aab34 100644 +--- a/util.h ++++ b/util.h +@@ -1,5 +1,11 @@ + /* See LICENSE.dwm file for copyright and license details. */ ++typedef struct listens { ++ struct wl_listener* listen; ++ struct listens* next; ++} listens; + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); + int fd_set_nonblock(int fd); ++struct listens* append_listener(struct wl_listener* l, struct listens* ls); ++struct listens* remove_listener(struct wl_listener* l, struct listens* ls); +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/inputdevicerules/README.md b/dwl/dwl-patches/patches/inputdevicerules/README.md new file mode 100644 index 0000000..3f87ef9 --- /dev/null +++ b/dwl/dwl-patches/patches/inputdevicerules/README.md @@ -0,0 +1,18 @@ +### Description + +Input device rules implemented using custom device create functions for +keyboards and pointing devices. + +Examples provided: + +- ignore unwanted input devices +- configure a toggle input device +- exclude certain keyboards (eg ydotool) from keyboard group + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/inputdevicerules) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/inputdevicerules/inputdevicerules-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/inputdevicerules/inputdevicerules-v0.6.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch b/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch new file mode 100644 index 0000000..b9e9fd2 --- /dev/null +++ b/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch @@ -0,0 +1,173 @@ +From 620a68ffbcecf996916a8fd637f0bcff7a72e004 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] input device rules + +* customise input device handling +* ignore unwanted input devices +* configure a toggle for an input device +--- + config.def.h | 13 ++++++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..88006d79 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,18 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* input devices */ ++static const InputRule inputrules[] = { ++ /* name kbcreate ptrcreate */ ++ /* ignore bad device - like a touchpad ;) */ ++ { "BAD DEVICE", NULL, NULL }, ++ /* ungroup ydotool device - fixes a bug */ ++ { "ydotoold virtual device", createungroupedkeyboard, createpointer }, ++ /* put your touchpad name here to enable toggle touchpad */ ++ { "Elan Touchpad", createkeyboard, createtogglepointer }, ++ { NULL, createkeyboard, createpointer }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +@@ -139,6 +151,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_u, togglepointer, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 3dba2bf5..a11c7587 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++ void (*ptrcreate)(struct wlr_pointer *); ++} InputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -266,6 +272,8 @@ static void createmon(struct wl_listener *listener, void *data); + static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); ++static void createtogglepointer(struct wlr_pointer *pointer); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -335,6 +343,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointer(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -406,6 +415,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct libinput_device *togglepointerdevice = NULL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1092,6 +1103,33 @@ createpointerconstraint(struct wl_listener *listener, void *data) + &pointer_constraint->destroy, destroypointerconstraint); + } + ++void ++createtogglepointer(struct wlr_pointer *pointer) ++{ ++ struct libinput_device *device; ++ ++ createpointer(pointer); ++ ++ if (wlr_input_device_is_libinput(&pointer->base) ++ && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ togglepointerdevice = device; ++ } ++} ++ ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1467,13 +1505,27 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const InputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->kbcreate) ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: +- createpointer(wlr_pointer_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->ptrcreate) ++ r->ptrcreate(wlr_pointer_from_input_device(device)); ++ break; ++ } ++ } + break; + default: + /* TODO handle other input device types */ +@@ -2647,6 +2699,18 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointer(const Arg *arg) ++{ ++ if (!togglepointerdevice) ++ return; ++ ++ libinput_device_config_send_events_set_mode( ++ togglepointerdevice, ++ libinput_device_config_send_events_get_mode(togglepointerdevice) ^ LIBINPUT_CONFIG_SEND_EVENTS_DISABLED ++ ); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch b/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch new file mode 100644 index 0000000..18f6f9e --- /dev/null +++ b/dwl/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch @@ -0,0 +1,173 @@ +From 89f870a04f903681b0a7a0ac4eb1ae70c4984b46 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] input device rules + +* customise input device handling +* ignore unwanted input devices +* configure a toggle for an input device +--- + config.def.h | 13 ++++++++++ + dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0b287ab5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,18 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* input devices */ ++static const InputRule inputrules[] = { ++ /* name kbcreate ptrcreate */ ++ /* ignore bad device - like a touchpad ;) */ ++ { "BAD DEVICE", NULL, NULL }, ++ /* ungroup ydotool device - fixes a bug */ ++ { "ydotoold virtual device", createungroupedkeyboard, createpointer }, ++ /* put your touchpad name here to enable toggle touchpad */ ++ { "Elan Touchpad", createkeyboard, createtogglepointer }, ++ { NULL, createkeyboard, createpointer }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +@@ -142,6 +154,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_u, togglepointer, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index a2711f67..f6f91938 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,6 +143,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++ void (*ptrcreate)(struct wlr_pointer *); ++} InputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -270,6 +276,8 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtogglepointer(struct wlr_pointer *pointer); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -340,6 +348,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointer(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +422,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct libinput_device *togglepointerdevice = NULL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1133,6 +1144,33 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtogglepointer(struct wlr_pointer *pointer) ++{ ++ struct libinput_device *device; ++ ++ createpointer(pointer); ++ ++ if (wlr_input_device_is_libinput(&pointer->base) ++ && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ togglepointerdevice = device; ++ } ++} ++ ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1531,13 +1569,27 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const InputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->kbcreate) ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: +- createpointer(wlr_pointer_from_input_device(device)); ++ for (r = inputrules; r < END(inputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ if (r->ptrcreate) ++ r->ptrcreate(wlr_pointer_from_input_device(device)); ++ break; ++ } ++ } + break; + default: + /* TODO handle other input device types */ +@@ -2739,6 +2791,18 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointer(const Arg *arg) ++{ ++ if (!togglepointerdevice) ++ return; ++ ++ libinput_device_config_send_events_set_mode( ++ togglepointerdevice, ++ libinput_device_config_send_events_get_mode(togglepointerdevice) ^ LIBINPUT_CONFIG_SEND_EVENTS_DISABLED ++ ); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/ipc/README.md b/dwl/dwl-patches/patches/ipc/README.md new file mode 100644 index 0000000..bf648a4 --- /dev/null +++ b/dwl/dwl-patches/patches/ipc/README.md @@ -0,0 +1,28 @@ +### Description +Largely based on [raphi](https://sr.ht/~raphi/)'s [somebar](https://sr.ht/~raphi/somebar/), this patch provides an ipc for wayland clients to get and set dwl state. The ipc is intended for status bars, but can also be scripted with tools like [dwlmsg](https://codeberg.org/notchoc/dwlmsg). + +Status information to stdout is currently disabled as dwl tends to freeze. For now, `dwlmsg -w` should act as a drop-in replacement. + +Note to [pertag](../pertag/) users: apply [this](./ipcpertag.patch) for ipc tagsetting to work as expected + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/ipc) +- [2024-08-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/ipc/ipc.patch) +- [2024-07-29](https://codeberg.org/dwl/dwl-patches/raw/commit/d235f0f88ed069eca234da5a544fb1c6e19f1d33/patches/ipc/ipc.patch) don't focus other outputs (apply [this minipatch](./focus-tagset-output.patch) if you'd prefer that) +- [2024-07-16](https://codeberg.org/dwl/dwl-patches/raw/commit/642b2559d522034785c1c1203c6d426855ec19ca/patches/ipc/ipc.patch) +- [2024-06-30](https://codeberg.org/dwl/dwl-patches/raw/commit/9a751e5020133d3ab9219e68a43109c6f3c931a7/patches/ipc/ipc.patch) +- [2024-06-21](https://codeberg.org/dwl/dwl-patches/raw/commit/f96ee44cbaef06bd38b8fa29ac7ecba8b1b5abd5/patches/ipc/ipc.patch) +- [2024-06-19](https://codeberg.org/dwl/dwl-patches/raw/commit/e69afc7263b8d982a7923e5d4910f2e1f7140bb8/patches/ipc/ipc.patch) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/raw/commit/f8598a91b44acc3bd7e9041be97265bbce8fa219/patches/ipc/ipc.patch) +- [2024-03-13](https://codeberg.org/dwl/dwl-patches/raw/commit/0150cfebbcd85f2d6e6728afad345a11a0c45947/ipc/ipc.patch) +- [2024-02-20](https://codeberg.org/dwl/dwl-patches/raw/commit/0c5ae06e4bc1d7f641376e8fcb86b43bd48ce2ee/ipc/ipc.patch) +- [2023-10-28](https://gist.githubusercontent.com/fbushstone/b116c44340eb7a7878de1119dd931ca5/raw/ee66ac9e2a5dddd9b528df553e21080c2811e974/ipc-v2-fixed.patch) Updated version of 2023-04-29, prevents ipc from freezing the compositor in printstatus. +- [2023-04-29](https://github.com/djpohly/dwl/compare/main...madcowog:ipc-v2.patch) Use this for dwl-ipc-unstable-v2. If you are using commit [9d68554](https://github.com/djpohly/dwl/commit/9d68554c59a886b641d27a364884fb461af2d4f1) or later, use this. For status bars this protocol is supported by dwlb, Waybar and dwl-bar. +- [2023-04-29](https://github.com/djpohly/dwl/compare/main...madcowog:ipc-bbdf2.patch) Use this for dwl-ipc-unstable-v1. If you are using commit [bbdaf2a9](https://github.com/djpohly/dwl/commit/bbdf2a913b72e7a308ee0dfde6518a4285d4a775), [release 0.4](https://github.com/djpohly/dwl/releases/tag/v0.4) or earlier, use this. For status bars, this protocol is supported by dwl-bar. +- [2023-02-20](https://lists.sr.ht/~raphi/public-inbox/patches/39166) Use this for net-tapesoftware-dwl-wm-unstable-v1. If you are using commit [c69a2bec](https://github.com/djpohly/dwl/commit/c69a2bec3ff417fbc4ea8fec0a49096773e01e7d) or later, use this. For status bars this protocol is supported by somebar. + +### Authors +- [MadcowOG](https://github.com/MadcowOG) +- [fbushstone](https://github.com/fbushstone) +- [notchoc](https://codeberg.org/notchoc) +- [snuk](https://codeberg.org/snuk) diff --git a/dwl/dwl-patches/patches/ipc/focus-tagset-output.patch b/dwl/dwl-patches/patches/ipc/focus-tagset-output.patch new file mode 100644 index 0000000..2ea3015 --- /dev/null +++ b/dwl/dwl-patches/patches/ipc/focus-tagset-output.patch @@ -0,0 +1,24 @@ +From b65477621f0438b0ed6c405f74c9d15e8fb57e96 Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 29 Jul 2024 21:26:06 +0800 +Subject: [PATCH] ipc: focus set_tag'd output + +--- + dwl.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/dwl.c b/dwl.c +index 4a025f0..dcbb063 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1533,6 +1533,7 @@ dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, + if (!ipc_output) + return; + monitor = ipc_output->mon; ++ selmon = monitor; + + if (!newtags || newtags == monitor->tagset[monitor->seltags]) + return; +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/ipc/ipc.patch b/dwl/dwl-patches/patches/ipc/ipc.patch new file mode 100644 index 0000000..9f989e7 --- /dev/null +++ b/dwl/dwl-patches/patches/ipc/ipc.patch @@ -0,0 +1,597 @@ +From 6c6d655b68770ce82a24fde9b58c4d97b672553a Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 23 Oct 2023 10:35:17 +0800 +Subject: [PATCH] implement dwl-ipc-unstable-v2 + https://codeberg.org/dwl/dwl-patches/wiki/ipc + +--- + Makefile | 14 +- + config.def.h | 1 + + dwl.c | 257 ++++++++++++++++++++++++++---- + protocols/dwl-ipc-unstable-v2.xml | 181 +++++++++++++++++++++ + 4 files changed, 419 insertions(+), 34 deletions(-) + create mode 100644 protocols/dwl-ipc-unstable-v2.xml + +diff --git a/Makefile b/Makefile +index 8db7409..a79a080 100644 +--- a/Makefile ++++ b/Makefile +@@ -17,12 +17,14 @@ DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEV + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + + all: dwl +-dwl: dwl.o util.o +- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ ++dwl: dwl.o util.o dwl-ipc-unstable-v2-protocol.o ++ $(CC) dwl.o util.o dwl-ipc-unstable-v2-protocol.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ dwl-ipc-unstable-v2-protocol.h + util.o: util.c util.h ++dwl-ipc-unstable-v2-protocol.o: dwl-ipc-unstable-v2-protocol.c dwl-ipc-unstable-v2-protocol.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland + # protocols, which are specified in XML. wlroots requires you to rig these up +@@ -45,6 +47,12 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++dwl-ipc-unstable-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ protocols/dwl-ipc-unstable-v2.xml $@ ++dwl-ipc-unstable-v2-protocol.c: ++ $(WAYLAND_SCANNER) private-code \ ++ protocols/dwl-ipc-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..1593033 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -127,6 +127,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index 8a587d1..7a4949b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -68,6 +68,7 @@ + #include + #endif + ++#include "dwl-ipc-unstable-v2-protocol.h" + #include "util.h" + + /* macros */ +@@ -144,6 +145,12 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ struct wl_list link; ++ struct wl_resource *resource; ++ Monitor *mon; ++} DwlIpcOutput; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -189,6 +196,7 @@ typedef struct { + + struct Monitor { + struct wl_list link; ++ struct wl_list dwl_ipc_outputs; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ +@@ -286,6 +294,17 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id); ++static void dwl_ipc_manager_destroy(struct wl_resource *resource); ++static void dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output); ++static void dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource); ++static void dwl_ipc_output_destroy(struct wl_resource *resource); ++static void dwl_ipc_output_printstatus(Monitor *monitor); ++static void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output); ++static void dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags); ++static void dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index); ++static void dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset); ++static void dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -338,6 +357,7 @@ static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -411,6 +431,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct zdwl_ipc_manager_v2_interface dwl_manager_implementation = {.release = dwl_ipc_manager_release, .get_output = dwl_ipc_manager_get_output}; ++static struct zdwl_ipc_output_v2_interface dwl_output_implementation = {.release = dwl_ipc_output_release, .set_tags = dwl_ipc_output_set_tags, .set_layout = dwl_ipc_output_set_layout, .set_client_tags = dwl_ipc_output_set_client_tags}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -703,6 +726,10 @@ cleanupmon(struct wl_listener *listener, void *data) + LayerSurface *l, *tmp; + size_t i; + ++ DwlIpcOutput *ipc_output, *ipc_output_tmp; ++ wl_list_for_each_safe(ipc_output, ipc_output_tmp, &m->dwl_ipc_outputs, link) ++ wl_resource_destroy(ipc_output->resource); ++ + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) +@@ -983,6 +1010,8 @@ createmon(struct wl_listener *listener, void *data) + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + ++ wl_list_init(&m->dwl_ipc_outputs); ++ + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + +@@ -1334,6 +1363,192 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++dwl_ipc_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) ++{ ++ struct wl_resource *manager_resource = wl_resource_create(client, &zdwl_ipc_manager_v2_interface, version, id); ++ if (!manager_resource) { ++ wl_client_post_no_memory(client); ++ return; ++ } ++ wl_resource_set_implementation(manager_resource, &dwl_manager_implementation, NULL, dwl_ipc_manager_destroy); ++ ++ zdwl_ipc_manager_v2_send_tags(manager_resource, TAGCOUNT); ++ ++ for (unsigned int i = 0; i < LENGTH(layouts); i++) ++ zdwl_ipc_manager_v2_send_layout(manager_resource, layouts[i].symbol); ++} ++ ++void ++dwl_ipc_manager_destroy(struct wl_resource *resource) ++{ ++ /* No state to destroy */ ++} ++ ++void ++dwl_ipc_manager_get_output(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *output) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor = wlr_output_from_resource(output)->data; ++ struct wl_resource *output_resource = wl_resource_create(client, &zdwl_ipc_output_v2_interface, wl_resource_get_version(resource), id); ++ if (!output_resource) ++ return; ++ ++ ipc_output = ecalloc(1, sizeof(*ipc_output)); ++ ipc_output->resource = output_resource; ++ ipc_output->mon = monitor; ++ wl_resource_set_implementation(output_resource, &dwl_output_implementation, ipc_output, dwl_ipc_output_destroy); ++ wl_list_insert(&monitor->dwl_ipc_outputs, &ipc_output->link); ++ dwl_ipc_output_printstatus_to(ipc_output); ++} ++ ++void ++dwl_ipc_manager_release(struct wl_client *client, struct wl_resource *resource) ++{ ++ wl_resource_destroy(resource); ++} ++ ++static void ++dwl_ipc_output_destroy(struct wl_resource *resource) ++{ ++ DwlIpcOutput *ipc_output = wl_resource_get_user_data(resource); ++ wl_list_remove(&ipc_output->link); ++ free(ipc_output); ++} ++ ++void ++dwl_ipc_output_printstatus(Monitor *monitor) ++{ ++ DwlIpcOutput *ipc_output; ++ wl_list_for_each(ipc_output, &monitor->dwl_ipc_outputs, link) ++ dwl_ipc_output_printstatus_to(ipc_output); ++} ++ ++void ++dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) ++{ ++ Monitor *monitor = ipc_output->mon; ++ Client *c, *focused; ++ int tagmask, state, numclients, focused_client, tag; ++ const char *title, *appid; ++ focused = focustop(monitor); ++ zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); ++ ++ for (tag = 0 ; tag < TAGCOUNT; tag++) { ++ numclients = state = focused_client = 0; ++ tagmask = 1 << tag; ++ if ((tagmask & monitor->tagset[monitor->seltags]) != 0) ++ state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != monitor) ++ continue; ++ if (!(c->tags & tagmask)) ++ continue; ++ if (c == focused) ++ focused_client = 1; ++ if (c->isurgent) ++ state |= ZDWL_IPC_OUTPUT_V2_TAG_STATE_URGENT; ++ ++ numclients++; ++ } ++ zdwl_ipc_output_v2_send_tag(ipc_output->resource, tag, state, numclients, focused_client); ++ } ++ title = focused ? client_get_title(focused) : ""; ++ appid = focused ? client_get_appid(focused) : ""; ++ ++ zdwl_ipc_output_v2_send_layout(ipc_output->resource, monitor->lt[monitor->sellt] - layouts); ++ zdwl_ipc_output_v2_send_title(ipc_output->resource, title); ++ zdwl_ipc_output_v2_send_appid(ipc_output->resource, appid); ++ zdwl_ipc_output_v2_send_layout_symbol(ipc_output->resource, monitor->ltsymbol); ++ if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FULLSCREEN_SINCE_VERSION) { ++ zdwl_ipc_output_v2_send_fullscreen(ipc_output->resource, focused ? focused->isfullscreen : 0); ++ } ++ if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FLOATING_SINCE_VERSION) { ++ zdwl_ipc_output_v2_send_floating(ipc_output->resource, focused ? focused->isfloating : 0); ++ } ++ zdwl_ipc_output_v2_send_frame(ipc_output->resource); ++} ++ ++void ++dwl_ipc_output_set_client_tags(struct wl_client *client, struct wl_resource *resource, uint32_t and_tags, uint32_t xor_tags) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ Client *selected_client; ++ unsigned int newtags = 0; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ ++ monitor = ipc_output->mon; ++ selected_client = focustop(monitor); ++ if (!selected_client) ++ return; ++ ++ newtags = (selected_client->tags & and_tags) ^ xor_tags; ++ if (!newtags) ++ return; ++ ++ selected_client->tags = newtags; ++ if (selmon == monitor) ++ focusclient(focustop(monitor), 1); ++ arrange(selmon); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ ++ monitor = ipc_output->mon; ++ if (index >= LENGTH(layouts)) ++ return; ++ if (index != monitor->lt[monitor->sellt] - layouts) ++ monitor->sellt ^= 1; ++ ++ monitor->lt[monitor->sellt] = &layouts[index]; ++ arrange(monitor); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset) ++{ ++ DwlIpcOutput *ipc_output; ++ Monitor *monitor; ++ unsigned int newtags = tagmask & TAGMASK; ++ ++ ipc_output = wl_resource_get_user_data(resource); ++ if (!ipc_output) ++ return; ++ monitor = ipc_output->mon; ++ ++ if (!newtags || newtags == monitor->tagset[monitor->seltags]) ++ return; ++ if (toggle_tagset) ++ monitor->seltags ^= 1; ++ ++ monitor->tagset[monitor->seltags] = newtags; ++ if (selmon == monitor) ++ focusclient(focustop(monitor), 1); ++ arrange(monitor); ++ printstatus(); ++} ++ ++void ++dwl_ipc_output_release(struct wl_client *client, struct wl_resource *resource) ++{ ++ wl_resource_destroy(resource); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -2033,38 +2248,9 @@ void + printstatus(void) + { + Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; + +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); ++ wl_list_for_each(m, &mons, link) ++ dwl_ipc_output_printstatus(m); + } + + void +@@ -2584,6 +2770,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ wl_global_create(dpy, &zdwl_ipc_manager_v2_interface, 2, NULL, dwl_ipc_manager_bind); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2681,6 +2869,13 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) { ++ DwlIpcOutput *ipc_output; ++ wl_list_for_each(ipc_output, &selmon->dwl_ipc_outputs, link) ++ zdwl_ipc_output_v2_send_toggle_visibility(ipc_output->resource); ++} ++ + void + togglefloating(const Arg *arg) + { +diff --git a/protocols/dwl-ipc-unstable-v2.xml b/protocols/dwl-ipc-unstable-v2.xml +new file mode 100644 +index 0000000..0a6e7e5 +--- /dev/null ++++ b/protocols/dwl-ipc-unstable-v2.xml +@@ -0,0 +1,181 @@ ++ ++ ++ ++ ++ This protocol allows clients to update and get updates from dwl. ++ ++ Warning! The protocol described in this file is experimental and ++ backward incompatible changes may be made. Backward compatible ++ changes may be added together with the corresponding interface ++ version bump. ++ Backward incompatible changes are done by bumping the version ++ number in the protocol and interface names and resetting the ++ interface version. Once the protocol is to be declared stable, ++ the 'z' prefix and the version number in the protocol and ++ interface names are removed and the interface version number is ++ reset. ++ ++ ++ ++ ++ This interface is exposed as a global in wl_registry. ++ ++ Clients can use this interface to get a dwl_ipc_output. ++ After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events. ++ The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client. ++ ++ ++ ++ ++ Indicates that the client will not the dwl_ipc_manager object anymore. ++ Objects created through this instance are not affected. ++ ++ ++ ++ ++ ++ Get a dwl_ipc_outout for the specified wl_output. ++ ++ ++ ++ ++ ++ ++ ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all tags. ++ ++ ++ ++ ++ ++ ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all layouts. ++ ++ ++ ++ ++ ++ ++ ++ Observe and control a dwl output. ++ ++ Events are double-buffered: ++ Clients should cache events and redraw when a dwl_ipc_output.frame event is sent. ++ ++ Request are not double-buffered: ++ The compositor will update immediately upon request. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates to that the client no longer needs this dwl_ipc_output. ++ ++ ++ ++ ++ ++ Indicates the client should hide or show themselves. ++ If the client is visible then hide, if hidden then show. ++ ++ ++ ++ ++ ++ Indicates if the output is active. Zero is invalid, nonzero is valid. ++ ++ ++ ++ ++ ++ ++ Indicates that a tag has been updated. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates a new layout is selected. ++ ++ ++ ++ ++ ++ ++ Indicates the title has changed. ++ ++ ++ ++ ++ ++ ++ Indicates the appid has changed. ++ ++ ++ ++ ++ ++ ++ Indicates the layout has changed. Since layout symbols are dynamic. ++ As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying. ++ You can ignore the zdwl_ipc_output.layout event. ++ ++ ++ ++ ++ ++ ++ Indicates that a sequence of status updates have finished and the client should redraw. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ The tags are updated as follows: ++ new_tags = (current_tags AND and_tags) XOR xor_tags ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Indicates if the selected client on this output is fullscreen. ++ ++ ++ ++ ++ ++ ++ Indicates if the selected client on this output is floating. ++ ++ ++ ++ ++ +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/ipc/ipcpertag.patch b/dwl/dwl-patches/patches/ipc/ipcpertag.patch new file mode 100644 index 0000000..97e3c33 --- /dev/null +++ b/dwl/dwl-patches/patches/ipc/ipcpertag.patch @@ -0,0 +1,97 @@ +From c7d77ff4dec1da5a68b4da8aa42d4ed78dc41a00 Mon Sep 17 00:00:00 2001 +From: choc +Date: Thu, 14 Mar 2024 11:18:37 +0800 +Subject: [PATCH] fix ipc to work with pertag + +--- + dwl.c | 50 +++++++++++++++++++++++++++++++++++--------------- + 1 file changed, 35 insertions(+), 15 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a1a7809..d86e6e2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1526,28 +1526,37 @@ void + dwl_ipc_output_set_layout(struct wl_client *client, struct wl_resource *resource, uint32_t index) + { + DwlIpcOutput *ipc_output; +- Monitor *monitor; ++ Client *c = NULL; ++ Monitor *monitor = NULL; + + ipc_output = wl_resource_get_user_data(resource); + if (!ipc_output) + return; +- + monitor = ipc_output->mon; ++ ++ if (monitor != selmon) ++ c = focustop(selmon); ++ + if (index >= LENGTH(layouts)) + return; +- if (index != monitor->lt[monitor->sellt] - layouts) +- monitor->sellt ^= 1; + +- monitor->lt[monitor->sellt] = &layouts[index]; +- arrange(monitor); +- printstatus(); ++ if (c) { ++ monitor = selmon; ++ selmon = ipc_output->mon; ++ } ++ setlayout(&(Arg){.v = &layouts[index]}); ++ if (c) { ++ selmon = monitor; ++ focusclient(c, 0); ++ } + } + + void + dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, uint32_t tagmask, uint32_t toggle_tagset) + { + DwlIpcOutput *ipc_output; +- Monitor *monitor; ++ Client *c = NULL; ++ Monitor *monitor = NULL; + unsigned int newtags = tagmask & TAGMASK; + + ipc_output = wl_resource_get_user_data(resource); +@@ -1555,16 +1564,27 @@ dwl_ipc_output_set_tags(struct wl_client *client, struct wl_resource *resource, + return; + monitor = ipc_output->mon; + +- if (!newtags || newtags == monitor->tagset[monitor->seltags]) ++ if (monitor != selmon) ++ c = focustop(selmon); ++ ++ if (!newtags) + return; +- if (toggle_tagset) ++ ++ /* view toggles seltags for us so we un-toggle it */ ++ if (!toggle_tagset) { + monitor->seltags ^= 1; ++ monitor->tagset[monitor->seltags] = 0; ++ } + +- monitor->tagset[monitor->seltags] = newtags; +- if (selmon == monitor) +- focusclient(focustop(monitor), 1); +- arrange(monitor); +- printstatus(); ++ if (c) { ++ monitor = selmon; ++ selmon = ipc_output->mon; ++ } ++ view(&(Arg){.ui = newtags}); ++ if (c) { ++ selmon = monitor; ++ focusclient(c, 0); ++ } + } + + void +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/kblayout/README.md b/dwl/dwl-patches/patches/kblayout/README.md new file mode 100644 index 0000000..259694c --- /dev/null +++ b/dwl/dwl-patches/patches/kblayout/README.md @@ -0,0 +1,32 @@ +### Description + +This patch adds per-client keyboard layout and ability to send current +keyboard layout information to a status bar. + +Only per-client feature is enabled by default. You can edit +`kblayout_file` and `kblayout_cmd` variables to notify a status bar +about keyboard layout. + +[Someblocks](https://sr.ht/~raphi/someblocks) config that works +with the example settings in `config.h`: + +```c +static const Block blocks[] = { + /*Icon*/ /*Command*/ /*Update Interval*/ /*Update Signal*/ + {"", "cat /tmp/dwl-kblayout", 0, 3}, +}; +``` + +Both of these features are included in one patch because their +implementation happens to share some code. If you don't need +any of these features, just disable it in `config.h`. + +### Download + +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/kblayout) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/kblayout/kblayout.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [ForzCross](https://codeberg.org/ForzCross) diff --git a/dwl/dwl-patches/patches/kblayout/kblayout.patch b/dwl/dwl-patches/patches/kblayout/kblayout.patch new file mode 100644 index 0000000..707a4fc --- /dev/null +++ b/dwl/dwl-patches/patches/kblayout/kblayout.patch @@ -0,0 +1,187 @@ +From ad18a8b8e9de138c3d89246ac0e25c0467ff5971 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 11 Oct 2024 10:50:14 +0200 +Subject: [PATCH] Add per client keyboard layout and status bar info + +--- + config.def.h | 3 +++ + dwl.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 72 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..862c104 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,9 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++/* keyboard layout change notification for status bar */ ++static const char kblayout_file[] = "/tmp/dwl-keymap"; ++static const char *kblayout_cmd[] = {"pkill", "-RTMIN+3", "someblocks", NULL}; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index a2711f6..95ca3d3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -141,6 +142,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ unsigned int kblayout_idx; + } Client; + + typedef struct { +@@ -294,6 +296,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); ++static void kblayout(KeyboardGroup *kb); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); +@@ -413,6 +416,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static unsigned int kblayout_idx = -1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -879,6 +884,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); ++ ++ kblayout(kb_group); + } + + KeyboardGroup * +@@ -1056,11 +1063,13 @@ createnotify(struct wl_listener *listener, void *data) + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; ++ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->kblayout_idx = kb ? kb->modifiers.group : 0; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1339,10 +1348,24 @@ dirtomon(enum wlr_direction dir) + void + focusclient(Client *c, int lift) + { ++ /* Copied from wlroots/types/wlr_keyboard_group.c */ ++ struct keyboard_group_device { ++ struct wlr_keyboard *keyboard; ++ struct wl_listener key; ++ struct wl_listener modifiers; ++ struct wl_listener keymap; ++ struct wl_listener repeat_info; ++ struct wl_listener destroy; ++ struct wl_list link; // wlr_keyboard_group.devices ++ }; ++ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; ++ struct keyboard_group_device *device; ++ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat); ++ struct wlr_keyboard_group *group = kb ? wlr_keyboard_group_from_wlr_keyboard(kb) : NULL; + + if (locked) + return; +@@ -1395,6 +1418,19 @@ focusclient(Client *c, int lift) + } + printstatus(); + ++ /* Update keyboard layout */ ++ if (group) { ++ // Update the first real device, because kb or group->kb is not a real ++ // keyboard and its effective layout gets overwritten ++ device = wl_container_of(group->devices.next, device, link); ++ wlr_keyboard_notify_modifiers(device->keyboard, ++ device->keyboard->modifiers.depressed, ++ device->keyboard->modifiers.latched, ++ device->keyboard->modifiers.locked, ++ c ? c->kblayout_idx : 0 ++ ); ++ } ++ + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); +@@ -1405,7 +1441,7 @@ focusclient(Client *c, int lift) + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ +- client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); ++ client_notify_enter(client_surface(c), kb); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +@@ -1554,6 +1590,36 @@ inputdevice(struct wl_listener *listener, void *data) + wlr_seat_set_capabilities(seat, caps); + } + ++void ++kblayout(KeyboardGroup *kb) ++{ ++ FILE *f; ++ Client *c; ++ unsigned int idx = kb->wlr_group->keyboard.modifiers.group; ++ ++ // If layout did not change, do nothing ++ if (kblayout_idx == idx) ++ return; ++ kblayout_idx = idx; ++ ++ // Update client layout ++ if ((c = focustop(selmon))) ++ c->kblayout_idx = kblayout_idx; ++ ++ // Save current layout to kblayout_file ++ if (*kblayout_file && (f = fopen(kblayout_file, "w"))) { ++ fputs(xkb_keymap_layout_get_name(kb->wlr_group->keyboard.keymap, ++ idx), f); ++ fclose(f); ++ } ++ ++ // Run kblayout_cmd ++ if (kblayout_cmd[0] && fork() == 0) { ++ execvp(kblayout_cmd[0], (char *const *)kblayout_cmd); ++ die("dwl: execvp %s failed:", kblayout_cmd[0]); ++ } ++} ++ + int + keybinding(uint32_t mods, xkb_keysym_t sym) + { +@@ -1631,6 +1697,8 @@ keypressmod(struct wl_listener *listener, void *data) + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); ++ ++ kblayout(group); + } + + int +-- +2.46.2 + diff --git a/dwl/dwl-patches/patches/keyboardshortcutsinhibit/README.md b/dwl/dwl-patches/patches/keyboardshortcutsinhibit/README.md new file mode 100644 index 0000000..c52de56 --- /dev/null +++ b/dwl/dwl-patches/patches/keyboardshortcutsinhibit/README.md @@ -0,0 +1,11 @@ +### Description +Allows clients to use the keyboard-shortcuts-inhibit protocol to block the compositor from using keybinds. This is useful for virtualization software like looking-glass which requires this protocol to run. + +### Download +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/patch/keyboard-shortcuts-inhibit) +- [2024-05-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch) +- [2023-05-01](https://github.com/djpohly/dwl/compare/main...madcowog:keyboard-shortcuts-inhibit.patch) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [MadcowOG](https://github.com/MadcowOG) diff --git a/dwl/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch b/dwl/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch new file mode 100644 index 0000000..bea327e --- /dev/null +++ b/dwl/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch @@ -0,0 +1,101 @@ +From ac1fa09172a401427cabbda045688903bdd3cbe7 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Wed, 12 Jun 2024 20:05:40 +0200 +Subject: [PATCH] feat: apply keyboard shortcuts inhibit patch + +--- + dwl.c | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 6f041a0..8cab9e0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -267,6 +268,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); ++static void createshortcutsinhibitor(struct wl_listener *listener, void *data); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); + static void destroydecoration(struct wl_listener *listener, void *data); +@@ -280,6 +282,7 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroyshortcutsinhibitmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -375,6 +378,7 @@ static struct wl_list clients; /* tiling order */ + static struct wl_list fstack; /* focus order */ + static struct wlr_idle_notifier_v1 *idle_notifier; + static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; ++static struct wlr_keyboard_shortcuts_inhibit_manager_v1 *shortcuts_inhibit_mgr; + static struct wlr_layer_shell_v1 *layer_shell; + static struct wlr_output_manager_v1 *output_mgr; + static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +@@ -394,6 +398,7 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; ++static struct wl_listener new_shortcuts_inhibitor = {.notify = createshortcutsinhibitor}; + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; +@@ -1083,6 +1088,10 @@ createpointer(struct wlr_pointer *pointer) + wlr_cursor_attach_input_device(cursor, &pointer->base); + } + ++void createshortcutsinhibitor(struct wl_listener *listener, void *data) { ++ wlr_keyboard_shortcuts_inhibitor_v1_activate(data); ++} ++ + void + createpointerconstraint(struct wl_listener *listener, void *data) + { +@@ -1280,6 +1289,11 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void destroyshortcutsinhibitmgr(struct wl_listener *listener, void *data) { ++ wl_list_remove(&new_shortcuts_inhibitor.link); ++ wl_list_remove(&listener->link); ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1531,7 +1545,9 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { ++ if (!locked ++ && event->state == WL_KEYBOARD_KEY_STATE_PRESSED ++ && wl_list_empty(&shortcuts_inhibit_mgr->inhibitors)) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } +@@ -2450,6 +2466,10 @@ setup(void) + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + ++ shortcuts_inhibit_mgr = wlr_keyboard_shortcuts_inhibit_v1_create(dpy); ++ wl_signal_add(&shortcuts_inhibit_mgr->events.new_inhibitor, &new_shortcuts_inhibitor); ++ LISTEN_STATIC(&shortcuts_inhibit_mgr->events.destroy, destroyshortcutsinhibitmgr); ++ + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), +-- +2.44.1 + diff --git a/dwl/dwl-patches/patches/keycodes/README.md b/dwl/dwl-patches/patches/keycodes/README.md new file mode 100644 index 0000000..b56adc3 --- /dev/null +++ b/dwl/dwl-patches/patches/keycodes/README.md @@ -0,0 +1,20 @@ +### Description +Use keycodes instead of keysyms. This way, input is independent from keyboard +layout (you can use the keys.h file to customize, or get the keycodes with +`wev` or `xkbcli interactive-wayland` (x11-libs/libxkbcommon[tools] in gentoo)). + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/keycodes) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/keycodes/keycodes.patch) +- [keycodes-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/keycodes/keycodes-0.7.patch) + +### Config after patching +(run in DWL source directory) +``` +export XKB_DEFAULT_VARIANT=yourbestkeyboardlayout +cc -lxkbcommon -o generate-keys generate-keys.c +./generate-keys +``` + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/keycodes/keycodes-0.7.patch b/dwl/dwl-patches/patches/keycodes/keycodes-0.7.patch new file mode 100644 index 0000000..7852307 --- /dev/null +++ b/dwl/dwl-patches/patches/keycodes/keycodes-0.7.patch @@ -0,0 +1,883 @@ +From 4985178cfed599c9a32d154f42e02cb66db7a82d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Fri, 4 Jun 2021 16:51:01 -0500 +Subject: [PATCH 1/2] allow use keycodes instead keysyms +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 85 ++++----- + dwl.c | 35 ++-- + keys.h | 514 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 569 insertions(+), 65 deletions(-) + create mode 100644 keys.h + +diff --git a/config.def.h b/config.def.h +index 22d2171d..87a6e60f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -109,11 +109,11 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +-#define TAGKEYS(KEY,SKEY,TAG) \ ++#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_SHIFT, KEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,KEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -122,51 +122,52 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#include "keys.h" + static const Key keys[] = { +- /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, +- TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), +- TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), +- TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +- TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), +- TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), +- TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), +- TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), +- TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), +- TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ /* modifier key function argument */ ++ { MODKEY, Key_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_Return, spawn, {.v = termcmd} }, ++ { MODKEY, Key_j, focusstack, {.i = +1} }, ++ { MODKEY, Key_k, focusstack, {.i = -1} }, ++ { MODKEY, Key_i, incnmaster, {.i = +1} }, ++ { MODKEY, Key_d, incnmaster, {.i = -1} }, ++ { MODKEY, Key_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, Key_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, Key_Return, zoom, {0} }, ++ { MODKEY, Key_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_c, killclient, {0} }, ++ { MODKEY, Key_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, Key_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, Key_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, Key_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_space, togglefloating, {0} }, ++ { MODKEY, Key_e, togglefullscreen, {0} }, ++ { MODKEY, Key_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_0, tag, {.ui = ~0} }, ++ { MODKEY, Key_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, Key_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_period, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ TAGKEYS( Key_1, 0), ++ TAGKEYS( Key_2, 1), ++ TAGKEYS( Key_3, 2), ++ TAGKEYS( Key_4, 3), ++ TAGKEYS( Key_5, 4), ++ TAGKEYS( Key_6, 5), ++ TAGKEYS( Key_7, 6), ++ TAGKEYS( Key_8, 7), ++ TAGKEYS( Key_9, 8), ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,Key_BackSpace, quit, {0} }, ++#define CHVT(KEY,n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT, KEY, chvt, {.ui = (n)} } + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } +- CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), +- CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++ CHVT(Key_F1, 1), CHVT(Key_F2, 2), CHVT(Key_F3, 3), CHVT(Key_F4, 4), ++ CHVT(Key_F5, 5), CHVT(Key_F6, 6), CHVT(Key_F7, 7), CHVT(Key_F8, 8), ++ CHVT(Key_F9, 9), CHVT(Key_F10, 10), CHVT(Key_F11, 11), CHVT(Key_F12, 12), + }; + + static const Button buttons[] = { +diff --git a/dwl.c b/dwl.c +index a2711f67..68860d4e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -145,7 +145,7 @@ typedef struct { + + typedef struct { + uint32_t mod; +- xkb_keysym_t keysym; ++ xkb_keycode_t keycode; + void (*func)(const Arg *); + const Arg arg; + } Key; +@@ -154,9 +154,8 @@ typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + +- int nsyms; +- const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ +- uint32_t mods; /* invalid if nsyms == 0 */ ++ xkb_keycode_t keycode; ++ uint32_t mods; /* invalid if keycode == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; +@@ -294,7 +293,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keycode_t keycode); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1555,7 +1554,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keycode_t keycode) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1565,7 +1564,7 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && keycode == k->keycode && k->func) { + k->func(&k->arg); + return 1; + } +@@ -1576,17 +1575,12 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + void + keypress(struct wl_listener *listener, void *data) + { +- int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +- /* Get a list of keysyms based on the keymap for this keyboard */ +- const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); +@@ -1595,19 +1589,16 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { +- for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; +- } ++ if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ handled = keybinding(mods, keycode); + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; +- group->keysyms = syms; +- group->nsyms = nsyms; ++ group->keycode = keycode; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { +- group->nsyms = 0; ++ group->keycode = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + +@@ -1637,15 +1628,13 @@ int + keyrepeat(void *data) + { + KeyboardGroup *group = data; +- int i; +- if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) ++ if (!group->keycode || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + +- for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keycode); + + return 0; + } +diff --git a/keys.h b/keys.h +new file mode 100644 +index 00000000..047b76b0 +--- /dev/null ++++ b/keys.h +@@ -0,0 +1,514 @@ ++/* You can use the macros within this file ++ * instead of search the keycodes yourself ++ * with wev or something like that ++ * You probably are also searching these: ++ * Key_XF86AudioMute ++ * Key_XF86AudioLowerVolume ++ * Key_XF86AudioRaiseVolume ++ * Key_XF86MonBrightnessDown ++ * Key_XF86MonBrightnessUp ++*/ ++ ++#define Key_Escape 0x009 ++#define Key_1 0x00a ++#define Key_exclam 0x00a ++#define Key_2 0x00b ++#define Key_at 0x00b ++#define Key_3 0x00c ++#define Key_numbersign 0x00c ++#define Key_4 0x00d ++#define Key_dollar 0x00d ++#define Key_5 0x00e ++#define Key_percent 0x00e ++#define Key_6 0x00f ++#define Key_asciicircum 0x00f ++#define Key_7 0x010 ++#define Key_ampersand 0x010 ++#define Key_8 0x011 ++#define Key_asterisk 0x011 ++#define Key_9 0x012 ++#define Key_parenleft 0x012 ++#define Key_0 0x013 ++#define Key_parenright 0x013 ++#define Key_minus 0x014 ++#define Key_underscore 0x014 ++#define Key_equal 0x015 ++#define Key_plus 0x015 ++#define Key_BackSpace 0x016 ++#define Key_Tab 0x017 ++#define Key_ISO_Left_Tab 0x017 ++#define Key_q 0x018 ++#define Key_Q 0x018 ++#define Key_w 0x019 ++#define Key_W 0x019 ++#define Key_e 0x01a ++#define Key_E 0x01a ++#define Key_r 0x01b ++#define Key_R 0x01b ++#define Key_t 0x01c ++#define Key_T 0x01c ++#define Key_y 0x01d ++#define Key_Y 0x01d ++#define Key_u 0x01e ++#define Key_U 0x01e ++#define Key_i 0x01f ++#define Key_I 0x01f ++#define Key_o 0x020 ++#define Key_O 0x020 ++#define Key_p 0x021 ++#define Key_P 0x021 ++#define Key_bracketleft 0x022 ++#define Key_braceleft 0x022 ++#define Key_bracketright 0x023 ++#define Key_braceright 0x023 ++#define Key_Return 0x024 ++#define Key_Control_L 0x025 ++#define Key_a 0x026 ++#define Key_A 0x026 ++#define Key_s 0x027 ++#define Key_S 0x027 ++#define Key_d 0x028 ++#define Key_D 0x028 ++#define Key_f 0x029 ++#define Key_F 0x029 ++#define Key_g 0x02a ++#define Key_G 0x02a ++#define Key_h 0x02b ++#define Key_H 0x02b ++#define Key_j 0x02c ++#define Key_J 0x02c ++#define Key_k 0x02d ++#define Key_K 0x02d ++#define Key_l 0x02e ++#define Key_L 0x02e ++#define Key_semicolon 0x02f ++#define Key_colon 0x02f ++#define Key_apostrophe 0x030 ++#define Key_quotedbl 0x030 ++#define Key_grave 0x031 ++#define Key_asciitilde 0x031 ++#define Key_Shift_L 0x032 ++#define Key_backslash 0x033 ++#define Key_bar 0x033 ++#define Key_z 0x034 ++#define Key_Z 0x034 ++#define Key_x 0x035 ++#define Key_X 0x035 ++#define Key_c 0x036 ++#define Key_C 0x036 ++#define Key_v 0x037 ++#define Key_V 0x037 ++#define Key_b 0x038 ++#define Key_B 0x038 ++#define Key_n 0x039 ++#define Key_N 0x039 ++#define Key_m 0x03a ++#define Key_M 0x03a ++#define Key_comma 0x03b ++#define Key_less 0x03b ++#define Key_period 0x03c ++#define Key_greater 0x03c ++#define Key_slash 0x03d ++#define Key_question 0x03d ++#define Key_Shift_R 0x03e ++#define Key_KP_Multiply 0x03f ++#define Key_XF86ClearGrab 0x03f ++#define Key_Alt_L 0x040 ++#define Key_Meta_L 0x040 ++#define Key_space 0x041 ++#define Key_Caps_Lock 0x042 ++#define Key_F1 0x043 ++#define Key_XF86Switch_VT_1 0x043 ++#define Key_F2 0x044 ++#define Key_XF86Switch_VT_2 0x044 ++#define Key_F3 0x045 ++#define Key_XF86Switch_VT_3 0x045 ++#define Key_F4 0x046 ++#define Key_XF86Switch_VT_4 0x046 ++#define Key_F5 0x047 ++#define Key_XF86Switch_VT_5 0x047 ++#define Key_F6 0x048 ++#define Key_XF86Switch_VT_6 0x048 ++#define Key_F7 0x049 ++#define Key_XF86Switch_VT_7 0x049 ++#define Key_F8 0x04a ++#define Key_XF86Switch_VT_8 0x04a ++#define Key_F9 0x04b ++#define Key_XF86Switch_VT_9 0x04b ++#define Key_F10 0x04c ++#define Key_XF86Switch_VT_10 0x04c ++#define Key_Num_Lock 0x04d ++#define Key_Scroll_Lock 0x04e ++#define Key_KP_Home 0x04f ++#define Key_KP_7 0x04f ++#define Key_KP_Up 0x050 ++#define Key_KP_8 0x050 ++#define Key_KP_Prior 0x051 ++#define Key_KP_9 0x051 ++#define Key_KP_Subtract 0x052 ++#define Key_XF86Prev_VMode 0x052 ++#define Key_KP_Left 0x053 ++#define Key_KP_4 0x053 ++#define Key_KP_Begin 0x054 ++#define Key_KP_5 0x054 ++#define Key_KP_Right 0x055 ++#define Key_KP_6 0x055 ++#define Key_KP_Add 0x056 ++#define Key_XF86Next_VMode 0x056 ++#define Key_KP_End 0x057 ++#define Key_KP_1 0x057 ++#define Key_KP_Down 0x058 ++#define Key_KP_2 0x058 ++#define Key_KP_Next 0x059 ++#define Key_KP_3 0x059 ++#define Key_KP_Insert 0x05a ++#define Key_KP_0 0x05a ++#define Key_KP_Delete 0x05b ++#define Key_KP_Decimal 0x05b ++#define Key_ISO_Level3_Shift 0x05c ++#define Key_less2 0x05e ++#define Key_greater2 0x05e ++#define Key_bar2 0x05e ++#define Key_brokenbar 0x05e ++#define Key_F11 0x05f ++#define Key_XF86Switch_VT_11 0x05f ++#define Key_F12 0x060 ++#define Key_XF86Switch_VT_12 0x060 ++#define Key_Katakana 0x062 ++#define Key_Hiragana 0x063 ++#define Key_Henkan_Mode 0x064 ++#define Key_Hiragana_Katakana 0x065 ++#define Key_Muhenkan 0x066 ++#define Key_KP_Enter 0x068 ++#define Key_Control_R 0x069 ++#define Key_KP_Divide 0x06a ++#define Key_XF86Ungrab 0x06a ++#define Key_Print 0x06b ++#define Key_Sys_Req 0x06b ++#define Key_Alt_R 0x06c ++#define Key_Meta_R 0x06c ++#define Key_Linefeed 0x06d ++#define Key_Home 0x06e ++#define Key_Up 0x06f ++#define Key_Prior 0x070 ++#define Key_Left 0x071 ++#define Key_Right 0x072 ++#define Key_End 0x073 ++#define Key_Down 0x074 ++#define Key_Next 0x075 ++#define Key_Insert 0x076 ++#define Key_Delete 0x077 ++#define Key_XF86AudioMute 0x079 ++#define Key_XF86AudioLowerVolume 0x07a ++#define Key_XF86AudioRaiseVolume 0x07b ++#define Key_XF86PowerOff 0x07c ++#define Key_KP_Equal 0x07d ++#define Key_plusminus 0x07e ++#define Key_Pause 0x07f ++#define Key_Break 0x07f ++#define Key_XF86LaunchA 0x080 ++#define Key_KP_Decimal2 0x081 ++#define Key_Hangul 0x082 ++#define Key_Hangul_Hanja 0x083 ++#define Key_Super_L 0x085 ++#define Key_Super_R 0x086 ++#define Key_Menu 0x087 ++#define Key_Cancel 0x088 ++#define Key_Redo 0x089 ++#define Key_SunProps 0x08a ++#define Key_Undo 0x08b ++#define Key_SunFront 0x08c ++#define Key_XF86Copy 0x08d ++#define Key_XF86Open 0x08e ++#define Key_XF86Paste 0x08f ++#define Key_Find 0x090 ++#define Key_XF86Cut 0x091 ++#define Key_Help 0x092 ++#define Key_XF86MenuKB 0x093 ++#define Key_XF86Calculator 0x094 ++#define Key_XF86Sleep 0x096 ++#define Key_XF86WakeUp 0x097 ++#define Key_XF86Explorer 0x098 ++#define Key_XF86Send 0x099 ++#define Key_XF86Xfer 0x09b ++#define Key_XF86Launch1 0x09c ++#define Key_XF86Launch2 0x09d ++#define Key_XF86WWW 0x09e ++#define Key_XF86DOS 0x09f ++#define Key_XF86ScreenSaver 0x0a0 ++#define Key_XF86RotateWindows 0x0a1 ++#define Key_XF86TaskPane 0x0a2 ++#define Key_XF86Mail 0x0a3 ++#define Key_XF86Favorites 0x0a4 ++#define Key_XF86MyComputer 0x0a5 ++#define Key_XF86Back 0x0a6 ++#define Key_XF86Forward 0x0a7 ++#define Key_XF86Eject 0x0a9 ++#define Key_XF86Eject2 0x0aa ++#define Key_XF86AudioNext 0x0ab ++#define Key_XF86AudioPlay 0x0ac ++#define Key_XF86AudioPause 0x0ac ++#define Key_XF86AudioPrev 0x0ad ++#define Key_XF86AudioStop 0x0ae ++#define Key_XF86Eject3 0x0ae ++#define Key_XF86AudioRecord 0x0af ++#define Key_XF86AudioRewind 0x0b0 ++#define Key_XF86Phone 0x0b1 ++#define Key_XF86Tools 0x0b3 ++#define Key_XF86HomePage 0x0b4 ++#define Key_XF86Reload 0x0b5 ++#define Key_XF86Close 0x0b6 ++#define Key_XF86ScrollUp 0x0b9 ++#define Key_XF86ScrollDown 0x0ba ++#define Key_parenleft2 0x0bb ++#define Key_parenright2 0x0bc ++#define Key_XF86New 0x0bd ++#define Key_Redo2 0x0be ++#define Key_XF86Tools2 0x0bf ++#define Key_XF86Launch5 0x0c0 ++#define Key_XF86Launch6 0x0c1 ++#define Key_XF86Launch7 0x0c2 ++#define Key_XF86Launch8 0x0c3 ++#define Key_XF86Launch9 0x0c4 ++#define Key_XF86AudioMicMute 0x0c6 ++#define Key_XF86TouchpadToggle 0x0c7 ++#define Key_XF86TouchpadOn 0x0c8 ++#define Key_XF86TouchpadOff 0x0c9 ++#define Key_ISO_Level5_Shift 0x0cb ++#define Key_Alt_L2 0x0cc ++#define Key_Meta_L2 0x0cd ++#define Key_Super_L2 0x0ce ++#define Key_Hyper_L 0x0cf ++#define Key_XF86AudioPlay2 0x0d0 ++#define Key_XF86AudioPause2 0x0d1 ++#define Key_XF86Launch3 0x0d2 ++#define Key_XF86Launch4 0x0d3 ++#define Key_XF86LaunchB 0x0d4 ++#define Key_XF86Suspend 0x0d5 ++#define Key_XF86Close2 0x0d6 ++#define Key_XF86AudioPlay3 0x0d7 ++#define Key_XF86AudioForward 0x0d8 ++#define Key_Print2 0x0da ++#define Key_XF86WebCam 0x0dc ++#define Key_XF86AudioPreset 0x0dd ++#define Key_XF86Mail2 0x0df ++#define Key_XF86Messenger 0x0e0 ++#define Key_XF86Search 0x0e1 ++#define Key_XF86Go 0x0e2 ++#define Key_XF86Finance 0x0e3 ++#define Key_XF86Game 0x0e4 ++#define Key_XF86Shop 0x0e5 ++#define Key_Cancel2 0x0e7 ++#define Key_XF86MonBrightnessDown 0x0e8 ++#define Key_XF86MonBrightnessUp 0x0e9 ++#define Key_XF86AudioMedia 0x0ea ++#define Key_XF86Display 0x0eb ++#define Key_XF86KbdLightOnOff 0x0ec ++#define Key_XF86KbdBrightnessDown 0x0ed ++#define Key_XF86KbdBrightnessUp 0x0ee ++#define Key_XF86Send2 0x0ef ++#define Key_XF86Reply 0x0f0 ++#define Key_XF86MailForward 0x0f1 ++#define Key_XF86Save 0x0f2 ++#define Key_XF86Documents 0x0f3 ++#define Key_XF86Battery 0x0f4 ++#define Key_XF86Bluetooth 0x0f5 ++#define Key_XF86WLAN 0x0f6 ++#define Key_XF86UWB 0x0f7 ++#define Key_XF86Next_VMode2 0x0f9 ++#define Key_XF86Prev_VMode2 0x0fa ++#define Key_XF86MonBrightnessCycle 0x0fb ++#define Key_XF86BrightnessAuto 0x0fc ++#define Key_XF86DisplayOff 0x0fd ++#define Key_XF86WWAN 0x0fe ++#define Key_XF86RFKill 0x0ff ++#define Key_XF86AudioMicMute2 0x100 ++#define Key_XF86Info 0x16e ++#define Key_XF86Favorites2 0x174 ++#define Key_XF86CycleAngle 0x17b ++#define Key_XF86FullScreen 0x17c ++#define Key_XF86Keyboard 0x17e ++#define Key_XF86AspectRatio 0x17f ++#define Key_XF86DVD 0x18d ++#define Key_XF86Audio 0x190 ++#define Key_XF86Video 0x191 ++#define Key_XF86Calendar 0x195 ++#define Key_XF86ChannelUp 0x19a ++#define Key_XF86ChannelDown 0x19b ++#define Key_XF86AudioRandomPlay 0x1a2 ++#define Key_XF86Break 0x1a3 ++#define Key_XF86VideoPhone 0x1a8 ++#define Key_XF86Game2 0x1a9 ++#define Key_XF86ZoomIn 0x1aa ++#define Key_XF86ZoomOut 0x1ab ++#define Key_XF86ZoomReset 0x1ac ++#define Key_XF86Word 0x1ad ++#define Key_XF86Editor 0x1ae ++#define Key_XF86Excel 0x1af ++#define Key_XF86GraphicsEditor 0x1b0 ++#define Key_XF86Presentation 0x1b1 ++#define Key_XF86Database 0x1b2 ++#define Key_XF86News 0x1b3 ++#define Key_XF86Voicemail 0x1b4 ++#define Key_XF86Addressbook 0x1b5 ++#define Key_XF86Messenger2 0x1b6 ++#define Key_XF86DisplayToggle 0x1b7 ++#define Key_XF86SpellCheck 0x1b8 ++#define Key_XF86LogOff 0x1b9 ++#define Key_dollar2 0x1ba ++#define Key_EuroSign 0x1bb ++#define Key_XF86FrameBack 0x1bc ++#define Key_XF86FrameForward 0x1bd ++#define Key_XF86ContextMenu 0x1be ++#define Key_XF86MediaRepeat 0x1bf ++#define Key_XF8610ChannelsUp 0x1c0 ++#define Key_XF8610ChannelsDown 0x1c1 ++#define Key_XF86Images 0x1c2 ++#define Key_XF86NotificationCenter 0x1c4 ++#define Key_XF86PickupPhone 0x1c5 ++#define Key_XF86HangupPhone 0x1c6 ++#define Key_XF86Fn 0x1d8 ++#define Key_XF86Fn_Esc 0x1d9 ++#define Key_XF86FnRightShift 0x1ed ++#define Key_braille_dot_1 0x1f9 ++#define Key_braille_dot_2 0x1fa ++#define Key_braille_dot_3 0x1fb ++#define Key_braille_dot_4 0x1fc ++#define Key_braille_dot_5 0x1fd ++#define Key_braille_dot_6 0x1fe ++#define Key_braille_dot_7 0x1ff ++#define Key_braille_dot_8 0x200 ++#define Key_braille_dot_9 0x201 ++#define Key_braille_dot_1_2 0x202 ++#define Key_XF86Numeric0 0x208 ++#define Key_XF86Numeric1 0x209 ++#define Key_XF86Numeric2 0x20a ++#define Key_XF86Numeric3 0x20b ++#define Key_XF86Numeric4 0x20c ++#define Key_XF86Numeric5 0x20d ++#define Key_XF86Numeric6 0x20e ++#define Key_XF86Numeric7 0x20f ++#define Key_XF86Numeric8 0x210 ++#define Key_XF86Numeric9 0x211 ++#define Key_XF86NumericStar 0x212 ++#define Key_XF86NumericPound 0x213 ++#define Key_XF86NumericA 0x214 ++#define Key_XF86NumericB 0x215 ++#define Key_XF86NumericC 0x216 ++#define Key_XF86NumericD 0x217 ++#define Key_XF86CameraFocus 0x218 ++#define Key_XF86WPSButton 0x219 ++#define Key_XF86TouchpadToggle2 0x21a ++#define Key_XF86TouchpadOn2 0x21b ++#define Key_XF86TouchpadOff2 0x21c ++#define Key_XF86CameraZoomIn 0x21d ++#define Key_XF86CameraZoomOut 0x21e ++#define Key_XF86CameraUp 0x21f ++#define Key_XF86CameraDown 0x220 ++#define Key_XF86CameraLeft 0x221 ++#define Key_XF86CameraRight 0x222 ++#define Key_XF86AttendantOn 0x223 ++#define Key_XF86AttendantOff 0x224 ++#define Key_XF86AttendantToggle 0x225 ++#define Key_XF86LightsToggle 0x226 ++#define Key_XF86ALSToggle 0x238 ++#define Key_XF86RotationLockToggle 0x239 ++#define Key_XF86Buttonconfig 0x248 ++#define Key_XF86Taskmanager 0x249 ++#define Key_XF86Journal 0x24a ++#define Key_XF86ControlPanel 0x24b ++#define Key_XF86AppSelect 0x24c ++#define Key_XF86Screensaver 0x24d ++#define Key_XF86VoiceCommand 0x24e ++#define Key_XF86Assistant 0x24f ++#define Key_ISO_Next_Group 0x250 ++#define Key_XF86EmojiPicker 0x251 ++#define Key_XF86Dictate 0x252 ++#define Key_XF86CameraAccessEnable 0x253 ++#define Key_XF86CameraAccessDisable 0x254 ++#define Key_XF86CameraAccessToggle 0x255 ++#define Key_XF86BrightnessMin 0x258 ++#define Key_XF86BrightnessMax 0x259 ++#define Key_XF86KbdInputAssistPrev 0x268 ++#define Key_XF86KbdInputAssistNext 0x269 ++#define Key_XF86KbdInputAssistPrevgroup 0x26a ++#define Key_XF86KbdInputAssistNextgroup 0x26b ++#define Key_XF86KbdInputAssistAccept 0x26c ++#define Key_XF86KbdInputAssistCancel 0x26d ++#define Key_XF86RightUp 0x26e ++#define Key_XF86RightDown 0x26f ++#define Key_XF86LeftUp 0x270 ++#define Key_XF86LeftDown 0x271 ++#define Key_XF86RootMenu 0x272 ++#define Key_XF86MediaTopMenu 0x273 ++#define Key_XF86Numeric11 0x274 ++#define Key_XF86Numeric12 0x275 ++#define Key_XF86AudioDesc 0x276 ++#define Key_XF863DMode 0x277 ++#define Key_XF86NextFavorite 0x278 ++#define Key_XF86StopRecord 0x279 ++#define Key_XF86PauseRecord 0x27a ++#define Key_XF86VOD 0x27b ++#define Key_XF86Unmute 0x27c ++#define Key_XF86FastReverse 0x27d ++#define Key_XF86SlowReverse 0x27e ++#define Key_XF86Data 0x27f ++#define Key_XF86OnScreenKeyboard 0x280 ++#define Key_XF86PrivacyScreenToggle 0x281 ++#define Key_XF86SelectiveScreenshot 0x282 ++#define Key_XF86NextElement 0x283 ++#define Key_XF86PreviousElement 0x284 ++#define Key_XF86AutopilotEngageToggle 0x285 ++#define Key_XF86MarkWaypoint 0x286 ++#define Key_XF86Sos 0x287 ++#define Key_XF86NavChart 0x288 ++#define Key_XF86FishingChart 0x289 ++#define Key_XF86SingleRangeRadar 0x28a ++#define Key_XF86DualRangeRadar 0x28b ++#define Key_XF86RadarOverlay 0x28c ++#define Key_XF86TraditionalSonar 0x28d ++#define Key_XF86ClearvuSonar 0x28e ++#define Key_XF86SidevuSonar 0x28f ++#define Key_XF86NavInfo 0x290 ++#define Key_XF86BrightnessAdjust 0x291 ++#define Key_XF86Macro1 0x298 ++#define Key_XF86Macro2 0x299 ++#define Key_XF86Macro3 0x29a ++#define Key_XF86Macro4 0x29b ++#define Key_XF86Macro5 0x29c ++#define Key_XF86Macro6 0x29d ++#define Key_XF86Macro7 0x29e ++#define Key_XF86Macro8 0x29f ++#define Key_XF86Macro9 0x2a0 ++#define Key_XF86Macro10 0x2a1 ++#define Key_XF86Macro11 0x2a2 ++#define Key_XF86Macro12 0x2a3 ++#define Key_XF86Macro13 0x2a4 ++#define Key_XF86Macro14 0x2a5 ++#define Key_XF86Macro15 0x2a6 ++#define Key_XF86Macro16 0x2a7 ++#define Key_XF86Macro17 0x2a8 ++#define Key_XF86Macro18 0x2a9 ++#define Key_XF86Macro19 0x2aa ++#define Key_XF86Macro20 0x2ab ++#define Key_XF86Macro21 0x2ac ++#define Key_XF86Macro22 0x2ad ++#define Key_XF86Macro23 0x2ae ++#define Key_XF86Macro24 0x2af ++#define Key_XF86Macro25 0x2b0 ++#define Key_XF86Macro26 0x2b1 ++#define Key_XF86Macro27 0x2b2 ++#define Key_XF86Macro28 0x2b3 ++#define Key_XF86Macro29 0x2b4 ++#define Key_XF86Macro30 0x2b5 ++#define Key_XF86MacroRecordStart 0x2b8 ++#define Key_XF86MacroRecordStop 0x2b9 ++#define Key_XF86MacroPresetCycle 0x2ba ++#define Key_XF86MacroPreset1 0x2bb ++#define Key_XF86MacroPreset2 0x2bc ++#define Key_XF86MacroPreset3 0x2bd ++#define Key_XF86KbdLcdMenu1 0x2c0 ++#define Key_XF86KbdLcdMenu2 0x2c1 ++#define Key_XF86KbdLcdMenu3 0x2c2 ++#define Key_XF86KbdLcdMenu4 0x2c3 ++#define Key_XF86KbdLcdMenu5 0x2c4 +-- +2.46.0 + + +From cc7d880886d91cd8d39ab31a48d03d1f745598d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 19 Mar 2023 15:50:07 -0600 +Subject: [PATCH 2/2] add program to generate keys.h +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + generate-keys.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) + create mode 100644 generate-keys.c + +diff --git a/generate-keys.c b/generate-keys.c +new file mode 100644 +index 00000000..37655611 +--- /dev/null ++++ b/generate-keys.c +@@ -0,0 +1,95 @@ ++/****************************************************************** ++ * Copyright 2023-2024 Leonardo Hernández Hernández ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the “Software”), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ++ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ ******************************************************************/ ++ ++/* cc -lxkbcommon -o generate-keys generate-keys.c */ ++ ++#define _DEFAULT_SOURCE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int ++main(void) ++{ ++ /* Allow generate keys with a different layout and variant. ++ * You can also use XKB_DEFAULT_* environmental variables and let this as is */ ++ struct xkb_rule_names rules = { ++ 0 ++ }; ++ struct xkb_context *context = NULL; ++ struct xkb_keymap *keymap = NULL; ++ xkb_keycode_t keycode, min_keycode, max_keycode; ++ xkb_layout_index_t layout, num_layouts; ++ xkb_level_index_t level, num_levels; ++ int i, nsyms; ++ const xkb_keysym_t *syms; ++ char keyname[64]; ++ bool ok = false; ++ FILE *file = fopen("keys.h", "w"); ++ if (!file) { ++ perror("Couldn't open keys.h"); ++ return EXIT_FAILURE; ++ } ++ ++ if (!(context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon context\n", stderr); ++ goto out; ++ } ++ ++ if (!(keymap = xkb_keymap_new_from_names(context, &rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon keymap\n", stderr); ++ goto out; ++ } ++ ++ min_keycode = xkb_keymap_min_keycode(keymap); ++ max_keycode = xkb_keymap_max_keycode(keymap); ++ ++ for (keycode = min_keycode; keycode <= max_keycode; keycode++) { ++ num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode); ++ for (layout = 0; layout < num_layouts; layout++) { ++ num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout); ++ for (level = 0; level < num_levels; level++) { ++ nsyms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms); ++ for (i = 0; i < nsyms; i++) { ++ xkb_keysym_get_name(syms[i], keyname, sizeof(keyname) / sizeof(keyname[0])); ++ fprintf(file, "#define Key_%-27s %#05"PRIx32"\n", keyname, keycode); ++ } ++ } ++ } ++ } ++ ++ ok = true; ++ sync(); ++ ++out: ++ fclose(file); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ return !ok; ++} +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/keycodes/keycodes.patch b/dwl/dwl-patches/patches/keycodes/keycodes.patch new file mode 100644 index 0000000..cc7e896 --- /dev/null +++ b/dwl/dwl-patches/patches/keycodes/keycodes.patch @@ -0,0 +1,883 @@ +From 308eda668e1d6496a605e44be34fd44c841a1133 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Fri, 4 Jun 2021 16:51:01 -0500 +Subject: [PATCH 1/2] allow use keycodes instead keysyms +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 85 ++++----- + dwl.c | 35 ++-- + keys.h | 514 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 569 insertions(+), 65 deletions(-) + create mode 100644 keys.h + +diff --git a/config.def.h b/config.def.h +index 22d2171d..87a6e60f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -109,11 +109,11 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +-#define TAGKEYS(KEY,SKEY,TAG) \ ++#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY|WLR_MODIFIER_SHIFT, KEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,KEY,toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +@@ -122,51 +122,52 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++#include "keys.h" + static const Key keys[] = { +- /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +- /* modifier key function argument */ +- { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, +- { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, +- { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, +- { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +- { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, +- { MODKEY, XKB_KEY_Return, zoom, {0} }, +- { MODKEY, XKB_KEY_Tab, view, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +- { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, +- { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +- { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +- { MODKEY, XKB_KEY_space, setlayout, {0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, +- { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +- { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, +- { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, +- TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), +- TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), +- TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +- TAGKEYS( XKB_KEY_4, XKB_KEY_dollar, 3), +- TAGKEYS( XKB_KEY_5, XKB_KEY_percent, 4), +- TAGKEYS( XKB_KEY_6, XKB_KEY_asciicircum, 5), +- TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), +- TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), +- TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), +- { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, ++ /* modifier key function argument */ ++ { MODKEY, Key_p, spawn, {.v = menucmd} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_Return, spawn, {.v = termcmd} }, ++ { MODKEY, Key_j, focusstack, {.i = +1} }, ++ { MODKEY, Key_k, focusstack, {.i = -1} }, ++ { MODKEY, Key_i, incnmaster, {.i = +1} }, ++ { MODKEY, Key_d, incnmaster, {.i = -1} }, ++ { MODKEY, Key_h, setmfact, {.f = -0.05f} }, ++ { MODKEY, Key_l, setmfact, {.f = +0.05f} }, ++ { MODKEY, Key_Return, zoom, {0} }, ++ { MODKEY, Key_Tab, view, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_c, killclient, {0} }, ++ { MODKEY, Key_t, setlayout, {.v = &layouts[0]} }, ++ { MODKEY, Key_f, setlayout, {.v = &layouts[1]} }, ++ { MODKEY, Key_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, Key_space, setlayout, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_space, togglefloating, {0} }, ++ { MODKEY, Key_e, togglefullscreen, {0} }, ++ { MODKEY, Key_0, view, {.ui = ~0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_0, tag, {.ui = ~0} }, ++ { MODKEY, Key_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY, Key_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_period, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ TAGKEYS( Key_1, 0), ++ TAGKEYS( Key_2, 1), ++ TAGKEYS( Key_3, 2), ++ TAGKEYS( Key_4, 3), ++ TAGKEYS( Key_5, 4), ++ TAGKEYS( Key_6, 5), ++ TAGKEYS( Key_7, 6), ++ TAGKEYS( Key_8, 7), ++ TAGKEYS( Key_9, 8), ++ { MODKEY|WLR_MODIFIER_SHIFT, Key_q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +- { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,Key_BackSpace, quit, {0} }, ++#define CHVT(KEY,n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT, KEY, chvt, {.ui = (n)} } + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is + * do not remove them. + */ +-#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } +- CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), +- CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++ CHVT(Key_F1, 1), CHVT(Key_F2, 2), CHVT(Key_F3, 3), CHVT(Key_F4, 4), ++ CHVT(Key_F5, 5), CHVT(Key_F6, 6), CHVT(Key_F7, 7), CHVT(Key_F8, 8), ++ CHVT(Key_F9, 9), CHVT(Key_F10, 10), CHVT(Key_F11, 11), CHVT(Key_F12, 12), + }; + + static const Button buttons[] = { +diff --git a/dwl.c b/dwl.c +index ad21e1ba..1c9e0ae5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -143,7 +143,7 @@ typedef struct { + + typedef struct { + uint32_t mod; +- xkb_keysym_t keysym; ++ xkb_keycode_t keycode; + void (*func)(const Arg *); + const Arg arg; + } Key; +@@ -151,9 +151,8 @@ typedef struct { + typedef struct { + struct wlr_keyboard_group *wlr_group; + +- int nsyms; +- const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ +- uint32_t mods; /* invalid if nsyms == 0 */ ++ xkb_keycode_t keycode; ++ uint32_t mods; /* invalid if keycode == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; +@@ -291,7 +290,7 @@ static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keycode_t keycode); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1597,7 +1596,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keycode_t keycode) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1607,7 +1606,7 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) +- && sym == k->keysym && k->func) { ++ && keycode == k->keycode && k->func) { + k->func(&k->arg); + return 1; + } +@@ -1618,17 +1617,12 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + void + keypress(struct wl_listener *listener, void *data) + { +- int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; +- /* Get a list of keysyms based on the keymap for this keyboard */ +- const xkb_keysym_t *syms; +- int nsyms = xkb_state_key_get_syms( +- group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); +@@ -1637,19 +1631,16 @@ keypress(struct wl_listener *listener, void *data) + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ +- if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { +- for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; +- } ++ if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ handled = keybinding(mods, keycode); + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; +- group->keysyms = syms; +- group->nsyms = nsyms; ++ group->keycode = keycode; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { +- group->nsyms = 0; ++ group->keycode = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + +@@ -1679,15 +1670,13 @@ int + keyrepeat(void *data) + { + KeyboardGroup *group = data; +- int i; +- if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) ++ if (!group->keycode || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + +- for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keycode); + + return 0; + } +diff --git a/keys.h b/keys.h +new file mode 100644 +index 00000000..047b76b0 +--- /dev/null ++++ b/keys.h +@@ -0,0 +1,514 @@ ++/* You can use the macros within this file ++ * instead of search the keycodes yourself ++ * with wev or something like that ++ * You probably are also searching these: ++ * Key_XF86AudioMute ++ * Key_XF86AudioLowerVolume ++ * Key_XF86AudioRaiseVolume ++ * Key_XF86MonBrightnessDown ++ * Key_XF86MonBrightnessUp ++*/ ++ ++#define Key_Escape 0x009 ++#define Key_1 0x00a ++#define Key_exclam 0x00a ++#define Key_2 0x00b ++#define Key_at 0x00b ++#define Key_3 0x00c ++#define Key_numbersign 0x00c ++#define Key_4 0x00d ++#define Key_dollar 0x00d ++#define Key_5 0x00e ++#define Key_percent 0x00e ++#define Key_6 0x00f ++#define Key_asciicircum 0x00f ++#define Key_7 0x010 ++#define Key_ampersand 0x010 ++#define Key_8 0x011 ++#define Key_asterisk 0x011 ++#define Key_9 0x012 ++#define Key_parenleft 0x012 ++#define Key_0 0x013 ++#define Key_parenright 0x013 ++#define Key_minus 0x014 ++#define Key_underscore 0x014 ++#define Key_equal 0x015 ++#define Key_plus 0x015 ++#define Key_BackSpace 0x016 ++#define Key_Tab 0x017 ++#define Key_ISO_Left_Tab 0x017 ++#define Key_q 0x018 ++#define Key_Q 0x018 ++#define Key_w 0x019 ++#define Key_W 0x019 ++#define Key_e 0x01a ++#define Key_E 0x01a ++#define Key_r 0x01b ++#define Key_R 0x01b ++#define Key_t 0x01c ++#define Key_T 0x01c ++#define Key_y 0x01d ++#define Key_Y 0x01d ++#define Key_u 0x01e ++#define Key_U 0x01e ++#define Key_i 0x01f ++#define Key_I 0x01f ++#define Key_o 0x020 ++#define Key_O 0x020 ++#define Key_p 0x021 ++#define Key_P 0x021 ++#define Key_bracketleft 0x022 ++#define Key_braceleft 0x022 ++#define Key_bracketright 0x023 ++#define Key_braceright 0x023 ++#define Key_Return 0x024 ++#define Key_Control_L 0x025 ++#define Key_a 0x026 ++#define Key_A 0x026 ++#define Key_s 0x027 ++#define Key_S 0x027 ++#define Key_d 0x028 ++#define Key_D 0x028 ++#define Key_f 0x029 ++#define Key_F 0x029 ++#define Key_g 0x02a ++#define Key_G 0x02a ++#define Key_h 0x02b ++#define Key_H 0x02b ++#define Key_j 0x02c ++#define Key_J 0x02c ++#define Key_k 0x02d ++#define Key_K 0x02d ++#define Key_l 0x02e ++#define Key_L 0x02e ++#define Key_semicolon 0x02f ++#define Key_colon 0x02f ++#define Key_apostrophe 0x030 ++#define Key_quotedbl 0x030 ++#define Key_grave 0x031 ++#define Key_asciitilde 0x031 ++#define Key_Shift_L 0x032 ++#define Key_backslash 0x033 ++#define Key_bar 0x033 ++#define Key_z 0x034 ++#define Key_Z 0x034 ++#define Key_x 0x035 ++#define Key_X 0x035 ++#define Key_c 0x036 ++#define Key_C 0x036 ++#define Key_v 0x037 ++#define Key_V 0x037 ++#define Key_b 0x038 ++#define Key_B 0x038 ++#define Key_n 0x039 ++#define Key_N 0x039 ++#define Key_m 0x03a ++#define Key_M 0x03a ++#define Key_comma 0x03b ++#define Key_less 0x03b ++#define Key_period 0x03c ++#define Key_greater 0x03c ++#define Key_slash 0x03d ++#define Key_question 0x03d ++#define Key_Shift_R 0x03e ++#define Key_KP_Multiply 0x03f ++#define Key_XF86ClearGrab 0x03f ++#define Key_Alt_L 0x040 ++#define Key_Meta_L 0x040 ++#define Key_space 0x041 ++#define Key_Caps_Lock 0x042 ++#define Key_F1 0x043 ++#define Key_XF86Switch_VT_1 0x043 ++#define Key_F2 0x044 ++#define Key_XF86Switch_VT_2 0x044 ++#define Key_F3 0x045 ++#define Key_XF86Switch_VT_3 0x045 ++#define Key_F4 0x046 ++#define Key_XF86Switch_VT_4 0x046 ++#define Key_F5 0x047 ++#define Key_XF86Switch_VT_5 0x047 ++#define Key_F6 0x048 ++#define Key_XF86Switch_VT_6 0x048 ++#define Key_F7 0x049 ++#define Key_XF86Switch_VT_7 0x049 ++#define Key_F8 0x04a ++#define Key_XF86Switch_VT_8 0x04a ++#define Key_F9 0x04b ++#define Key_XF86Switch_VT_9 0x04b ++#define Key_F10 0x04c ++#define Key_XF86Switch_VT_10 0x04c ++#define Key_Num_Lock 0x04d ++#define Key_Scroll_Lock 0x04e ++#define Key_KP_Home 0x04f ++#define Key_KP_7 0x04f ++#define Key_KP_Up 0x050 ++#define Key_KP_8 0x050 ++#define Key_KP_Prior 0x051 ++#define Key_KP_9 0x051 ++#define Key_KP_Subtract 0x052 ++#define Key_XF86Prev_VMode 0x052 ++#define Key_KP_Left 0x053 ++#define Key_KP_4 0x053 ++#define Key_KP_Begin 0x054 ++#define Key_KP_5 0x054 ++#define Key_KP_Right 0x055 ++#define Key_KP_6 0x055 ++#define Key_KP_Add 0x056 ++#define Key_XF86Next_VMode 0x056 ++#define Key_KP_End 0x057 ++#define Key_KP_1 0x057 ++#define Key_KP_Down 0x058 ++#define Key_KP_2 0x058 ++#define Key_KP_Next 0x059 ++#define Key_KP_3 0x059 ++#define Key_KP_Insert 0x05a ++#define Key_KP_0 0x05a ++#define Key_KP_Delete 0x05b ++#define Key_KP_Decimal 0x05b ++#define Key_ISO_Level3_Shift 0x05c ++#define Key_less2 0x05e ++#define Key_greater2 0x05e ++#define Key_bar2 0x05e ++#define Key_brokenbar 0x05e ++#define Key_F11 0x05f ++#define Key_XF86Switch_VT_11 0x05f ++#define Key_F12 0x060 ++#define Key_XF86Switch_VT_12 0x060 ++#define Key_Katakana 0x062 ++#define Key_Hiragana 0x063 ++#define Key_Henkan_Mode 0x064 ++#define Key_Hiragana_Katakana 0x065 ++#define Key_Muhenkan 0x066 ++#define Key_KP_Enter 0x068 ++#define Key_Control_R 0x069 ++#define Key_KP_Divide 0x06a ++#define Key_XF86Ungrab 0x06a ++#define Key_Print 0x06b ++#define Key_Sys_Req 0x06b ++#define Key_Alt_R 0x06c ++#define Key_Meta_R 0x06c ++#define Key_Linefeed 0x06d ++#define Key_Home 0x06e ++#define Key_Up 0x06f ++#define Key_Prior 0x070 ++#define Key_Left 0x071 ++#define Key_Right 0x072 ++#define Key_End 0x073 ++#define Key_Down 0x074 ++#define Key_Next 0x075 ++#define Key_Insert 0x076 ++#define Key_Delete 0x077 ++#define Key_XF86AudioMute 0x079 ++#define Key_XF86AudioLowerVolume 0x07a ++#define Key_XF86AudioRaiseVolume 0x07b ++#define Key_XF86PowerOff 0x07c ++#define Key_KP_Equal 0x07d ++#define Key_plusminus 0x07e ++#define Key_Pause 0x07f ++#define Key_Break 0x07f ++#define Key_XF86LaunchA 0x080 ++#define Key_KP_Decimal2 0x081 ++#define Key_Hangul 0x082 ++#define Key_Hangul_Hanja 0x083 ++#define Key_Super_L 0x085 ++#define Key_Super_R 0x086 ++#define Key_Menu 0x087 ++#define Key_Cancel 0x088 ++#define Key_Redo 0x089 ++#define Key_SunProps 0x08a ++#define Key_Undo 0x08b ++#define Key_SunFront 0x08c ++#define Key_XF86Copy 0x08d ++#define Key_XF86Open 0x08e ++#define Key_XF86Paste 0x08f ++#define Key_Find 0x090 ++#define Key_XF86Cut 0x091 ++#define Key_Help 0x092 ++#define Key_XF86MenuKB 0x093 ++#define Key_XF86Calculator 0x094 ++#define Key_XF86Sleep 0x096 ++#define Key_XF86WakeUp 0x097 ++#define Key_XF86Explorer 0x098 ++#define Key_XF86Send 0x099 ++#define Key_XF86Xfer 0x09b ++#define Key_XF86Launch1 0x09c ++#define Key_XF86Launch2 0x09d ++#define Key_XF86WWW 0x09e ++#define Key_XF86DOS 0x09f ++#define Key_XF86ScreenSaver 0x0a0 ++#define Key_XF86RotateWindows 0x0a1 ++#define Key_XF86TaskPane 0x0a2 ++#define Key_XF86Mail 0x0a3 ++#define Key_XF86Favorites 0x0a4 ++#define Key_XF86MyComputer 0x0a5 ++#define Key_XF86Back 0x0a6 ++#define Key_XF86Forward 0x0a7 ++#define Key_XF86Eject 0x0a9 ++#define Key_XF86Eject2 0x0aa ++#define Key_XF86AudioNext 0x0ab ++#define Key_XF86AudioPlay 0x0ac ++#define Key_XF86AudioPause 0x0ac ++#define Key_XF86AudioPrev 0x0ad ++#define Key_XF86AudioStop 0x0ae ++#define Key_XF86Eject3 0x0ae ++#define Key_XF86AudioRecord 0x0af ++#define Key_XF86AudioRewind 0x0b0 ++#define Key_XF86Phone 0x0b1 ++#define Key_XF86Tools 0x0b3 ++#define Key_XF86HomePage 0x0b4 ++#define Key_XF86Reload 0x0b5 ++#define Key_XF86Close 0x0b6 ++#define Key_XF86ScrollUp 0x0b9 ++#define Key_XF86ScrollDown 0x0ba ++#define Key_parenleft2 0x0bb ++#define Key_parenright2 0x0bc ++#define Key_XF86New 0x0bd ++#define Key_Redo2 0x0be ++#define Key_XF86Tools2 0x0bf ++#define Key_XF86Launch5 0x0c0 ++#define Key_XF86Launch6 0x0c1 ++#define Key_XF86Launch7 0x0c2 ++#define Key_XF86Launch8 0x0c3 ++#define Key_XF86Launch9 0x0c4 ++#define Key_XF86AudioMicMute 0x0c6 ++#define Key_XF86TouchpadToggle 0x0c7 ++#define Key_XF86TouchpadOn 0x0c8 ++#define Key_XF86TouchpadOff 0x0c9 ++#define Key_ISO_Level5_Shift 0x0cb ++#define Key_Alt_L2 0x0cc ++#define Key_Meta_L2 0x0cd ++#define Key_Super_L2 0x0ce ++#define Key_Hyper_L 0x0cf ++#define Key_XF86AudioPlay2 0x0d0 ++#define Key_XF86AudioPause2 0x0d1 ++#define Key_XF86Launch3 0x0d2 ++#define Key_XF86Launch4 0x0d3 ++#define Key_XF86LaunchB 0x0d4 ++#define Key_XF86Suspend 0x0d5 ++#define Key_XF86Close2 0x0d6 ++#define Key_XF86AudioPlay3 0x0d7 ++#define Key_XF86AudioForward 0x0d8 ++#define Key_Print2 0x0da ++#define Key_XF86WebCam 0x0dc ++#define Key_XF86AudioPreset 0x0dd ++#define Key_XF86Mail2 0x0df ++#define Key_XF86Messenger 0x0e0 ++#define Key_XF86Search 0x0e1 ++#define Key_XF86Go 0x0e2 ++#define Key_XF86Finance 0x0e3 ++#define Key_XF86Game 0x0e4 ++#define Key_XF86Shop 0x0e5 ++#define Key_Cancel2 0x0e7 ++#define Key_XF86MonBrightnessDown 0x0e8 ++#define Key_XF86MonBrightnessUp 0x0e9 ++#define Key_XF86AudioMedia 0x0ea ++#define Key_XF86Display 0x0eb ++#define Key_XF86KbdLightOnOff 0x0ec ++#define Key_XF86KbdBrightnessDown 0x0ed ++#define Key_XF86KbdBrightnessUp 0x0ee ++#define Key_XF86Send2 0x0ef ++#define Key_XF86Reply 0x0f0 ++#define Key_XF86MailForward 0x0f1 ++#define Key_XF86Save 0x0f2 ++#define Key_XF86Documents 0x0f3 ++#define Key_XF86Battery 0x0f4 ++#define Key_XF86Bluetooth 0x0f5 ++#define Key_XF86WLAN 0x0f6 ++#define Key_XF86UWB 0x0f7 ++#define Key_XF86Next_VMode2 0x0f9 ++#define Key_XF86Prev_VMode2 0x0fa ++#define Key_XF86MonBrightnessCycle 0x0fb ++#define Key_XF86BrightnessAuto 0x0fc ++#define Key_XF86DisplayOff 0x0fd ++#define Key_XF86WWAN 0x0fe ++#define Key_XF86RFKill 0x0ff ++#define Key_XF86AudioMicMute2 0x100 ++#define Key_XF86Info 0x16e ++#define Key_XF86Favorites2 0x174 ++#define Key_XF86CycleAngle 0x17b ++#define Key_XF86FullScreen 0x17c ++#define Key_XF86Keyboard 0x17e ++#define Key_XF86AspectRatio 0x17f ++#define Key_XF86DVD 0x18d ++#define Key_XF86Audio 0x190 ++#define Key_XF86Video 0x191 ++#define Key_XF86Calendar 0x195 ++#define Key_XF86ChannelUp 0x19a ++#define Key_XF86ChannelDown 0x19b ++#define Key_XF86AudioRandomPlay 0x1a2 ++#define Key_XF86Break 0x1a3 ++#define Key_XF86VideoPhone 0x1a8 ++#define Key_XF86Game2 0x1a9 ++#define Key_XF86ZoomIn 0x1aa ++#define Key_XF86ZoomOut 0x1ab ++#define Key_XF86ZoomReset 0x1ac ++#define Key_XF86Word 0x1ad ++#define Key_XF86Editor 0x1ae ++#define Key_XF86Excel 0x1af ++#define Key_XF86GraphicsEditor 0x1b0 ++#define Key_XF86Presentation 0x1b1 ++#define Key_XF86Database 0x1b2 ++#define Key_XF86News 0x1b3 ++#define Key_XF86Voicemail 0x1b4 ++#define Key_XF86Addressbook 0x1b5 ++#define Key_XF86Messenger2 0x1b6 ++#define Key_XF86DisplayToggle 0x1b7 ++#define Key_XF86SpellCheck 0x1b8 ++#define Key_XF86LogOff 0x1b9 ++#define Key_dollar2 0x1ba ++#define Key_EuroSign 0x1bb ++#define Key_XF86FrameBack 0x1bc ++#define Key_XF86FrameForward 0x1bd ++#define Key_XF86ContextMenu 0x1be ++#define Key_XF86MediaRepeat 0x1bf ++#define Key_XF8610ChannelsUp 0x1c0 ++#define Key_XF8610ChannelsDown 0x1c1 ++#define Key_XF86Images 0x1c2 ++#define Key_XF86NotificationCenter 0x1c4 ++#define Key_XF86PickupPhone 0x1c5 ++#define Key_XF86HangupPhone 0x1c6 ++#define Key_XF86Fn 0x1d8 ++#define Key_XF86Fn_Esc 0x1d9 ++#define Key_XF86FnRightShift 0x1ed ++#define Key_braille_dot_1 0x1f9 ++#define Key_braille_dot_2 0x1fa ++#define Key_braille_dot_3 0x1fb ++#define Key_braille_dot_4 0x1fc ++#define Key_braille_dot_5 0x1fd ++#define Key_braille_dot_6 0x1fe ++#define Key_braille_dot_7 0x1ff ++#define Key_braille_dot_8 0x200 ++#define Key_braille_dot_9 0x201 ++#define Key_braille_dot_1_2 0x202 ++#define Key_XF86Numeric0 0x208 ++#define Key_XF86Numeric1 0x209 ++#define Key_XF86Numeric2 0x20a ++#define Key_XF86Numeric3 0x20b ++#define Key_XF86Numeric4 0x20c ++#define Key_XF86Numeric5 0x20d ++#define Key_XF86Numeric6 0x20e ++#define Key_XF86Numeric7 0x20f ++#define Key_XF86Numeric8 0x210 ++#define Key_XF86Numeric9 0x211 ++#define Key_XF86NumericStar 0x212 ++#define Key_XF86NumericPound 0x213 ++#define Key_XF86NumericA 0x214 ++#define Key_XF86NumericB 0x215 ++#define Key_XF86NumericC 0x216 ++#define Key_XF86NumericD 0x217 ++#define Key_XF86CameraFocus 0x218 ++#define Key_XF86WPSButton 0x219 ++#define Key_XF86TouchpadToggle2 0x21a ++#define Key_XF86TouchpadOn2 0x21b ++#define Key_XF86TouchpadOff2 0x21c ++#define Key_XF86CameraZoomIn 0x21d ++#define Key_XF86CameraZoomOut 0x21e ++#define Key_XF86CameraUp 0x21f ++#define Key_XF86CameraDown 0x220 ++#define Key_XF86CameraLeft 0x221 ++#define Key_XF86CameraRight 0x222 ++#define Key_XF86AttendantOn 0x223 ++#define Key_XF86AttendantOff 0x224 ++#define Key_XF86AttendantToggle 0x225 ++#define Key_XF86LightsToggle 0x226 ++#define Key_XF86ALSToggle 0x238 ++#define Key_XF86RotationLockToggle 0x239 ++#define Key_XF86Buttonconfig 0x248 ++#define Key_XF86Taskmanager 0x249 ++#define Key_XF86Journal 0x24a ++#define Key_XF86ControlPanel 0x24b ++#define Key_XF86AppSelect 0x24c ++#define Key_XF86Screensaver 0x24d ++#define Key_XF86VoiceCommand 0x24e ++#define Key_XF86Assistant 0x24f ++#define Key_ISO_Next_Group 0x250 ++#define Key_XF86EmojiPicker 0x251 ++#define Key_XF86Dictate 0x252 ++#define Key_XF86CameraAccessEnable 0x253 ++#define Key_XF86CameraAccessDisable 0x254 ++#define Key_XF86CameraAccessToggle 0x255 ++#define Key_XF86BrightnessMin 0x258 ++#define Key_XF86BrightnessMax 0x259 ++#define Key_XF86KbdInputAssistPrev 0x268 ++#define Key_XF86KbdInputAssistNext 0x269 ++#define Key_XF86KbdInputAssistPrevgroup 0x26a ++#define Key_XF86KbdInputAssistNextgroup 0x26b ++#define Key_XF86KbdInputAssistAccept 0x26c ++#define Key_XF86KbdInputAssistCancel 0x26d ++#define Key_XF86RightUp 0x26e ++#define Key_XF86RightDown 0x26f ++#define Key_XF86LeftUp 0x270 ++#define Key_XF86LeftDown 0x271 ++#define Key_XF86RootMenu 0x272 ++#define Key_XF86MediaTopMenu 0x273 ++#define Key_XF86Numeric11 0x274 ++#define Key_XF86Numeric12 0x275 ++#define Key_XF86AudioDesc 0x276 ++#define Key_XF863DMode 0x277 ++#define Key_XF86NextFavorite 0x278 ++#define Key_XF86StopRecord 0x279 ++#define Key_XF86PauseRecord 0x27a ++#define Key_XF86VOD 0x27b ++#define Key_XF86Unmute 0x27c ++#define Key_XF86FastReverse 0x27d ++#define Key_XF86SlowReverse 0x27e ++#define Key_XF86Data 0x27f ++#define Key_XF86OnScreenKeyboard 0x280 ++#define Key_XF86PrivacyScreenToggle 0x281 ++#define Key_XF86SelectiveScreenshot 0x282 ++#define Key_XF86NextElement 0x283 ++#define Key_XF86PreviousElement 0x284 ++#define Key_XF86AutopilotEngageToggle 0x285 ++#define Key_XF86MarkWaypoint 0x286 ++#define Key_XF86Sos 0x287 ++#define Key_XF86NavChart 0x288 ++#define Key_XF86FishingChart 0x289 ++#define Key_XF86SingleRangeRadar 0x28a ++#define Key_XF86DualRangeRadar 0x28b ++#define Key_XF86RadarOverlay 0x28c ++#define Key_XF86TraditionalSonar 0x28d ++#define Key_XF86ClearvuSonar 0x28e ++#define Key_XF86SidevuSonar 0x28f ++#define Key_XF86NavInfo 0x290 ++#define Key_XF86BrightnessAdjust 0x291 ++#define Key_XF86Macro1 0x298 ++#define Key_XF86Macro2 0x299 ++#define Key_XF86Macro3 0x29a ++#define Key_XF86Macro4 0x29b ++#define Key_XF86Macro5 0x29c ++#define Key_XF86Macro6 0x29d ++#define Key_XF86Macro7 0x29e ++#define Key_XF86Macro8 0x29f ++#define Key_XF86Macro9 0x2a0 ++#define Key_XF86Macro10 0x2a1 ++#define Key_XF86Macro11 0x2a2 ++#define Key_XF86Macro12 0x2a3 ++#define Key_XF86Macro13 0x2a4 ++#define Key_XF86Macro14 0x2a5 ++#define Key_XF86Macro15 0x2a6 ++#define Key_XF86Macro16 0x2a7 ++#define Key_XF86Macro17 0x2a8 ++#define Key_XF86Macro18 0x2a9 ++#define Key_XF86Macro19 0x2aa ++#define Key_XF86Macro20 0x2ab ++#define Key_XF86Macro21 0x2ac ++#define Key_XF86Macro22 0x2ad ++#define Key_XF86Macro23 0x2ae ++#define Key_XF86Macro24 0x2af ++#define Key_XF86Macro25 0x2b0 ++#define Key_XF86Macro26 0x2b1 ++#define Key_XF86Macro27 0x2b2 ++#define Key_XF86Macro28 0x2b3 ++#define Key_XF86Macro29 0x2b4 ++#define Key_XF86Macro30 0x2b5 ++#define Key_XF86MacroRecordStart 0x2b8 ++#define Key_XF86MacroRecordStop 0x2b9 ++#define Key_XF86MacroPresetCycle 0x2ba ++#define Key_XF86MacroPreset1 0x2bb ++#define Key_XF86MacroPreset2 0x2bc ++#define Key_XF86MacroPreset3 0x2bd ++#define Key_XF86KbdLcdMenu1 0x2c0 ++#define Key_XF86KbdLcdMenu2 0x2c1 ++#define Key_XF86KbdLcdMenu3 0x2c2 ++#define Key_XF86KbdLcdMenu4 0x2c3 ++#define Key_XF86KbdLcdMenu5 0x2c4 +-- +2.48.0 + + +From 16c0b9be41ba111bf551fd2e5e2bdaa537c6990d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 19 Mar 2023 15:50:07 -0600 +Subject: [PATCH 2/2] add program to generate keys.h +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + generate-keys.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) + create mode 100644 generate-keys.c + +diff --git a/generate-keys.c b/generate-keys.c +new file mode 100644 +index 00000000..37655611 +--- /dev/null ++++ b/generate-keys.c +@@ -0,0 +1,95 @@ ++/****************************************************************** ++ * Copyright 2023-2024 Leonardo Hernández Hernández ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the “Software”), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ++ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ++ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ ******************************************************************/ ++ ++/* cc -lxkbcommon -o generate-keys generate-keys.c */ ++ ++#define _DEFAULT_SOURCE ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int ++main(void) ++{ ++ /* Allow generate keys with a different layout and variant. ++ * You can also use XKB_DEFAULT_* environmental variables and let this as is */ ++ struct xkb_rule_names rules = { ++ 0 ++ }; ++ struct xkb_context *context = NULL; ++ struct xkb_keymap *keymap = NULL; ++ xkb_keycode_t keycode, min_keycode, max_keycode; ++ xkb_layout_index_t layout, num_layouts; ++ xkb_level_index_t level, num_levels; ++ int i, nsyms; ++ const xkb_keysym_t *syms; ++ char keyname[64]; ++ bool ok = false; ++ FILE *file = fopen("keys.h", "w"); ++ if (!file) { ++ perror("Couldn't open keys.h"); ++ return EXIT_FAILURE; ++ } ++ ++ if (!(context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon context\n", stderr); ++ goto out; ++ } ++ ++ if (!(keymap = xkb_keymap_new_from_names(context, &rules, ++ XKB_KEYMAP_COMPILE_NO_FLAGS))) { ++ fputs("Couldn't create xkbcommon keymap\n", stderr); ++ goto out; ++ } ++ ++ min_keycode = xkb_keymap_min_keycode(keymap); ++ max_keycode = xkb_keymap_max_keycode(keymap); ++ ++ for (keycode = min_keycode; keycode <= max_keycode; keycode++) { ++ num_layouts = xkb_keymap_num_layouts_for_key(keymap, keycode); ++ for (layout = 0; layout < num_layouts; layout++) { ++ num_levels = xkb_keymap_num_levels_for_key(keymap, keycode, layout); ++ for (level = 0; level < num_levels; level++) { ++ nsyms = xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms); ++ for (i = 0; i < nsyms; i++) { ++ xkb_keysym_get_name(syms[i], keyname, sizeof(keyname) / sizeof(keyname[0])); ++ fprintf(file, "#define Key_%-27s %#05"PRIx32"\n", keyname, keycode); ++ } ++ } ++ } ++ } ++ ++ ok = true; ++ sync(); ++ ++out: ++ fclose(file); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ return !ok; ++} +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/launchtag/README.md b/dwl/dwl-patches/patches/launchtag/README.md new file mode 100644 index 0000000..86ad447 --- /dev/null +++ b/dwl/dwl-patches/patches/launchtag/README.md @@ -0,0 +1,12 @@ +### Description +Straight port of the dwm taglayouts patch, renamed because I have a hard time +finding it on the suckless site. + +This patch allows you to set the default layout for each tag. + +This is intended to be applied on top of pertag. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/launchtag/launchtag.patch) +### Authors - latest at top +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl/dwl-patches/patches/launchtag/launchtag.patch b/dwl/dwl-patches/patches/launchtag/launchtag.patch new file mode 100644 index 0000000..c298fd6 --- /dev/null +++ b/dwl/dwl-patches/patches/launchtag/launchtag.patch @@ -0,0 +1,49 @@ +From e94f4b8363f4122f9b00f774a5b541a00c49fd35 Mon Sep 17 00:00:00 2001 +From: MayOrMayNotBeACat +Date: Fri, 6 Jun 2025 22:42:16 -0400 +Subject: [PATCH] Allow initial layout to be set per tag. + +--- + config.def.h | 1 + + dwl.c | 9 ++++++++- + 2 files changed, 9 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..8f2fade 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -16,6 +16,7 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) ++static int tag_layout[TAGCOUNT] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; /* Modify as needed to match TAGCOUNT */ + + /* logging */ + static int log_level = WLR_ERROR; +diff --git a/dwl.c b/dwl.c +index d234cd2..da46816 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1045,11 +1045,18 @@ createmon(struct wl_listener *listener, void *data) + m->pertag->nmasters[i] = m->nmaster; + m->pertag->mfacts[i] = m->mfact; + +- m->pertag->ltidxs[i][0] = m->lt[0]; ++ if (i != 0) ++ m->pertag->ltidxs[i][0] = &layouts[tag_layout[i-1]]; ++ else ++ m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + } + ++ m->lt[0] = m->pertag->ltidxs[1][0]; ++ m->lt[1] = m->pertag->ltidxs[1][1]; ++ strncpy(m->ltsymbol, m->pertag->ltidxs[1][0]->symbol, LENGTH(m->ltsymbol)); ++ + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/limitnmaster/README.md b/dwl/dwl-patches/patches/limitnmaster/README.md new file mode 100644 index 0000000..e750300 --- /dev/null +++ b/dwl/dwl-patches/patches/limitnmaster/README.md @@ -0,0 +1,8 @@ +### Description +Limits nmaster to within the range of currently-opened windows (nmaster will not change past the full horizontal split layout) + +### Download +- [2024-03-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/limitnmaster/limitnmaster.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl/dwl-patches/patches/limitnmaster/limitnmaster.patch b/dwl/dwl-patches/patches/limitnmaster/limitnmaster.patch new file mode 100644 index 0000000..f4339c8 --- /dev/null +++ b/dwl/dwl-patches/patches/limitnmaster/limitnmaster.patch @@ -0,0 +1,33 @@ +From 20c948398af900564a59007fc08d15eaa0b65da3 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Fri, 15 Mar 2024 17:33:27 -0500 +Subject: [PATCH] Added limitnmaster + +--- + dwl.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..210c41d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1391,9 +1391,15 @@ handlesig(int signo) + void + incnmaster(const Arg *arg) + { ++ unsigned int n = 0; ++ Client *c; ++ + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) ++ n++; ++ selmon->nmaster = MIN(MAX(selmon->nmaster + arg->i, 0), n); + arrange(selmon); + } + +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/lockedkeys/README.md b/dwl/dwl-patches/patches/lockedkeys/README.md new file mode 100644 index 0000000..50f0fd2 --- /dev/null +++ b/dwl/dwl-patches/patches/lockedkeys/README.md @@ -0,0 +1,23 @@ +### Description +This patch allows you to add keybindings to the lockscreen. + +```c +static const Key lockedkeys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, +#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } + CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/lockedkeys) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/fc4146f3068dcd46035a2a11fe9d6109a97ae6d6/lockedkeys/lockedkeys.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/2a6560c167e5c9afc5598ac5431d23d90de8846c/lockedkeys/lockedkeys.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/lockedkeys/lockedkeys.patch b/dwl/dwl-patches/patches/lockedkeys/lockedkeys.patch new file mode 100644 index 0000000..f683536 --- /dev/null +++ b/dwl/dwl-patches/patches/lockedkeys/lockedkeys.patch @@ -0,0 +1,79 @@ +From 70dc03a3817b8fd933244c2db1bb849d9626b12b Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 11 Apr 2024 13:16:40 -0500 +Subject: [PATCH] allow to add keybindings in lockscreen + +--- + config.def.h | 11 +++++++++++ + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8847e58..0d4a4f8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -164,6 +164,17 @@ static const Key keys[] = { + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; + ++static const Key lockedkeys[] = { ++ /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ ++ /* modifier key function argument */ ++ ++ /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ ++ { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, ++#define CHVT(n) { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_XF86Switch_VT_##n, chvt, {.ui = (n)} } ++ CHVT(1), CHVT(2), CHVT(3), CHVT(4), CHVT(5), CHVT(6), ++ CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), ++}; ++ + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +diff --git a/dwl.c b/dwl.c +index bf763df..db4bb2b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -287,6 +287,7 @@ static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int lockedkeybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1446,6 +1447,21 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + return 0; + } + ++int ++lockedkeybinding(uint32_t mods, xkb_keysym_t sym) ++{ ++ int handled = 0; ++ const Key *k; ++ for (k = lockedkeys; k < END(lockedkeys); k++) { ++ if (CLEANMASK(mods) == CLEANMASK(k->mod) && ++ sym == k->keysym && k->func) { ++ k->func(&k->arg); ++ handled = 1; ++ } ++ } ++ return handled; ++} ++ + void + keypress(struct wl_listener *listener, void *data) + { +@@ -1473,6 +1489,10 @@ keypress(struct wl_listener *listener, void *data) + handled = keybinding(mods, syms[i]) || handled; + } + ++ if (locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) ++ for (i = 0; i < nsyms; i++) ++ handled = lockedkeybinding(mods, syms[i]) || handled; ++ + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; +-- +2.43.2 diff --git a/dwl/dwl-patches/patches/mastercolumn/README.md b/dwl/dwl-patches/patches/mastercolumn/README.md new file mode 100644 index 0000000..14db13c --- /dev/null +++ b/dwl/dwl-patches/patches/mastercolumn/README.md @@ -0,0 +1,16 @@ +### Description +This patch adds a layout, `mastercol`, in which the windows in the master area are arranged in columns of equal size. The number of columns is always nmaster + 1, and the last column is a stack of leftover windows (as in the normal tile layout). It effectively differs from the default tile layout only in that master windows are arranged horizontally rather than vertically. + +For gaps, apply `mastercolumn-gaps.patch` on top of `mastercolumn.patch` and `gaps.patch`. + +### Download +##### `mastercolumn.patch` +- [git branch](/shivers/dwl/src/branch/mastercolumn) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mastercolumn/mastercolumn.patch) + +##### `mastercolumn-gaps.patch` +- [git branch](/shivers/dwl/src/branch/mastercolumn-gaps) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mastercolumn/mastercolumn-gaps.patch) + +### Authors +- [shivers](https://codeberg.org/shivers) diff --git a/dwl/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch b/dwl/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch new file mode 100644 index 0000000..943c4bd --- /dev/null +++ b/dwl/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch @@ -0,0 +1,63 @@ +From b6f2ee09778cdea8a1450d16bcf24a8a75e10b40 Mon Sep 17 00:00:00 2001 +From: moe +Date: Tue, 16 Jul 2024 13:56:24 -0400 +Subject: [PATCH 1/1] add mastercolumn gaps + +--- + dwl.c | 23 +++++++++++++++-------- + 1 file changed, 15 insertions(+), 8 deletions(-) + +diff --git a/dwl.c b/dwl.c +index b121094..be33c01 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1755,7 +1755,7 @@ unset_fullscreen: + void + mastercol(Monitor *m) + { +- unsigned int mw, mx, ty; ++ unsigned int h, w, r, e = m->gaps, mw, mx, ty; + int i, n = 0; + Client *c; + +@@ -1764,23 +1764,30 @@ mastercol(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; + + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; +- i = mx = ty = 0; ++ i = 0; ++ mx = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y, +- .width = (mw - mx) / (MIN(n, m->nmaster) - i), .height = m->w.height}, 0); +- mx += c->geom.width; ++ r = MIN(n, m->nmaster) - i; ++ w = (mw - mx - gappx*e - gappx*e * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + gappx*e, ++ .width = w, .height = m->w.height - 2*gappx*e}, 0); ++ mx += c->geom.width + gappx*e; + } else { ++ r = n - i; ++ h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ .width = m->w.width - mw - gappx*e, .height = h}, 0); ++ ty += c->geom.height + gappx*e; + } + i++; + } +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/mastercolumn/mastercolumn.patch b/dwl/dwl-patches/patches/mastercolumn/mastercolumn.patch new file mode 100644 index 0000000..425e414 --- /dev/null +++ b/dwl/dwl-patches/patches/mastercolumn/mastercolumn.patch @@ -0,0 +1,87 @@ +From eb0c6ff53ba823f26d13f18627a084959c353627 Mon Sep 17 00:00:00 2001 +From: moe +Date: Sat, 10 Aug 2024 15:58:15 -0400 +Subject: [PATCH] add mastercolumn layout + +--- + config.def.h | 2 ++ + dwl.c | 36 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..68b27a7 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "||=", mastercol }, + }; + + /* monitors */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..49f65ba 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -301,6 +301,7 @@ static int keyrepeat(void *data); + static void killclient(const Arg *arg); + static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); ++static void mastercol(Monitor *m); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); +@@ -1748,6 +1749,41 @@ unset_fullscreen: + } + } + ++void ++mastercol(Monitor *m) ++{ ++ unsigned int mw, mx, ty; ++ int i, n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ else ++ mw = m->w.width; ++ i = mx = ty = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (i < m->nmaster) { ++ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y, ++ .width = (mw - mx) / (MIN(n, m->nmaster) - i), .height = m->w.height}, 0); ++ mx += c->geom.width; ++ } else { ++ resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ ty += c->geom.height; ++ } ++ i++; ++ } ++} ++ ++ + void + maximizenotify(struct wl_listener *listener, void *data) + { +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/menu/README.md b/dwl/dwl-patches/patches/menu/README.md new file mode 100644 index 0000000..ad37016 --- /dev/null +++ b/dwl/dwl-patches/patches/menu/README.md @@ -0,0 +1,18 @@ +### Description + +This patch adds `menu` command, which allows dwl to interface with dmenu-like +programs. + +By default, two menus are available: +- focusing a window by its title/appid by pressing `Alt+o` +- selecting a layout from a list by pressing `Alt+Shift+o` + +Edit `menus` array in `config.h` to add/change menus and use a different dmenu +program (`wmenu` is the default). + +### Download +- [2025-03-21 v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/menu/menu.patch) +- [2024-07-13 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/menu/menu.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/menu/menu.patch b/dwl/dwl-patches/patches/menu/menu.patch new file mode 100644 index 0000000..c1dea4d --- /dev/null +++ b/dwl/dwl-patches/patches/menu/menu.patch @@ -0,0 +1,227 @@ +From da9861cf0448ca94011470634fd61c3ef2129a25 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 21 Mar 2025 21:48:42 +0100 +Subject: [PATCH] Add menu command + +--- + config.def.h | 8 +++ + dwl.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 164 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..a5914ca 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,12 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++static const Menu menus[] = { ++ /* command feed function action function */ ++ { "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction }, ++ { "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction }, ++}; ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +@@ -140,6 +146,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +diff --git a/dwl.c b/dwl.c +index def2562..b0e8310 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -242,6 +242,12 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *cmd; /* command to run a menu */ ++ void (*feed)(FILE *f); /* feed input to menu */ ++ void (*action)(char *line); /* do action based on menu output */ ++} Menu; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -302,6 +308,12 @@ static void killclient(const Arg *arg); + static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); ++static void menu(const Arg *arg); ++static int menuread(int fd, uint32_t mask, void *data); ++static void menuwinfeed(FILE *f); ++static void menuwinaction(char *line); ++static void menulayoutfeed(FILE *f); ++static void menulayoutaction(char *line); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, +@@ -413,6 +425,11 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const Menu *menu_current; ++static int menu_fd; ++static pid_t menu_pid; ++static struct wl_event_source *menu_source; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1768,6 +1785,145 @@ maximizenotify(struct wl_listener *listener, void *data) + wlr_xdg_surface_schedule_configure(c->surface.xdg); + } + ++void ++menu(const Arg *arg) ++{ ++ FILE *f; ++ int fd_right[2], fd_left[2]; ++ ++ if (menu_current != NULL) { ++ wl_event_source_remove(menu_source); ++ close(menu_fd); ++ kill(menu_pid, SIGTERM); ++ menu_current = NULL; ++ if (!arg->v) ++ return; ++ } ++ ++ if (pipe(fd_right) == -1 || pipe(fd_left) == -1) ++ return; ++ if ((menu_pid = fork()) == -1) ++ return; ++ if (menu_pid == 0) { ++ close(fd_right[1]); ++ close(fd_left[0]); ++ dup2(fd_right[0], STDIN_FILENO); ++ close(fd_right[0]); ++ dup2(fd_left[1], STDOUT_FILENO); ++ close(fd_left[1]); ++ execl("/bin/sh", "/bin/sh", "-c", ((Menu *)(arg->v))->cmd, NULL); ++ die("dwl: execl %s failed:", "/bin/sh"); ++ } ++ ++ close(fd_right[0]); ++ close(fd_left[1]); ++ menu_fd = fd_left[0]; ++ if (fd_set_nonblock(menu_fd) == -1) ++ return; ++ if (!(f = fdopen(fd_right[1], "w"))) ++ return; ++ menu_current = arg->v; ++ menu_current->feed(f); ++ fclose(f); ++ menu_source = wl_event_loop_add_fd(event_loop, ++ menu_fd, WL_EVENT_READABLE, menuread, NULL); ++} ++ ++int ++menuread(int fd, uint32_t mask, void *data) ++{ ++ char *s; ++ int n; ++ static char line[512]; ++ static int i = 0; ++ ++ if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { ++ i = 0; ++ menu(&(const Arg){ .v = NULL }); ++ return 0; ++ } ++ if ((n = read(menu_fd, line + i, LENGTH(line) - 1 - i)) == -1) { ++ if (errno != EAGAIN) { ++ i = 0; ++ menu(&(const Arg){ .v = NULL }); ++ } ++ return 0; ++ } ++ line[i + n] = '\0'; ++ if (!(s = strchr(line + i, '\n'))) { ++ i += n; ++ return 0; ++ } ++ i = 0; ++ *s = '\0'; ++ menu_current->action(line); ++ return 0; ++} ++ ++void ++menuwinfeed(FILE *f) ++{ ++ Client *c; ++ const char *title, *appid; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!(title = client_get_title(c))) ++ continue; ++ fprintf(f, "%s", title); ++ if ((appid = client_get_appid(c))) ++ fprintf(f, " | %s", appid); ++ fputc('\n', f); ++ } ++} ++ ++void ++menuwinaction(char *line) ++{ ++ Client *c; ++ const char *appid, *title; ++ static char buf[512]; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!(title = client_get_title(c))) ++ continue; ++ appid = client_get_appid(c); ++ snprintf(buf, LENGTH(buf) - 1, "%s%s%s", ++ title, appid ? " | " : "", appid ? appid : ""); ++ if (strcmp(line, buf) == 0) ++ goto found; ++ } ++ return; ++ ++found: ++ if (!c->mon) ++ return; ++ wl_list_remove(&c->flink); ++ wl_list_insert(&fstack, &c->flink); ++ selmon = c->mon; ++ view(&(const Arg){ .ui = c->tags }); ++} ++ ++void ++menulayoutfeed(FILE *f) ++{ ++ const Layout *l; ++ for (l = layouts; l < END(layouts); l++) ++ fprintf(f, "%s\n", l->symbol); ++} ++ ++void ++menulayoutaction(char *line) ++{ ++ const Layout *l; ++ for (l = layouts; l < END(layouts); l++) ++ if (strcmp(line, l->symbol) == 0) ++ goto found; ++ return; ++ ++found: ++ setlayout(&(const Arg){ .v = l }); ++} ++ + void + monocle(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/menurule/README.md b/dwl/dwl-patches/patches/menurule/README.md new file mode 100644 index 0000000..8d8b757 --- /dev/null +++ b/dwl/dwl-patches/patches/menurule/README.md @@ -0,0 +1,70 @@ +### Description + +This patch adds a dmenu interface to [setrule][setrule], which allows to add or +change client rules at runtime. It must be applied on top of [setrule][setrule] +and [menu][menu] patches. + +You can invoke the menu by pressing `Alt+R`. The menu lists all the rules, plus +a shortcut to define a new one that would apply to the currently focused client +(marked with `(NEW)`). Rules that already apply to the focused client are marked +with `<`. + +![menurule in action](menurule.png) + +To edit a rule, you need to select it, press `Tab`, change what you need and +finally press `Enter`. You can remove a rule by prepending it with `-`. To add a +new rule, just put new values into `[appid|title]`. + +To add support for new rules, you need to edit `fprintf` and `sscanf` calls in +`menurulefeed` and `menuruleaction` functions respectively. + +For example, this is what I did to add support for [swallow][swallow] patch +rules. + +```diff +diff --git a/dwl.c b/dwl.c +index 34397fc..f1b31ea 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2441,10 +2441,14 @@ menurulefeed(FILE *f) + fprintf(f, "%-*s " + " tags:%-4"PRIi32 + " isfloating:%-2d" ++ " isterm:%-2d" ++ " noswallow:%-2d" + " monitor:%-2d" + "%s\n", wid, buf, + r->tags, + r->isfloating, ++ r->isterm, ++ r->noswallow, + r->monitor, + (r == &t) ? " (NEW)" : match ? " <" : ""); + } +@@ -2465,10 +2469,14 @@ menuruleaction(char *line) + sscanf(line, "[%255[^|]|%255[^]]]" + " tags:%"SCNu32 + " isfloating:%d" ++ " isterm:%d" ++ " noswallow:%d" + " monitor:%d" + "%n", appid, title, + &r.tags, + &r.isfloating, ++ &r.isterm, ++ &r.noswallow, + &r.monitor, + &end); +``` + +[setrule]: /dwl/dwl-patches/src/branch/main/patches/setrule +[menu]: /dwl/dwl-patches/src/branch/main/patches/menu +[swallow]: /dwl/dwl-patches/src/branch/main/patches/swallow + +### Download + +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/menurule/menurule.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/menurule/menurule.patch b/dwl/dwl-patches/patches/menurule/menurule.patch new file mode 100644 index 0000000..b74a600 --- /dev/null +++ b/dwl/dwl-patches/patches/menurule/menurule.patch @@ -0,0 +1,167 @@ +From 7b578d9f4647d84f79a2e8a46a1c65cbacf8d90b Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Wed, 19 Mar 2025 02:28:46 +0100 +Subject: [PATCH] Add menurule to tweak rules at runtime + +--- + config.def.h | 2 + + dwl.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 118 insertions(+) + +diff --git a/config.def.h b/config.def.h +index e03a754..77b10ff 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -24,6 +24,7 @@ static const Menu menus[] = { + /* command feed function action function */ + { "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction }, + { "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction }, ++ { "wmenu -i -l 10 -p Rules", menurulefeed, menuruleaction }, + }; + + /* Max amount of dynamically added rules */ +@@ -151,6 +152,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} }, ++ { MODKEY, XKB_KEY_r, menu, {.v = &menus[2]} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{0} }, +diff --git a/dwl.c b/dwl.c +index be007d8..df4901f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -316,6 +316,8 @@ static void menuwinfeed(FILE *f); + static void menuwinaction(char *line); + static void menulayoutfeed(FILE *f); + static void menulayoutaction(char *line); ++static void menurulefeed(FILE *f); ++static void menuruleaction(char *line); + static void monocle(Monitor *m); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, +@@ -1974,6 +1976,120 @@ found: + setlayout(&(const Arg){ .v = l }); + } + ++void ++menurulefeed(FILE *f) ++{ ++ Rule t, *p, *r; ++ const char *appid, *title; ++ static char buf[515]; ++ Client *c = focustop(selmon); ++ int n, wid = 0, match; ++ ++ t = (Rule){ 0 }; ++ t.monitor = -1; ++ if (c) { ++ t.id = client_get_appid(c); ++ t.title = client_get_title(c); ++ appid = t.id ? t.id : broken; ++ title = t.title ? t.title : broken; ++ } ++ ++ for (p = drules; p <= drules + druleslen; p++) { ++ r = (p == drules + druleslen) ? &t : p; ++ n = 0; ++ n += strlen(r->id ? r->id : "NULL"); ++ n += strlen(r->title ? r->title : "NULL"); ++ n += 3; ++ wid = MAX(wid, n); ++ } ++ wid = MIN(wid, 40); ++ ++ for (p = drules; p <= drules + druleslen; p++) { ++ match = 0; ++ /* Check if rule applies to the focused client */ ++ if (c && p < drules + druleslen) { ++ match = (!p->title || strstr(title, p->title)) ++ && (!p->id || strstr(appid, p->id)); ++ if (match && p->id) ++ t.id = NULL; ++ if (match && p->title) ++ t.title = NULL; ++ } ++ r = (p == drules + druleslen) ? &t : p; ++ if (r == &t && t.id) ++ t.title = NULL; ++ /* Do not suggest to add a new empty rule */ ++ if (r == &t && !(t.id || t.title)) ++ continue; ++ if (r->id && r->title && ++ strcmp(r->id, "removedrule") == 0 && strcmp(r->title, "removedrule") == 0) ++ continue; ++ snprintf(buf, LENGTH(buf) - 1, "[%s|%s]", ++ r->id ? r->id : "NULL", r->title ? r->title : "NULL"); ++ fprintf(f, "%-*s " ++ " tags:%-4"PRIi32 ++ " isfloating:%-2d" ++ " monitor:%-2d" ++ "%s\n", wid, buf, ++ r->tags, ++ r->isfloating, ++ r->monitor, ++ (r == &t) ? " (NEW)" : match ? " <" : ""); ++ } ++} ++ ++void ++menuruleaction(char *line) ++{ ++ Rule r, *f; ++ static char appid[256], title[256]; ++ int del = 0, end; ++ ++ if (line[0] == '-') { ++ del = 1; ++ line++; ++ } ++ end = 0; ++ sscanf(line, "[%255[^|]|%255[^]]]" ++ " tags:%"SCNu32 ++ " isfloating:%d" ++ " monitor:%d" ++ "%n", appid, title, ++ &r.tags, ++ &r.isfloating, ++ &r.monitor, ++ &end); ++ ++ /* Full line was not parsed, exit */ ++ if (!end) ++ return; ++ ++ r.id = (strcmp(appid, "NULL") != 0) ? appid : NULL; ++ r.title = (strcmp(title, "NULL") != 0) ? title : NULL; ++ ++ /* Find which rule we are trying to edit */ ++ for (f = drules; f < drules + druleslen; f++) ++ if (((!r.title && !f->title) || (r.title && f->title && !strcmp(r.title, f->title))) ++ && (((!r.id && !f->id) || (r.id && f->id && !strcmp(r.id, f->id))))) ++ goto found; ++ ++ if (druleslen >= LENGTH(rules) + RULES_MAX) ++ return; /* No free slots left */ ++ ++ f = drules + druleslen++; ++ f->id = r.id ? strdup(r.id) : NULL; ++ f->title = r.title ? strdup(r.title) : NULL; ++ ++found: ++ if (del) { ++ f->id = f->title = "removedrule"; ++ return; ++ } ++ r.id = f->id; ++ r.title = f->title; ++ *f = r; ++} ++ + void + monocle(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/menurule/menurule.png b/dwl/dwl-patches/patches/menurule/menurule.png new file mode 100644 index 0000000..20faac2 Binary files /dev/null and b/dwl/dwl-patches/patches/menurule/menurule.png differ diff --git a/dwl/dwl-patches/patches/meson/README.md b/dwl/dwl-patches/patches/meson/README.md new file mode 100644 index 0000000..4641bbf --- /dev/null +++ b/dwl/dwl-patches/patches/meson/README.md @@ -0,0 +1,19 @@ +### Description +Add the meson build system. + +This is useful for people who do not want to self-manage a wlroots installation. + +To enable Xwayland support, you will need to enable it in the wlroots subproject: +```sh +meson setup -Dwlroots:xwayland=enabled build +``` +It is also reccomended to see the wlroots meson project configuration logs for any +unusual checks, such as requiring `hwdata` for the DRM backend. + +### Download +- [git branch](/sewn/dwl/src/branch/meson) +- [2024-12-07](/dwl/dwl-patches/raw/branch/main/patches/meson/meson.patch) + +### Authors +- [sewn](/sewn) + diff --git a/dwl/dwl-patches/patches/meson/meson.patch b/dwl/dwl-patches/patches/meson/meson.patch new file mode 100644 index 0000000..6d78036 --- /dev/null +++ b/dwl/dwl-patches/patches/meson/meson.patch @@ -0,0 +1,135 @@ +From 136cdeb302fdfe28e5cd5c6a1693b05c3d1bfb58 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sat, 7 Dec 2024 09:59:01 +0300 +Subject: [PATCH] add meson + +--- + .gitignore | 1 + + meson.build | 93 ++++++++++++++++++++++++++++++++++++++++ + subprojects/wlroots.wrap | 5 +++ + 3 files changed, 99 insertions(+) + create mode 100644 meson.build + create mode 100644 subprojects/wlroots.wrap + +diff --git a/.gitignore b/.gitignore +index 0dde90e..9246a31 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -4,3 +4,4 @@ dwl + *-protocol.h + .ccls-cache + config.h ++subprojects/wlroots +diff --git a/meson.build b/meson.build +new file mode 100644 +index 0000000..e2219ec +--- /dev/null ++++ b/meson.build +@@ -0,0 +1,93 @@ ++project( ++ 'dwl', ++ 'c', ++ version: run_command('git', 'describe', '--tags', '--dirty', check: false).stdout().strip(), ++ license: [ 'GPL-3.0-only', 'CC0-1.0', 'MIT' ], ++ meson_version: '>=1.3', ++ default_options: [ ++ 'c_std=c99', ++ 'warning_level=2', ++ ], ++) ++ ++configure_file( ++ input: 'config.def.h', ++ output: 'config.h', ++ copy: true, ++ install_dir: '.', ++) ++ ++cc = meson.get_compiler('c') ++ ++add_project_arguments([ ++ '-DWLR_USE_UNSTABLE', ++ '-D_POSIX_C_SOURCE=200809L', ++ '-DVERSION="@0@"'.format(meson.project_version()), ++ ++ '-Wno-unused-parameter', ++], language: 'c') ++ ++wlroots = subproject('wlroots', ++ default_options: [ ++ 'backends=drm,libinput,auto', ++ 'default_library=static', ++ 'examples=false', ++ 'session=enabled', ++ ], ++) ++wlroots_has_xwlr = wlroots.get_variable('features').get('xwayland') ++ ++libinput = dependency('libinput') ++math = cc.find_library('m') ++wayland_server = dependency('wayland-server') ++xcb = dependency('xcb', required: wlroots_has_xwlr) ++xcb_icccm = dependency('xcb-icccm', required: wlroots_has_xwlr) ++xkbcommon = dependency('xkbcommon') ++ ++dwl_deps = [ ++ libinput, ++ math, ++ wayland_server, ++ wlroots.get_variable('wlroots'), ++ xkbcommon, ++] ++ ++if wlroots_has_xwlr ++ add_project_arguments('-DXWAYLAND', language: 'c') ++ dwl_deps += [ xcb, xcb_icccm ] ++endif ++ ++wayland_protos = dependency('wayland-protocols') ++wayland_scanner = dependency('wayland-scanner') ++wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir') ++wayland_scanner_prog = find_program( ++ wayland_scanner.get_variable('wayland_scanner'), ++ native: true, ++) ++ ++protocols = [ ++ wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', ++ wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', ++ 'protocols/wlr-layer-shell-unstable-v1.xml', ++ 'protocols/wlr-output-power-management-unstable-v1.xml', ++ wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', ++] ++protocols_src = [] ++ ++wayland_scanner_server = generator( ++ wayland_scanner_prog, ++ output: '@BASENAME@-protocol.h', ++ arguments: ['server-header', '@INPUT@', '@OUTPUT@'], ++) ++ ++foreach xml : protocols ++ protocols_src += wayland_scanner_server.process(xml) ++endforeach ++ ++executable( ++ 'dwl', ++ [ 'dwl.c', 'util.c', protocols_src ], ++ include_directories: [include_directories('.')], ++ dependencies: dwl_deps, ++ install: true, ++) +diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap +new file mode 100644 +index 0000000..3d9cbfa +--- /dev/null ++++ b/subprojects/wlroots.wrap +@@ -0,0 +1,5 @@ ++[wrap-git] ++url = https://gitlab.freedesktop.org/wlroots/wlroots.git ++revision = master ++depth = 1 ++clone-recursive = true +-- +2.47.1 + diff --git a/dwl/dwl-patches/patches/minimalborders/README.md b/dwl/dwl-patches/patches/minimalborders/README.md new file mode 100644 index 0000000..2a670c9 --- /dev/null +++ b/dwl/dwl-patches/patches/minimalborders/README.md @@ -0,0 +1,34 @@ +### Description +Dynamically adjusts the borders between adjacent windows to make them visually merge + +**NOTE:** to disable minimalborders after applying this patch, set `draw_minimal_borders` to `0` + +```c +static const int draw_minimal_borders = 0; /* disable minimalborders */ +``` + +
+Preview: +
+with:
+
+```c
+static const unsigned int borderpx         = 10;  /* border pixel of windows */
+```
+
+Before applying the patch
+
+
+After applying the patch
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/minimalborders) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/minimalborders/minimalborders.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/7a5c3420822074c544fa102e030b7c30aa6b6be8/minimalborders/minimalborders.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/be3735bc6a5c64ff76c200a8679453bd179be456/minimalborders/minimalborders.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/minimalborders/minimalborders.patch b/dwl/dwl-patches/patches/minimalborders/minimalborders.patch new file mode 100644 index 0000000..cb19900 --- /dev/null +++ b/dwl/dwl-patches/patches/minimalborders/minimalborders.patch @@ -0,0 +1,161 @@ +From ce0eb92fb100801f343fbe9b76639847a9e39160 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:22:57 -0500 +Subject: [PATCH] implement minimalborders + +--- + config.def.h | 1 + + dwl.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 72 insertions(+), 6 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..0322dbf 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const int draw_minimal_borders = 1; /* merge adjacent borders */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +diff --git a/dwl.c b/dwl.c +index dc0437e..198061b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -106,6 +106,7 @@ typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ ++ int interact; + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; +@@ -316,7 +317,8 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resizeapply(Client *c, struct wlr_box geo, int interact); ++static void resizenoapply(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -408,6 +410,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static void (*resize)(Client *c, struct wlr_box geo, int interact) = resizeapply; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -476,6 +480,35 @@ applyrules(Client *c) + setmon(c, mon, newtags); + } + ++void ++applyminimalborders(Client *c, Monitor *m) ++{ ++ struct wlr_box geom = c->geom; ++ ++ geom.x -= borderpx; ++ geom.width += borderpx; ++ geom.y -= borderpx; ++ geom.height += borderpx; ++ ++ if (geom.x < m->w.x) { ++ geom.x += borderpx; ++ geom.width -= borderpx; ++ } ++ if (geom.x + geom.width > m->w.width - (int)borderpx) { ++ geom.width -= borderpx; ++ } ++ ++ if (geom.y < m->w.y) { ++ geom.y += borderpx; ++ geom.height -= borderpx; ++ } ++ if (geom.y + geom.height > m->w.height - (int)borderpx) { ++ geom.height -= borderpx; ++ } ++ ++ resize(c, geom, 0); ++} ++ + void + arrange(Monitor *m) + { +@@ -510,8 +543,28 @@ arrange(Monitor *m) + : c->scene->node.parent); + } + +- if (m->lt[m->sellt]->arrange) +- m->lt[m->sellt]->arrange(m); ++ if (m->lt[m->sellt]->arrange) { ++ if (draw_minimal_borders) { ++ int save_width = m->w.width; ++ int save_height = m->w.height; ++ m->w.width += borderpx; ++ m->w.height += borderpx; ++ resize = resizenoapply; ++ m->lt[m->sellt]->arrange(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ if (draw_minimal_borders) ++ applyminimalborders(c, m); ++ resizeapply(c, c->geom, c->interact); ++ } ++ m->w.width = save_width; ++ m->w.height = save_height; ++ resize = resizeapply; ++ } else { ++ m->lt[m->sellt]->arrange(m); ++ } ++ } + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -1962,8 +2015,13 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && +- sloppyfocus && time && c && !client_is_unmanaged(c)) +- focusclient(c, 0); ++ sloppyfocus && time && c && !client_is_unmanaged(c)) { ++ if (c->isfloating || c->isfullscreen) { ++ focusclient(c, 0); ++ } else { ++ focusclient(c, 1); ++ } ++ } + + /* If surface is NULL, clear pointer focus */ + if (!surface) { +@@ -2128,7 +2186,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resizeapply(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2160,6 +2218,13 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++void ++resizenoapply(Client *c, struct wlr_box geo, int interact) ++{ ++ c->geom = geo; ++ c->interact = interact; ++} ++ + void + run(char *startup_cmd) + { +-- +2.45.1 diff --git a/dwl/dwl-patches/patches/modes/README.md b/dwl/dwl-patches/patches/modes/README.md new file mode 100644 index 0000000..c855af9 --- /dev/null +++ b/dwl/dwl-patches/patches/modes/README.md @@ -0,0 +1,35 @@ +### Description +Implement modes, that way each mapping is associated with a mode and is only active while in that mode, default mode is `NORMAL` + +### Example + +In the example below, you declare a mode: `BROWSER`, which is activated when you press modkey + b. Then, you can press f to launch `Firefox` and return to the default `NORMAL` mode. + +```c +enum { + BROWSER, +}; +const char *modes_labels[] = { + "browser", +}; + +static const Key keys[] = { + // ... + { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} }, + // ... +}; + +static const Modekey modekeys[] = { + /* mode modifier key function argument */ + { BROWSER, { 0, XKB_KEY_f, spawn, SHCMD("firefox") } }, + { BROWSER, { 0, XKB_KEY_f, entermode, {.i = NORMAL} } }, + { BROWSER, { 0, XKB_KEY_Escape, entermode, {.i = NORMAL} } }, +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/modes) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/modes/modes.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/modes/modes.patch b/dwl/dwl-patches/patches/modes/modes.patch new file mode 100644 index 0000000..5ecf846 --- /dev/null +++ b/dwl/dwl-patches/patches/modes/modes.patch @@ -0,0 +1,165 @@ +From a32b85018ff2cea0fc9f9137789860a4aadc3b3a Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 6 Mar 2024 07:31:17 -0500 +Subject: [PATCH] implement modes + +like sway/river modes +--- + config.def.h | 20 ++++++++++++++++++++ + dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 66 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..1616136 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* You can also use glsl colors */ + ++enum { ++ BROWSER, ++}; ++const char *modes_labels[] = { ++ "browser", ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -152,6 +159,8 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + ++ { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} }, ++ + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is +@@ -162,6 +171,17 @@ static const Key keys[] = { + CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12), + }; + ++static const Modekey modekeys[] = { ++ /* mode modifier key function argument */ ++ { BROWSER, { 0, XKB_KEY_f, spawn, SHCMD("firefox") } }, ++ { BROWSER, { 0, XKB_KEY_f, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_b, spawn, SHCMD("brave") } }, ++ { BROWSER, { 0, XKB_KEY_b, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_g, spawn, SHCMD("google-chrome-stable") } }, ++ { BROWSER, { 0, XKB_KEY_g, entermode, {.i = NORMAL} } }, ++ { BROWSER, { 0, XKB_KEY_Escape, entermode, {.i = NORMAL} } }, ++}; ++ + static const Button buttons[] = { + { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, + { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +diff --git a/dwl.c b/dwl.c +index ef27a1d..1ada006 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,11 @@ typedef struct { + const Arg arg; + } Key; + ++typedef struct { ++ int mode_index; ++ Key key; ++} Modekey; ++ + typedef struct { + struct wl_list link; + struct wlr_keyboard *wlr_keyboard; +@@ -270,6 +275,7 @@ static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int modekeybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -327,6 +333,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void entermode(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -377,6 +384,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const int NORMAL = -1; ++static int active_mode_index = NORMAL; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -1372,6 +1382,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + */ + int handled = 0; + const Key *k; ++ ++ if (active_mode_index >= 0) { ++ return modekeybinding(mods, sym); ++ } ++ + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) && + sym == k->keysym && k->func) { +@@ -1382,6 +1397,29 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + return handled; + } + ++int ++modekeybinding(uint32_t mods, xkb_keysym_t sym) ++{ ++ int handled = 0; ++ const Modekey *mk; ++ const Key *k; ++ ++ for (mk = modekeys; mk < END(modekeys); mk++) { ++ if (active_mode_index != mk->mode_index) { ++ continue; ++ } ++ ++ k = &mk->key; ++ if (CLEANMASK(mods) == CLEANMASK(k->mod) && ++ sym == k->keysym && k->func) { ++ k->func(&k->arg); ++ handled = 1; ++ } ++ } ++ ++ return handled; ++} ++ + void + keypress(struct wl_listener *listener, void *data) + { +@@ -1851,6 +1889,7 @@ printstatus(void) + printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags], + sel, urg); + printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); ++ printf("%s mode %s\n", m->wlr_output->name, modes_labels[active_mode_index] ? modes_labels[active_mode_index] : ""); + } + fflush(stdout); + } +@@ -2746,6 +2785,13 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++entermode(const Arg *arg) ++{ ++ active_mode_index = arg->i; ++ printstatus(); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.42.0 + diff --git a/dwl/dwl-patches/patches/monitorconfig/README.md b/dwl/dwl-patches/patches/monitorconfig/README.md new file mode 100644 index 0000000..313e5a0 --- /dev/null +++ b/dwl/dwl-patches/patches/monitorconfig/README.md @@ -0,0 +1,9 @@ +### Description +Allows more monitor configuration in config.h + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/monitorconfig) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/monitorconfig/monitorconfig.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/monitorconfig/monitorconfig.patch b/dwl/dwl-patches/patches/monitorconfig/monitorconfig.patch new file mode 100644 index 0000000..4922f8d --- /dev/null +++ b/dwl/dwl-patches/patches/monitorconfig/monitorconfig.patch @@ -0,0 +1,95 @@ +From 73f70cd9d817a307030f360f6c8a2500046b8b76 Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Mon, 4 Apr 2022 16:08:29 +0200 +Subject: [PATCH] Updated patch now allowing setting x and y +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: Leonardo Hernández Hernández +--- + config.def.h | 11 +++++++---- + dwl.c | 25 +++++++++++++++++++------ + 2 files changed, 26 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8f498d2..4ccacd2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -38,12 +38,15 @@ static const Layout layouts[] = { + /* monitors */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ +- /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ /* name mfact nmaster scale layout rotate/reflect x y resx resy rate mode adaptive*/ ++ /* example of a HiDPI laptop monitor at 120Hz: ++ { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 120.000f, 1, 1}, ++ * mode let's the user decide on how dwl should implement the modes: ++ * -1 Sets a custom mode following the users choice ++ * All other number's set the mode at the index n, 0 is the standard mode; see wlr-randr + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 0 ,1}, + }; + + /* keyboard */ +diff --git a/dwl.c b/dwl.c +index 52bfbc8..9609b6d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -215,6 +215,11 @@ typedef struct { + const Layout *lt; + enum wl_output_transform rr; + int x, y; ++ int resx; ++ int resy; ++ float rate; ++ int mode; ++ int adaptive; + } MonitorRule; + + typedef struct { +@@ -865,6 +870,7 @@ createmon(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; ++ struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.next, mode, link); + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +@@ -893,16 +899,23 @@ createmon(struct wl_listener *listener, void *data) + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); ++ ++ wlr_output_state_set_adaptive_sync_enabled(&state, r->adaptive); ++ ++ if(r->mode == -1) ++ wlr_output_state_set_custom_mode(&state, r->resx, r->resy, ++ (int) (r->rate > 0 ? r->rate * 1000 : 0)); ++ else if (!wl_list_empty(&wlr_output->modes)) { ++ for (int j = 0; j < r->mode; j++) { ++ mode = wl_container_of(mode->link.next, mode, link); ++ } ++ wlr_output_state_set_mode(&state, mode); ++ } ++ + break; + } + } + +- /* The mode is a tuple of (width, height, refresh rate), and each +- * monitor supports only a specific set of modes. We just pick the +- * monitor's preferred mode; a more sophisticated compositor would let +- * the user configure it. */ +- wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); +- + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/mouse-trackpad-split/README.md b/dwl/dwl-patches/patches/mouse-trackpad-split/README.md new file mode 100644 index 0000000..a3e6e44 --- /dev/null +++ b/dwl/dwl-patches/patches/mouse-trackpad-split/README.md @@ -0,0 +1,8 @@ +### Description +Separates natural scrolling and acceleration settings for trackpad and mouse. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch) + +### Authors +- [mmistika](https://codeberg.org/mmistika) diff --git a/dwl/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch b/dwl/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch new file mode 100644 index 0000000..89e33ac --- /dev/null +++ b/dwl/dwl-patches/patches/mouse-trackpad-split/mouse-trackpad-split-0.7.patch @@ -0,0 +1,96 @@ +From 211b52988756e9fecccf071fdea82832e1e17a0c Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Tue, 24 Jun 2025 22:25:00 +0200 +Subject: [PATCH] Separate trackpad/mouse natural scroll and accel + +Signed-off-by: mmistika +--- + config.def.h | 12 +++++++++--- + dwl.c | 26 ++++++++++++++++++-------- + 2 files changed, 27 insertions(+), 11 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d05a89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -67,10 +67,14 @@ static const int repeat_delay = 600; + static const int tap_to_click = 1; + static const int tap_and_drag = 1; + static const int drag_lock = 1; +-static const int natural_scrolling = 0; + static const int disable_while_typing = 1; + static const int left_handed = 0; + static const int middle_button_emulation = 0; ++ ++/* Natural scrolling */ ++static const int trackpad_natural_scrolling = 0; ++static const int mouse_natural_scrolling = 0; ++ + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +@@ -97,8 +101,10 @@ static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE + */ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; ++static const enum libinput_config_accel_profile trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; ++static const double trackpad_accel_speed = 0.0; ++static const enum libinput_config_accel_profile mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; ++static const double mouse_accel_speed = 0.0; + + /* You can choose between: + LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +diff --git a/dwl.c b/dwl.c +index c717c1d..f05d6c5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1083,14 +1083,29 @@ createpointer(struct wlr_pointer *pointer) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { ++ /* Trackpad */ + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); +- } + +- if (libinput_device_config_scroll_has_natural_scroll(device)) +- libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, trackpad_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, trackpad_accel_profile); ++ libinput_device_config_accel_set_speed(device, trackpad_accel_speed); ++ } ++ } else { ++ /* Mouse */ ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, mouse_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, mouse_accel_profile); ++ libinput_device_config_accel_set_speed(device, mouse_accel_speed); ++ } ++ } + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); +@@ -1109,11 +1124,6 @@ createpointer(struct wlr_pointer *pointer) + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); +- +- if (libinput_device_config_accel_is_available(device)) { +- libinput_device_config_accel_set_profile(device, accel_profile); +- libinput_device_config_accel_set_speed(device, accel_speed); +- } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +-- +2.50.0 + diff --git a/dwl/dwl-patches/patches/movecenter/README.md b/dwl/dwl-patches/patches/movecenter/README.md new file mode 100644 index 0000000..aff8f7d --- /dev/null +++ b/dwl/dwl-patches/patches/movecenter/README.md @@ -0,0 +1,28 @@ +### Description + +> This patch is no longer being maintained by me [wochap](https://codeberg.org/wochap), since I'm now using a different patch specific to my use case: https://codeberg.org/wochap/dwl/src/branch/v0.6-c/betterfloat/betterfloat-diff.patch. + +This patch provides a keybinding to center the focused floating window. + +Press MODKEY + x to center the focused floating window. + +It does NOT center windows that are not floating. + +The variable `respect_monitor_reserved_area` allows the user to choose whether to center relative to the monitor or relative to the window area. + +
+Explanation of respect_monitor_reserved_area: +
+The "Monitor area" refers to the space enclosed by the green rectangle, while the "Window area" refers to the space enclosed by the red rectangle.
+
+
+
+ +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6/movecenter) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/commit/b1ca929ee645cd3e175f198e250448b54624acd6/patches/movecenter/movecenter.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/187d7f511572457750fcf6e42c99cdc7befe05e7/patches/movecenter/movecenter.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) + diff --git a/dwl/dwl-patches/patches/movecenter/movecenter.patch b/dwl/dwl-patches/patches/movecenter/movecenter.patch new file mode 100644 index 0000000..f96bd36 --- /dev/null +++ b/dwl/dwl-patches/patches/movecenter/movecenter.patch @@ -0,0 +1,82 @@ +From bc5206882c71b32198dae5f1c85601a863a7c0a9 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 31 Jul 2024 07:43:10 -0500 +Subject: [PATCH] implement movecenter fn + +--- + config.def.h | 2 ++ + dwl.c | 31 +++++++++++++++++++++++++++++++ + 2 files changed, 33 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..f5225d9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -142,6 +143,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_x, movecenter, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..791e598 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -336,6 +336,8 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void _movecenter(Client *c, int interact); ++static void movecenter(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2683,6 +2685,35 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++_movecenter(Client *c, int interact) ++{ ++ struct wlr_box b; ++ ++ if (!c || !c->mon) { ++ return; ++ } ++ ++ if (!c->isfloating) { ++ return; ++ } ++ ++ b = respect_monitor_reserved_area ? c->mon->w : c->mon->m; ++ resize(c, (struct wlr_box){ ++ .x = (b.width - c->geom.width) / 2 + b.x, ++ .y = (b.height - c->geom.height) / 2 + b.y, ++ .width = c->geom.width, ++ .height = c->geom.height, ++ }, interact); ++} ++ ++void ++movecenter(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ _movecenter(c, 1); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/moveresizekb/README.md b/dwl/dwl-patches/patches/moveresizekb/README.md new file mode 100644 index 0000000..0c53caf --- /dev/null +++ b/dwl/dwl-patches/patches/moveresizekb/README.md @@ -0,0 +1,20 @@ +### Description +This allows the user to change size and placement of floating windows using only the keyboard, default keybindings: + +| Keybinding | Action | +| :--- | :--- | +| MODKEY + Up | move 40px up | +| MODKEY + Down | move 40px down | +| MODKEY + Left | move 40px left | +| MODKEY + Right | move 40px right | +| MODKEY + Shift + Up | shrink height 40px | +| MODKEY + Shift + Down | grow height 40px | +| MODKEY + Shift + Left | shrink width 40px | +| MODKEY + Shift + Right | grow width 40px | + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/moveresizekb) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/moveresizekb/moveresizekb.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/moveresizekb/moveresizekb.patch b/dwl/dwl-patches/patches/moveresizekb/moveresizekb.patch new file mode 100644 index 0000000..aeb1e5e --- /dev/null +++ b/dwl/dwl-patches/patches/moveresizekb/moveresizekb.patch @@ -0,0 +1,70 @@ +From c8af428f964679089599e4ffbe7d28d08a4e875f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Tue, 5 Mar 2024 23:42:55 -0500 +Subject: [PATCH] implement keybindings to move and resize focused floating + window + +--- + config.def.h | 8 ++++++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 27 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..d0570b8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -135,6 +135,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 40, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, -40, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Right, moveresizekb, {.v = (int []){ 40, 0, 0, 0 }}}, ++ { MODKEY, XKB_KEY_Left, moveresizekb, {.v = (int []){ -40, 0, 0, 0 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, moveresizekb, {.v = (int []){ 0, 0, 0, 40 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, moveresizekb, {.v = (int []){ 0, 0, 0, -40 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, moveresizekb, {.v = (int []){ 0, 0, 40, 0 }}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, moveresizekb, {.v = (int []){ 0, 0, -40, 0 }}}, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index ef27a1d..251472b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -313,6 +313,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void moveresizekb(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2454,6 +2455,24 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++moveresizekb(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ Monitor *m = selmon; ++ ++ if(!(m && arg && arg->v && c && c->isfloating)) { ++ return; ++ } ++ ++ resize(c, (struct wlr_box){ ++ .x = c->geom.x + ((int *)arg->v)[0], ++ .y = c->geom.y + ((int *)arg->v)[1], ++ .width = c->geom.width + ((int *)arg->v)[2], ++ .height = c->geom.height + ((int *)arg->v)[3], ++ }, 1); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.42.0 + diff --git a/dwl/dwl-patches/patches/movestack/README.md b/dwl/dwl-patches/patches/movestack/README.md new file mode 100644 index 0000000..f50bf68 --- /dev/null +++ b/dwl/dwl-patches/patches/movestack/README.md @@ -0,0 +1,13 @@ +### Description +Allows you to move a window up and down the stack. + +### Download +- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/movestack) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/movestack/movestack.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [sam-barr](https://github.com/ss7m) +- [Dmitry Zakharchenko](https://github.com/dm1tz) +- [Abanoub8](https://github.com/Abanoub8) +- [Nikita Ivanov](https://github.com/NikitaIvanovV) diff --git a/dwl/dwl-patches/patches/movestack/movestack.patch b/dwl/dwl-patches/patches/movestack/movestack.patch new file mode 100644 index 0000000..2543c66 --- /dev/null +++ b/dwl/dwl-patches/patches/movestack/movestack.patch @@ -0,0 +1,87 @@ +From 08230817bd3926e29d9897657eb1852cb27d461f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 23:21:19 +0100 +Subject: [PATCH] Allows you to move a window up and down the stack + +--- + config.def.h | 2 ++ + dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..2c129f2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, movestack, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index def2562..045d6fa 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -303,6 +303,7 @@ static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); ++static void movestack(const Arg *arg); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +@@ -1786,6 +1787,48 @@ monocle(Monitor *m) + wlr_scene_node_raise_to_top(&c->scene->node); + } + ++void ++movestack(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ ++ if (!sel) { ++ return; ++ } ++ ++ if (wl_list_length(&clients) <= 1) { ++ return; ++ } ++ ++ if (arg->i > 0) { ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ } else { ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ /* backup one client */ ++ c = wl_container_of(c->link.prev, c, link); ++ } ++ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ arrange(selmon); ++} ++ + void + motionabsolute(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/namedscratchpads/README.md b/dwl/dwl-patches/patches/namedscratchpads/README.md new file mode 100644 index 0000000..e436583 --- /dev/null +++ b/dwl/dwl-patches/patches/namedscratchpads/README.md @@ -0,0 +1,19 @@ +### Description +Allows for the creation of multiple scratchpad windows, each assigned to a different keybinding. In simple terms, it enables 'run or raise' functionality + +This patch adds the following functions: + +* `togglescratch`: simply toggles the scratchpad window +* `focusortogglescratch`: change the focus to the scratchpad window if it is visible and toggles it if it is already in focus +* `focusortogglematchingscratch`: similar to `focusortogglescratch` but also closes all other scratchpad windows + +If you don't assign keybindings to any of the above functions and so get a compiler warning about them not being used, just remove them from your dwl branch to stop the warning. + +### Download +- [git branch (v0.6)](https://codeberg.org/bencc/dwl/src/branch/namedscratchpads) +- [2024-07-13 (v0.6)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/namedscratchpads/namedscratchpads.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [wochap](https://codeberg.org/wochap) +- [Louis-Michel Raynauld](https://github.com/loumray) diff --git a/dwl/dwl-patches/patches/namedscratchpads/namedscratchpads.patch b/dwl/dwl-patches/patches/namedscratchpads/namedscratchpads.patch new file mode 100644 index 0000000..7a0a257 --- /dev/null +++ b/dwl/dwl-patches/patches/namedscratchpads/namedscratchpads.patch @@ -0,0 +1,278 @@ +From bd83d56b3a268112b029961ad6ff4232b2d6f00d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 16:35:05 +1000 +Subject: [PATCH 1/2] namedscratchpads + +--- + config.def.h | 11 ++++++++--- + dwl.c | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 47 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..36a691a9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor scratchkey */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, -1, 0 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1, 0 }, /* Start on ONLY tag "9" */ ++ { NULL, "scratchpad", 0, 1, -1, 's' }, + }; + + /* layout(s) */ +@@ -122,11 +123,15 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; + ++/* named scratchpads - First arg only serves to match against key in rules*/ ++static const char *scratchpadcmd[] = { "s", "alacritty", "-t", "scratchpad", NULL }; ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_grave, togglescratch, {.v = scratchpadcmd } }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index dc0437e0..eb0eb775 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ char scratchkey; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -230,6 +231,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ const char scratchkey; + } Rule; + + typedef struct { +@@ -330,12 +332,14 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void spawnscratch(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + 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 togglefullscreen(const Arg *arg); ++static void togglescratch(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -456,6 +460,7 @@ applyrules(Client *c) + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); ++ c->scratchkey = 0; + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) +@@ -465,6 +470,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->scratchkey = r->scratchkey; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -2603,6 +2609,16 @@ spawn(const Arg *arg) + } + } + ++void spawnscratch(const Arg *arg) ++{ ++ if (fork() == 0) { ++ dup2(STDERR_FILENO, STDOUT_FILENO); ++ setsid(); ++ execvp(((char **)arg->v)[1], ((char **)arg->v)+1); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[1]); ++ } ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +@@ -2686,6 +2702,29 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglescratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ ++ /* search for first window that matches the scratchkey */ ++ wl_list_for_each(c, &clients, link) ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ found = 1; ++ break; ++ } ++ ++ if (found) { ++ c->tags = VISIBLEON(c, selmon) ? 0 : selmon->tagset[selmon->seltags]; ++ ++ focusclient(c->tags == 0 ? focustop(selmon) : c, 1); ++ arrange(selmon); ++ } else{ ++ spawnscratch(arg); ++ } ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + + +From 4963c34b4958fba9d53a23a1c9929d554a4e8b3d Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 13 Jul 2024 14:50:45 +1000 +Subject: [PATCH 2/2] namedscratchpads: focusortoggle functions + +--- + config.def.h | 2 ++ + dwl.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 89 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 36a691a9..21bb66bb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -132,6 +132,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_grave, togglescratch, {.v = scratchpadcmd } }, ++ // { MODKEY, XKB_KEY_grave, focusortogglescratch, {.v = scratchpadcmd } }, ++ // { MODKEY, XKB_KEY_grave, focusortogglematchingscratch, {.v = scratchpadcmd } }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index eb0eb775..d7c5552f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -286,6 +286,8 @@ static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); ++static void focusortogglematchingscratch(const Arg *arg); ++static void focusortogglescratch(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); +@@ -1405,6 +1407,91 @@ focusmon(const Arg *arg) + focusclient(focustop(selmon), 1); + } + ++void ++focusortogglematchingscratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ unsigned int hide = 0; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->scratchkey == 0) { ++ continue; ++ } ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ if (VISIBLEON(c, selmon)) { ++ if (found == 1) { ++ if (hide == 1) { ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ } ++ continue; ++ } ++ if (focustop(selmon) == c) { ++ // hide ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ hide = 1; ++ } else { ++ // focus ++ focusclient(c, 1); ++ } ++ } else { ++ // show ++ c->tags = selmon->tagset[selmon->seltags]; ++ // focus ++ focusclient(c, 1); ++ } ++ found = 1; ++ continue; ++ } ++ if (VISIBLEON(c, selmon)) { ++ // hide ++ c->tags = 0; ++ } ++ } ++ ++ if (found) { ++ arrange(selmon); ++ } else { ++ spawnscratch(arg); ++ } ++} ++ ++void ++focusortogglescratch(const Arg *arg) ++{ ++ Client *c; ++ unsigned int found = 0; ++ ++ /* search for first window that matches the scratchkey */ ++ wl_list_for_each(c, &clients, link) ++ if (c->scratchkey == ((char**)arg->v)[0][0]) { ++ found = 1; ++ break; ++ } ++ ++ if (found) { ++ if (VISIBLEON(c, selmon)) { ++ if (focustop(selmon) == c) { ++ // hide ++ c->tags = 0; ++ focusclient(focustop(selmon), 1); ++ } else { ++ // focus ++ focusclient(c, 1); ++ } ++ } else { ++ // show ++ c->tags = selmon->tagset[selmon->seltags]; ++ focusclient(c, 1); ++ } ++ arrange(selmon); ++ } else{ ++ spawnscratch(arg); ++ } ++} ++ + void + focusstack(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/nextlayout/README.md b/dwl/dwl-patches/patches/nextlayout/README.md new file mode 100644 index 0000000..ea1d4a6 --- /dev/null +++ b/dwl/dwl-patches/patches/nextlayout/README.md @@ -0,0 +1,9 @@ +### Description +Change the current layout to the next available one. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/nextlayout/nextlayout.patch) + +### Authors +- [sewn](/sewn) + diff --git a/dwl/dwl-patches/patches/nextlayout/nextlayout.patch b/dwl/dwl-patches/patches/nextlayout/nextlayout.patch new file mode 100644 index 0000000..11f4dc7 --- /dev/null +++ b/dwl/dwl-patches/patches/nextlayout/nextlayout.patch @@ -0,0 +1,65 @@ +From 7d8cfa63681830a3af4512799b8260f8249bc514 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sun, 8 Sep 2024 22:49:33 +0300 +Subject: [PATCH] add feature to switch to next available layout + +ported from suckless cyclelayouts to be slightly more useful +https://dwm.suckless.org/patches/cyclelayouts/ +--- + config.def.h | 2 ++ + dwl.c | 12 ++++++++++++ + 2 files changed, 14 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..f88a615 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { NULL, NULL }, /* terminate */ + }; + + /* monitors */ +@@ -140,6 +141,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, ++ { MODKEY, XKB_KEY_n, nextlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..a66d9d9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -308,6 +308,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static void nextlayout(const Arg *arg); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -1927,6 +1928,17 @@ moveresize(const Arg *arg) + } + } + ++void ++nextlayout(const Arg *arg) ++{ ++ Layout *l; ++ for(l = (Layout *)layouts; l != selmon->lt[selmon->sellt]; l++); ++ if(l->symbol && (l + 1)->symbol) ++ setlayout(&((Arg) { .v = (l + 1) })); ++ else ++ setlayout(&((Arg) { .v = layouts })); ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/numlock-capslock/README.md b/dwl/dwl-patches/patches/numlock-capslock/README.md new file mode 100644 index 0000000..54a4f34 --- /dev/null +++ b/dwl/dwl-patches/patches/numlock-capslock/README.md @@ -0,0 +1,10 @@ +### Description +Allows activating numlock or capslock at startup. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/numlock-capslock) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/numlock-capslock/numlock-capslock.patch) +- [numlock-capslock.patch](/dwl/dwl-patches/raw/branch/main/patches/numlock-capslock/numlock-capslock-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch b/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch new file mode 100644 index 0000000..7b30be3 --- /dev/null +++ b/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch @@ -0,0 +1,84 @@ +From cbacfb5031b91bc6677b0fb7c07dbe79cc2e0177 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 4 Apr 2021 19:56:09 -0500 +Subject: [PATCH] add option to enable numlock/capslock +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 4 ++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..21dc6201 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,10 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* numlock and capslock */ ++static const int numlock = 1; ++static const int capslock = 0; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index a2711f67..a11f0bcf 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -360,6 +361,7 @@ static void zoom(const Arg *arg); + static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; ++static uint32_t locked_mods = 0; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wl_event_loop *event_loop; +@@ -877,6 +879,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + ++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); ++ + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); + } +@@ -898,6 +902,21 @@ createkeyboardgroup(void) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); ++ if (numlock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (capslock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (locked_mods) ++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0); ++ + xkb_keymap_unref(keymap); + xkb_context_unref(context); + +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock.patch b/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock.patch new file mode 100644 index 0000000..adf9f38 --- /dev/null +++ b/dwl/dwl-patches/patches/numlock-capslock/numlock-capslock.patch @@ -0,0 +1,84 @@ +From ec5dbcd9f4629549d3d14b1791305a42479a935f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Sun, 4 Apr 2021 19:56:09 -0500 +Subject: [PATCH] add option to enable numlock/capslock +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 4 ++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..21dc6201 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -60,6 +60,10 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* numlock and capslock */ ++static const int numlock = 1; ++static const int capslock = 0; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index ad21e1ba..d0059ec8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -355,6 +356,7 @@ static void zoom(const Arg *arg); + /* variables */ + static pid_t child_pid = -1; + static int locked; ++static uint32_t locked_mods = 0; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wl_event_loop *event_loop; +@@ -936,6 +938,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + ++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); ++ + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); + } +@@ -957,6 +961,21 @@ createkeyboardgroup(void) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); ++ if (numlock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (capslock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (locked_mods) ++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0); ++ + xkb_keymap_unref(keymap); + xkb_context_unref(context); + +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/passthrough/README.md b/dwl/dwl-patches/patches/passthrough/README.md new file mode 100644 index 0000000..0380951 --- /dev/null +++ b/dwl/dwl-patches/patches/passthrough/README.md @@ -0,0 +1,13 @@ +### Description +allows pausing keybind handling + +also allows for bitcarrying-esque control of nested instances + +default keybind is Ctrl+Logo+Alt+Shift+Esc, can be customized in config.h + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/passthrough) +- [2024-06-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/passthrough/passthrough.patch) +- [2024-06-22](https://codeberg.org/dwl/dwl-patches/raw/commit/3f44fb23d8cb6c7d700f41525dc00493e392083c/patches/passthrough/passthrough.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/passthrough/passthrough.patch b/dwl/dwl-patches/patches/passthrough/passthrough.patch new file mode 100644 index 0000000..d87f00e --- /dev/null +++ b/dwl/dwl-patches/patches/passthrough/passthrough.patch @@ -0,0 +1,82 @@ +From cd67c8386b0188daa15348c1d0d99187a556e461 Mon Sep 17 00:00:00 2001 +From: choc +Date: Mon, 2 Jan 2023 13:00:29 +0800 +Subject: [PATCH] passthrough: allow pausing keybind handling + +also allows for bitcarrying-esque control of nested instances +--- + config.def.h | 4 ++++ + dwl.c | 14 ++++++++++++++ + 2 files changed, 18 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 646a3d6..2d14e2a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,9 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* passthrough */ ++static int passthrough = 0; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: */ +@@ -156,6 +159,7 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), ++ { WLR_MODIFIER_ALT|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, XKB_KEY_Escape, togglepassthrough, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ +diff --git a/dwl.c b/dwl.c +index 9fb50a7..a1c65b4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -339,6 +339,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepassthrough(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -620,6 +621,11 @@ buttonpress(struct wl_listener *listener, void *data) + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && + event->button == b->button && b->func) { ++ if (passthrough) { ++ if (b->func != togglepassthrough) continue; ++ b->func(&b->arg); ++ break; ++ } + b->func(&b->arg); + return; + } +@@ -1509,6 +1515,8 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { ++ if (passthrough && k->func != togglepassthrough) ++ continue; + k->func(&k->arg); + return 1; + } +@@ -2677,6 +2685,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepassthrough(const Arg *arg) ++{ ++ passthrough = !passthrough; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/perinputconfig/README.md b/dwl/dwl-patches/patches/perinputconfig/README.md new file mode 100644 index 0000000..ab1602c --- /dev/null +++ b/dwl/dwl-patches/patches/perinputconfig/README.md @@ -0,0 +1,12 @@ +### Description +Replace the singular keyboard and pointer input configuration with an array allowing to set different variables matching by name. + +Tip to find the names: Grep for `device_name` and add a line after it to print to stdout. Then run EX: `dwl > /tmp/print_device_names.log`, exit dwl, and should see the names. + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_perinputconfig) +- [2024-06-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/perinputconfig/perinputconfig.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/perinputconfig/perinputconfig-v0.5.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) diff --git a/dwl/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch b/dwl/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch new file mode 100644 index 0000000..25a2a92 --- /dev/null +++ b/dwl/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch @@ -0,0 +1,173 @@ +From 9388faea3c4648aa99c01b9e4ce9287237b28b38 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Mon, 1 Apr 2024 21:23:39 +0100 +Subject: [PATCH] Backport perinputconfig to v0.5 + +- Array replaced singular variables for configuration +- Only applies to enable-state, acceleration profile, and speed +- Like EX: Rules, requires NULL/default set at the end +- Keyboards can now also set by name +--- + config.def.h | 37 +++++++++++++++++++++++-------------- + dwl.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 69 insertions(+), 17 deletions(-) + +diff --git a/config.def.h b/config.def.h +index db0babc..861a937 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -46,12 +46,13 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ ++/* NOTE: Always include a fallback rule at the end (name as NULL) */ ++static const KeyboardRule kbrules[] = { ++ /* name rules model layout variant options */ + /* example: +- .options = "ctrl:nocaps", ++ { "keyboard", NULL, NULL, "us,de", NULL, "ctrl:nocaps" }, + */ +- .options = NULL, ++ { NULL, NULL, NULL, NULL, NULL, NULL }, + }; + + static const int repeat_rate = 25; +@@ -81,23 +82,31 @@ LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER + static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + /* You can choose between: ++LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle ++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; ++ ++/* ++send_events_mode: You can choose between: + LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +-*/ +-static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +-/* You can choose between: ++accel_profile: You can choose between: + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE ++ ++NOTE: Always include a fallback rule at the end (name as NULL) + */ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; +-/* You can choose between: +-LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +-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 InputRule inputrules[] = { ++ /* name send_events_mode accel_profile accel_speed*/ ++ /* examples: ++ { "SynPS/2 Synaptics TouchPad", LIBINPUT_CONFIG_SEND_EVENTS_DISABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++ { "TPPS/2 IBM TrackPoint", LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0.0 }, ++ */ ++ { NULL, LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++}; + + /* 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 ef27a1d..a35f480 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -226,6 +226,22 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *name; ++ uint32_t send_events_mode; ++ enum libinput_config_accel_profile accel_profile; ++ double accel_speed; ++} InputRule; ++ ++typedef struct { ++ const char *name; ++ const char *rules; ++ const char *model; ++ const char *layout; ++ const char *variant; ++ const char *options; ++} KeyboardRule; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -766,11 +782,31 @@ createidleinhibitor(struct wl_listener *listener, void *data) + void + createkeyboard(struct wlr_keyboard *keyboard) + { ++ struct xkb_rule_names xkb_rules; ++ struct libinput_device *libinput_device = NULL; + struct xkb_context *context; + struct xkb_keymap *keymap; ++ const KeyboardRule *krule = NULL; ++ const char *device_name = ""; + Keyboard *kb = keyboard->data = ecalloc(1, sizeof(*kb)); + kb->wlr_keyboard = keyboard; + ++ if (wlr_input_device_is_libinput(&keyboard->base) ++ && (libinput_device = wlr_libinput_get_device_handle(&keyboard->base))) { ++ device_name = libinput_device_get_name(libinput_device); ++ } ++ for (krule = kbrules; krule < END(kbrules); krule++) { ++ if (!krule->name || strstr(device_name, krule->name)) ++ break; ++ } ++ if (krule) { ++ xkb_rules.rules = krule->rules; ++ xkb_rules.model = krule->model; ++ xkb_rules.layout = krule->layout; ++ xkb_rules.variant = krule->variant; ++ xkb_rules.options = krule->options; ++ } ++ + /* Prepare an XKB keymap and assign it to the keyboard. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + keymap = xkb_keymap_new_from_names(context, &xkb_rules, +@@ -989,10 +1025,17 @@ createnotify(struct wl_listener *listener, void *data) + void + createpointer(struct wlr_pointer *pointer) + { ++ const InputRule *irule; + if (wlr_input_device_is_libinput(&pointer->base)) { + struct libinput_device *libinput_device = (struct libinput_device*) + wlr_libinput_get_device_handle(&pointer->base); + ++ const char *device_name = libinput_device_get_name(libinput_device); ++ for (irule = inputrules; irule < END(inputrules); irule++) { ++ if (!irule->name || strstr(device_name, irule->name)) ++ break; ++ } ++ + if (libinput_device_config_tap_get_finger_count(libinput_device)) { + libinput_device_config_tap_set_enabled(libinput_device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(libinput_device, tap_and_drag); +@@ -1019,11 +1062,11 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_click_set_method (libinput_device, click_method); + + if (libinput_device_config_send_events_get_modes(libinput_device)) +- libinput_device_config_send_events_set_mode(libinput_device, send_events_mode); ++ libinput_device_config_send_events_set_mode(libinput_device, irule->send_events_mode); + + if (libinput_device_config_accel_is_available(libinput_device)) { +- libinput_device_config_accel_set_profile(libinput_device, accel_profile); +- libinput_device_config_accel_set_speed(libinput_device, accel_speed); ++ libinput_device_config_accel_set_profile(libinput_device, irule->accel_profile); ++ libinput_device_config_accel_set_speed(libinput_device, irule->accel_speed); + } + } + +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/perinputconfig/perinputconfig.patch b/dwl/dwl-patches/patches/perinputconfig/perinputconfig.patch new file mode 100644 index 0000000..dae4bc8 --- /dev/null +++ b/dwl/dwl-patches/patches/perinputconfig/perinputconfig.patch @@ -0,0 +1,295 @@ +From c268707811fb5d8244115f23a0430f024e4e11a9 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 8 Jun 2024 11:30:34 +0100 +Subject: [PATCH] perinputconfig - 2024-06-08 Update + +* Array replaced singular variables for configuration +* Only applies to enable-state, acceleration profile, and speed +* Like EX: Rules, requires NULL/default set at the end +* Keyboards can now also set by name +--- + config.def.h | 36 +++++++++++++--------- + dwl.c | 87 +++++++++++++++++++++++++++++++++++++++++++--------- + 2 files changed, 95 insertions(+), 28 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4..c733137 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -49,12 +49,13 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ ++/* NOTE: Always include a fallback rule at the end (name as NULL) */ ++static const KeyboardRule kbrules[] = { ++ /* name rules model layout variant options */ + /* example: +- .options = "ctrl:nocaps", ++ { "keyboard", NULL, NULL, "us,de", NULL, "ctrl:nocaps" }, + */ +- .options = NULL, ++ { NULL, NULL, NULL, NULL, NULL, NULL }, + }; + + static const int repeat_rate = 25; +@@ -84,24 +85,31 @@ LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER + static const enum libinput_config_click_method click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + /* You can choose between: ++LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle ++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; ++ ++/* ++send_events_mode: You can choose between: + LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED + LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE +-*/ +-static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + +-/* You can choose between: ++accel_profile: You can choose between: + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE +-*/ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; + +-/* You can choose between: +-LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +-LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right ++NOTE: Always include a fallback rule at the end (name as NULL) + */ +-static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; ++static const InputRule inputrules[] = { ++ /* name send_events_mode accel_profile accel_speed*/ ++ /* examples: ++ { "SynPS/2 Synaptics TouchPad", LIBINPUT_CONFIG_SEND_EVENTS_DISABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++ { "TPPS/2 IBM TrackPoint", LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE, 0.0 }, ++ */ ++ { NULL, LIBINPUT_CONFIG_SEND_EVENTS_ENABLED, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT, 0.0 }, ++}; + + /* 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 6f041a0..0673a05 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -240,6 +240,22 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ const char *name; ++ uint32_t send_events_mode; ++ enum libinput_config_accel_profile accel_profile; ++ double accel_speed; ++} InputRule; ++ ++typedef struct { ++ const char *name; ++ const char *rules; ++ const char *model; ++ const char *layout; ++ const char *variant; ++ const char *options; ++} KeyboardRule; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -259,7 +275,7 @@ static void commitnotify(struct wl_listener *listener, void *data); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +-static KeyboardGroup *createkeyboardgroup(void); ++static KeyboardGroup *createkeyboardgroup(struct xkb_rule_names *new_xkb_rules); + static void createlayersurface(struct wl_listener *listener, void *data); + static void createlocksurface(struct wl_listener *listener, void *data); + static void createmon(struct wl_listener *listener, void *data); +@@ -396,7 +412,7 @@ static struct wlr_session_lock_v1 *cur_lock; + static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; +-static KeyboardGroup *kb_group; ++static struct wl_list kb_groups; + static struct wlr_surface *held_grab; + static unsigned int cursor_mode; + static Client *grabc; +@@ -671,6 +687,8 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ KeyboardGroup *kb_group; ++ + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -683,7 +701,8 @@ cleanup(void) + wlr_xcursor_manager_destroy(cursor_mgr); + wlr_output_layout_destroy(output_layout); + +- destroykeyboardgroup(&kb_group->destroy, NULL); ++ wl_list_for_each(kb_group, &kb_groups, link) ++ destroykeyboardgroup(&kb_group->destroy, NULL); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) +@@ -803,6 +822,30 @@ createidleinhibitor(struct wl_listener *listener, void *data) + void + createkeyboard(struct wlr_keyboard *keyboard) + { ++ KeyboardGroup *kb_group; ++ const char *device_name = ""; ++ const KeyboardRule *krule = NULL; ++ struct libinput_device *device = NULL; ++ ++ if (wlr_input_device_is_libinput(&keyboard->base) ++ && (device = wlr_libinput_get_device_handle(&keyboard->base))) { ++ device_name = libinput_device_get_name(device); ++ } ++ for (krule = kbrules; krule < END(kbrules); krule++) { ++ if (!krule->name || strstr(device_name, krule->name)) ++ break; ++ } ++ if (krule) { ++ struct xkb_rule_names xkb_rules; ++ xkb_rules.rules = krule->rules; ++ xkb_rules.model = krule->model; ++ xkb_rules.layout = krule->layout; ++ xkb_rules.variant = krule->variant; ++ xkb_rules.options = krule->options; ++ kb_group = createkeyboardgroup(&xkb_rules); ++ } else ++ wl_list_for_each(kb_group, &kb_groups, link); ++ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + +@@ -811,11 +854,16 @@ createkeyboard(struct wlr_keyboard *keyboard) + } + + KeyboardGroup * +-createkeyboardgroup(void) ++createkeyboardgroup(struct xkb_rule_names *new_xkb_rules) + { + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; ++ struct xkb_rule_names xkb_rules; ++ ++ memset(&xkb_rules, 0, sizeof(struct xkb_rule_names)); ++ if (new_xkb_rules) ++ xkb_rules = *new_xkb_rules; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; +@@ -845,6 +893,9 @@ createkeyboardgroup(void) + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); ++ ++ wl_list_init(&group->destroy.link); ++ wl_list_insert(&kb_groups, &group->link); + return group; + } + +@@ -1042,9 +1093,15 @@ createnotify(struct wl_listener *listener, void *data) + void + createpointer(struct wlr_pointer *pointer) + { ++ const InputRule *irule; + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { ++ const char *device_name = libinput_device_get_name(device); ++ for (irule = inputrules; irule < END(inputrules); irule++) { ++ if (!irule->name || strstr(device_name, irule->name)) ++ break; ++ } + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); +@@ -1072,11 +1129,11 @@ createpointer(struct wlr_pointer *pointer) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) +- libinput_device_config_send_events_set_mode(device, send_events_mode); ++ libinput_device_config_send_events_set_mode(device, irule->send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { +- libinput_device_config_accel_set_profile(device, accel_profile); +- libinput_device_config_accel_set_speed(device, accel_speed); ++ libinput_device_config_accel_set_profile(device, irule->accel_profile); ++ libinput_device_config_accel_set_speed(device, irule->accel_speed); + } + } + +@@ -1277,7 +1334,6 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); +- free(group); + } + + Monitor * +@@ -1467,6 +1523,7 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ KeyboardGroup *group; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +@@ -1485,8 +1542,11 @@ inputdevice(struct wl_listener *listener, void *data) + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; +- if (!wl_list_empty(&kb_group->wlr_group->devices)) +- caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ wl_list_for_each(group, &kb_groups, link) ++ if (!wl_list_empty(&group->wlr_group->devices)) { ++ caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ break; ++ } + wlr_seat_set_capabilities(seat, caps); + } + +@@ -2431,6 +2491,7 @@ setup(void) + */ + wl_list_init(&clients); + wl_list_init(&fstack); ++ wl_list_init(&kb_groups); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_surface, createnotify); +@@ -2514,8 +2575,7 @@ setup(void) + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +- kb_group = createkeyboardgroup(); +- wl_list_init(&kb_group->destroy.link); ++ createkeyboardgroup(NULL); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); +@@ -2857,10 +2917,9 @@ virtualkeyboard(struct wl_listener *listener, void *data) + { + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ +- KeyboardGroup *group = createkeyboardgroup(); ++ KeyboardGroup *group = createkeyboardgroup(NULL); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); +- LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/pertag/README.md b/dwl/dwl-patches/patches/pertag/README.md new file mode 100644 index 0000000..7473da0 --- /dev/null +++ b/dwl/dwl-patches/patches/pertag/README.md @@ -0,0 +1,12 @@ +### Description +Makes layout, mwfact and nmaster individual for every tag. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6/pertag) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/pertag/pertag.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/bf098459219e7a473d8edb4c0435aeb6a4b82e38/pertag/pertag.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/3f9a58cde9e3aa02991b3e5a22d371b153cb1459/pertag/pertag.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Guido Cella](https://github.com/guidocella) diff --git a/dwl/dwl-patches/patches/pertag/pertag.patch b/dwl/dwl-patches/patches/pertag/pertag.patch new file mode 100644 index 0000000..971732a --- /dev/null +++ b/dwl/dwl-patches/patches/pertag/pertag.patch @@ -0,0 +1,170 @@ +From d3b551ffe3ec85e16341962e322150b81af6722f Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 31 Jul 2024 08:27:26 -0500 +Subject: [PATCH] makes layout, mwfact and nmaster individual for every tag + +inspiration: https://github.com/djpohly/dwl/wiki/pertag +--- + dwl.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 70 insertions(+), 5 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 145fd01..2f364bc 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -102,6 +102,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct Pertag Pertag; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -199,6 +200,7 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ Pertag *pertag; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -427,6 +429,14 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++struct Pertag { ++ unsigned int curtag, prevtag; /* current and previous tag */ ++ int nmasters[TAGCOUNT + 1]; /* number of windows in master area */ ++ float mfacts[TAGCOUNT + 1]; /* mfacts per tag */ ++ unsigned int sellts[TAGCOUNT + 1]; /* selected layouts */ ++ const Layout *ltidxs[TAGCOUNT + 1][2]; /* matrix of tags and layouts indexes */ ++}; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -712,6 +722,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ free(m->pertag); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -983,6 +994,18 @@ createmon(struct wl_listener *listener, void *data) + wl_list_insert(&mons, &m->link); + printstatus(); + ++ m->pertag = calloc(1, sizeof(Pertag)); ++ m->pertag->curtag = m->pertag->prevtag = 1; ++ ++ for (i = 0; i <= TAGCOUNT; i++) { ++ m->pertag->nmasters[i] = m->nmaster; ++ m->pertag->mfacts[i] = m->mfact; ++ ++ m->pertag->ltidxs[i][0] = m->lt[0]; ++ m->pertag->ltidxs[i][1] = m->lt[1]; ++ m->pertag->sellts[i] = m->sellt; ++ } ++ + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +@@ -1472,7 +1495,7 @@ incnmaster(const Arg *arg) + { + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); + } + +@@ -2305,9 +2328,9 @@ setlayout(const Arg *arg) + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +- selmon->sellt ^= 1; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) +- selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2324,7 +2347,7 @@ setmfact(const Arg *arg) + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; +- selmon->mfact = f; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); + } + +@@ -2701,9 +2724,29 @@ void + toggleview(const Arg *arg) + { + uint32_t newtagset; ++ size_t i; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ if (newtagset == (uint32_t)~0) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = 0; ++ } ++ ++ /* test if the user did not select the same tag */ ++ if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ for (i = 0; !(newtagset & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ ++ /* apply settings for this view */ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +@@ -2892,11 +2935,33 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { ++ size_t i, tmptag; ++ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ +- if (arg->ui & TAGMASK) ++ if (arg->ui & ~0) { + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ ++ if (arg->ui == TAGMASK) ++ selmon->pertag->curtag = 0; ++ else { ++ for (i = 0; !(arg->ui & 1 << i); i++) ; ++ selmon->pertag->curtag = i + 1; ++ } ++ } else { ++ tmptag = selmon->pertag->prevtag; ++ selmon->pertag->prevtag = selmon->pertag->curtag; ++ selmon->pertag->curtag = tmptag; ++ } ++ ++ selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; ++ selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; ++ selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; ++ selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; ++ selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; ++ + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/README.md b/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/README.md new file mode 100644 index 0000000..3ca5432 --- /dev/null +++ b/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/README.md @@ -0,0 +1,16 @@ +### Description +Forward the following events to client: +swipe_begin, swipe_update, swipe_end, pinch_begin, pinch_update and pinch_end + +This patch allows you to pinch zoom in Chrome, for example. In combination with the following patches [gestures](https://codeberg.org/dwl/dwl-patches/wiki/gestures) and [shiftview](https://codeberg.org/dwl/dwl-patches/wiki/shiftview), it would allow you to switch workspaces by performing a 3-finger swipe on your touchpad. + + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/pointer-gestures-unstable-v1) +- [2024-07-12](https://codeberg.org/dwl/dwl-patches/raw/commit/05dbce217b676e989b0fc9e0eecf83b386ac9e07/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/2322f3efeae8da44227e0acc760ffd3dea153716/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/c676de59d51e613bd52ac46c77a24b1cac9a61a1/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/fc4146f3068dcd46035a2a11fe9d6109a97ae6d6/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch b/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch new file mode 100644 index 0000000..f76648a --- /dev/null +++ b/dwl/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch @@ -0,0 +1,186 @@ +From be7e98d28fc59aab67026e7d5efdcaeb26029713 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 12 Jul 2024 11:30:17 -0500 +Subject: [PATCH] implement pointer-gestures-unstable-v1 + +--- + dwl.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 136 insertions(+) + +diff --git a/dwl.c b/dwl.c +index dc0437e..e5805b1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -250,6 +251,14 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); ++static void swipe_begin(struct wl_listener *listener, void *data); ++static void swipe_update(struct wl_listener *listener, void *data); ++static void swipe_end(struct wl_listener *listener, void *data); ++static void pinch_begin(struct wl_listener *listener, void *data); ++static void pinch_update(struct wl_listener *listener, void *data); ++static void pinch_end(struct wl_listener *listener, void *data); ++static void hold_begin(struct wl_listener *listener, void *data); ++static void hold_end(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); +@@ -383,6 +392,7 @@ static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; + static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; + static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; + static struct wlr_output_power_manager_v1 *power_mgr; ++static struct wlr_pointer_gestures_v1 *pointer_gestures; + + static struct wlr_pointer_constraints_v1 *pointer_constraints; + static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +@@ -644,6 +654,122 @@ buttonpress(struct wl_listener *listener, void *data) + event->time_msec, event->button, event->state); + } + ++void ++swipe_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_begin_event *event = data; ++ ++ // Forward swipe begin event to client ++ wlr_pointer_gestures_v1_send_swipe_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++swipe_update(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_update_event *event = data; ++ ++ // Forward swipe update event to client ++ wlr_pointer_gestures_v1_send_swipe_update( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->dx, ++ event->dy ++ ); ++} ++ ++void ++swipe_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_swipe_end_event *event = data; ++ ++ // Forward swipe end event to client ++ wlr_pointer_gestures_v1_send_swipe_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ ++void ++pinch_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_begin_event *event = data; ++ ++ // Forward pinch begin event to client ++ wlr_pointer_gestures_v1_send_pinch_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++pinch_update(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_update_event *event = data; ++ ++ // Forward pinch update event to client ++ wlr_pointer_gestures_v1_send_pinch_update( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->dx, ++ event->dy, ++ event->scale, ++ event->rotation ++ ); ++} ++ ++void ++pinch_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_pinch_end_event *event = data; ++ ++ // Forward pinch end event to client ++ wlr_pointer_gestures_v1_send_pinch_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ ++void ++hold_begin(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_hold_begin_event *event = data; ++ ++ // Forward hold begin event to client ++ wlr_pointer_gestures_v1_send_hold_begin( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->fingers ++ ); ++} ++ ++void ++hold_end(struct wl_listener *listener, void *data) ++{ ++ struct wlr_pointer_hold_end_event *event = data; ++ ++ // Forward hold end event to client ++ wlr_pointer_gestures_v1_send_hold_end( ++ pointer_gestures, ++ seat, ++ event->time_msec, ++ event->cancelled ++ ); ++} ++ + void + chvt(const Arg *arg) + { +@@ -2556,6 +2682,16 @@ setup(void) + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + ++ pointer_gestures = wlr_pointer_gestures_v1_create(dpy); ++ LISTEN_STATIC(&cursor->events.swipe_begin, swipe_begin); ++ LISTEN_STATIC(&cursor->events.swipe_update, swipe_update); ++ LISTEN_STATIC(&cursor->events.swipe_end, swipe_end); ++ LISTEN_STATIC(&cursor->events.pinch_begin, pinch_begin); ++ LISTEN_STATIC(&cursor->events.pinch_update, pinch_update); ++ LISTEN_STATIC(&cursor->events.pinch_end, pinch_end); ++ LISTEN_STATIC(&cursor->events.hold_begin, hold_begin); ++ LISTEN_STATIC(&cursor->events.hold_end, hold_end); ++ + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +-- +2.45.1 diff --git a/dwl/dwl-patches/patches/primaryselection/README.md b/dwl/dwl-patches/patches/primaryselection/README.md new file mode 100644 index 0000000..1154452 --- /dev/null +++ b/dwl/dwl-patches/patches/primaryselection/README.md @@ -0,0 +1,10 @@ +### Description +Adds a config option to disable/enable primary selection (middle-click paste). + +### Download +- [git branch](https://codeberg.org/nullsystem/dwl/src/branch/main_primaryselection) +- [2024-04-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/primaryselection/primaryselection.patch) + +### Authors +- [nullsystem](https://codeberg.org/nullsystem) +- [Palanix](https://github.com/PalanixYT) - Previous Primary-Selection patch diff --git a/dwl/dwl-patches/patches/primaryselection/primaryselection.patch b/dwl/dwl-patches/patches/primaryselection/primaryselection.patch new file mode 100644 index 0000000..ab6bb64 --- /dev/null +++ b/dwl/dwl-patches/patches/primaryselection/primaryselection.patch @@ -0,0 +1,50 @@ +From 4fc77fde2f6015564544e029f9905fc1678fcb59 Mon Sep 17 00:00:00 2001 +From: nullsystem +Date: Sat, 6 Apr 2024 14:19:44 +0100 +Subject: [PATCH] primaryselection - disable/enable primary selection + +* Just simply adds a config to disable/enable primary selection +--- + config.def.h | 1 + + dwl.c | 6 ++++-- + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..057e1c3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -66,6 +66,7 @@ static const int natural_scrolling = 0; + static const int disable_while_typing = 1; + static const int left_handed = 0; + static const int middle_button_emulation = 0; ++static const int enable_primary_selection = 0; + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +diff --git a/dwl.c b/dwl.c +index bf763df..7e8d8f2 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2339,7 +2339,8 @@ setup(void) + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); +- wlr_primary_selection_v1_device_manager_create(dpy); ++ if (enable_primary_selection) ++ wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); +@@ -2449,7 +2450,8 @@ setup(void) + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +- LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); ++ if (enable_primary_selection) ++ LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/push/README.md b/dwl/dwl-patches/patches/push/README.md new file mode 100644 index 0000000..5582bfd --- /dev/null +++ b/dwl/dwl-patches/patches/push/README.md @@ -0,0 +1,11 @@ +### Description +Adds functions `pushup` and `pushdown` to move windows within the tiling order. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/push) +- [2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/push/push.patch) +- [push-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/push/push-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Devin J. Pohly](https://github.com/djpohly) diff --git a/dwl/dwl-patches/patches/push/push-0.7.patch b/dwl/dwl-patches/patches/push/push-0.7.patch new file mode 100644 index 0000000..4afad97 --- /dev/null +++ b/dwl/dwl-patches/patches/push/push-0.7.patch @@ -0,0 +1,127 @@ +From 01290daca2b01131c5c022389afd0b593b4707eb Mon Sep 17 00:00:00 2001 +From: "Devin J. Pohly" +Date: Thu, 4 Mar 2021 00:45:50 -0600 +Subject: [PATCH] port dwm "push" patch to dwl +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + Makefile | 2 +- + dwl.c | 2 ++ + push.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + push.h | 4 ++++ + 4 files changed, 70 insertions(+), 1 deletion(-) + create mode 100644 push.c + create mode 100644 push.h + +diff --git a/Makefile b/Makefile +index 3358bae9..87bf3160 100644 +--- a/Makefile ++++ b/Makefile +@@ -19,7 +19,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c client.h config.h config.mk push.h cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +diff --git a/dwl.c b/dwl.c +index a2711f67..c3d78aa3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -427,7 +427,9 @@ static xcb_atom_t netatom[NetLast]; + #endif + + /* configuration, allows nested code to access above variables */ ++#include "push.h" + #include "config.h" ++#include "push.c" + + /* attempt to encapsulate suck into one file */ + #include "client.h" +diff --git a/push.c b/push.c +new file mode 100644 +index 00000000..323c317e +--- /dev/null ++++ b/push.c +@@ -0,0 +1,63 @@ ++static Client * ++nexttiled(Client *sel) { ++ Client *c; ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static Client * ++prevtiled(Client *sel) { ++ Client *c; ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static void ++pushup(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = prevtiled(sel))) { ++ /* attach before c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(c->link.prev, &sel->link); ++ } else { ++ /* move to the end */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(clients.prev, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++static void ++pushdown(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = nexttiled(sel))) { ++ /* attach after c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ } else { ++ /* move to the front */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&clients, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} +diff --git a/push.h b/push.h +new file mode 100644 +index 00000000..59c0f80e +--- /dev/null ++++ b/push.h +@@ -0,0 +1,4 @@ ++static Client *nexttiled(Client *sel); ++static Client *prevtiled(Client *sel); ++static void pushdown(const Arg *arg); ++static void pushup(const Arg *arg); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/push/push.patch b/dwl/dwl-patches/patches/push/push.patch new file mode 100644 index 0000000..5f6cba9 --- /dev/null +++ b/dwl/dwl-patches/patches/push/push.patch @@ -0,0 +1,127 @@ +From 06d86c26da60e4196ec3c5228b04c66ac042a1f8 Mon Sep 17 00:00:00 2001 +From: "Devin J. Pohly" +Date: Thu, 4 Mar 2021 00:45:50 -0600 +Subject: [PATCH] port dwm "push" patch to dwl +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + Makefile | 2 +- + dwl.c | 2 ++ + push.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + push.h | 4 ++++ + 4 files changed, 70 insertions(+), 1 deletion(-) + create mode 100644 push.c + create mode 100644 push.h + +diff --git a/Makefile b/Makefile +index 578194f2..f919a61e 100644 +--- a/Makefile ++++ b/Makefile +@@ -19,7 +19,7 @@ LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl.o: dwl.c client.h config.h config.mk push.h cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ + wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h + util.o: util.c util.h +diff --git a/dwl.c b/dwl.c +index ad21e1ba..69753921 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -450,7 +450,9 @@ static struct wlr_xwayland *xwayland; + #endif + + /* configuration, allows nested code to access above variables */ ++#include "push.h" + #include "config.h" ++#include "push.c" + + /* attempt to encapsulate suck into one file */ + #include "client.h" +diff --git a/push.c b/push.c +new file mode 100644 +index 00000000..323c317e +--- /dev/null ++++ b/push.c +@@ -0,0 +1,63 @@ ++static Client * ++nexttiled(Client *sel) { ++ Client *c; ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static Client * ++prevtiled(Client *sel) { ++ Client *c; ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) ++ break; /* don't wrap */ ++ if (!c->isfloating && VISIBLEON(c, selmon)) ++ return c; ++ } ++ return NULL; ++} ++ ++static void ++pushup(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = prevtiled(sel))) { ++ /* attach before c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(c->link.prev, &sel->link); ++ } else { ++ /* move to the end */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(clients.prev, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++static void ++pushdown(const Arg *arg) { ++ Client *sel = focustop(selmon); ++ Client *c; ++ ++ if(!sel || sel->isfloating) ++ return; ++ if((c = nexttiled(sel))) { ++ /* attach after c */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ } else { ++ /* move to the front */ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&clients, &sel->link); ++ } ++ focusclient(sel, 1); ++ arrange(selmon); ++} +diff --git a/push.h b/push.h +new file mode 100644 +index 00000000..59c0f80e +--- /dev/null ++++ b/push.h +@@ -0,0 +1,4 @@ ++static Client *nexttiled(Client *sel); ++static Client *prevtiled(Client *sel); ++static void pushdown(const Arg *arg); ++static void pushup(const Arg *arg); +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/regexrules/README.md b/dwl/dwl-patches/patches/regexrules/README.md new file mode 100644 index 0000000..8c014b2 --- /dev/null +++ b/dwl/dwl-patches/patches/regexrules/README.md @@ -0,0 +1,19 @@ +### Description +Allows the use of regular expressions for window rules "app_id" and "title" + +```c +static const Rule rules[] = { + // ... + { "kitty-htop", NULL, 1 << 8, 0, -1 }, + { "^kitty$", NULL, 0, 0, -1 }, + // ... +}; +``` + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/regexrules) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/2a6560c167e5c9afc5598ac5431d23d90de8846c/regexrules/regexrules.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/98cba933c9f4099202e54f39acbf17e05bde828a/regexrules/regexrules.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/regexrules/regexrules.patch b/dwl/dwl-patches/patches/regexrules/regexrules.patch new file mode 100644 index 0000000..f7207d3 --- /dev/null +++ b/dwl/dwl-patches/patches/regexrules/regexrules.patch @@ -0,0 +1,76 @@ +From 7fed9449575b1e4f58d519d2f87b7e66e2056125 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 11 Apr 2024 12:45:47 -0500 +Subject: [PATCH] implement regex support in rules for app_id and title Enables + the use of regular expressions for window rules "app_id" and "title" + +--- + config.def.h | 1 + + dwl.c | 19 +++++++++++++++++-- + 2 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8847e58..89f5b60 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -25,6 +25,7 @@ static const Rule rules[] = { + /* examples: */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "^kitty_EXAMPLE$", NULL, 0, 0, -1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index bf763df..fc185af 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -347,6 +348,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static int regex_match(const char *pattern, const char *str); + + /* variables */ + static const char broken[] = "broken"; +@@ -459,8 +461,8 @@ applyrules(Client *c) + title = broken; + + for (r = rules; r < END(rules); r++) { +- if ((!r->title || strstr(title, r->title)) +- && (!r->id || strstr(appid, r->id))) { ++ if ((!r->title || regex_match(r->title, title)) ++ && (!r->id || regex_match(r->id, appid))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; +@@ -2929,6 +2931,19 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++int ++regex_match(const char *pattern, const char *str) { ++ regex_t regex; ++ int reti; ++ if (regcomp(®ex, pattern, REG_EXTENDED) != 0) ++ return 0; ++ reti = regexec(®ex, str, (size_t)0, NULL, 0); ++ regfree(®ex); ++ if (reti == 0) ++ return 1; ++ return 0; ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.2 diff --git a/dwl/dwl-patches/patches/regions/README.md b/dwl/dwl-patches/patches/regions/README.md new file mode 100644 index 0000000..8b5679a --- /dev/null +++ b/dwl/dwl-patches/patches/regions/README.md @@ -0,0 +1,11 @@ +### Description +This patch will allow for a program to be used and have the current window regions on all monitors to be passed to the program as standard input. + +example is `grim -g "$(slurp)"` + +### Download +- [git branch](https://codeberg.org/sewn/dwl/src/branch/regions) +- [2024-02-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/regions/regions.patch) + +### Authors +- [sewn](https://github.com/apprehensions) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/regions/regions.patch b/dwl/dwl-patches/patches/regions/regions.patch new file mode 100644 index 0000000..207c0bd --- /dev/null +++ b/dwl/dwl-patches/patches/regions/regions.patch @@ -0,0 +1,71 @@ +From 9991f8bbf2e379dfca8eb356c03d3d20085255a8 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sun, 23 Jul 2023 08:13:52 +0300 +Subject: [PATCH] pass window regions to given program as stdin + +--- + config.def.h | 1 + + dwl.c | 28 ++++++++++++++++++++++++++++ + 2 files changed, 29 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..79d0236 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -122,6 +122,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_r, regions, SHCMD("grim -g \"$(slurp)\"") }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index fa76db2..9588e36 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -334,6 +334,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void regions(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -2823,6 +2824,33 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++regions(const Arg *arg) ++{ ++ int pipefd[2]; ++ Client *c; ++ Monitor *m; ++ ++ if (pipe(pipefd) == -1) ++ return; ++ if (fork() == 0) { ++ close(pipefd[1]); ++ dup2(pipefd[0], STDIN_FILENO); ++ close(pipefd[0]); ++ setsid(); ++ execvp(((char **)arg->v)[0], (char **)arg->v); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[0]); ++ } ++ ++ close(pipefd[0]); ++ wl_list_for_each(m, &mons, link) ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m)) ++ dprintf(pipefd[1], "%d,%d %dx%d\n", ++ c->geom.x, c->geom.y, c->geom.width, c->geom.height); ++ close(pipefd[1]); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.1 + diff --git a/dwl/dwl-patches/patches/relative-mouse-resize/README.md b/dwl/dwl-patches/patches/relative-mouse-resize/README.md new file mode 100644 index 0000000..ee462ce --- /dev/null +++ b/dwl/dwl-patches/patches/relative-mouse-resize/README.md @@ -0,0 +1,13 @@ +### Description +When resizing windows, the mouse will jump and resize the window in the quadrant that the resize starts at. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/relative-mouse-resize) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/0bd725d0786248e1dfedbe6aa7453edfe736de43/patches/relative-mouse-resize/relative-mouse-resize.patch) +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/655fd2916c1bcaa022ce6dcdfb370051cf64df66/relative-mouse-resize/relative-mouse-resize.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/b828e21717fa584affeb3245359c3ab615759fa4/relative-mouse-resize/relative-mouse-resize.patch) +- [v0.4](https://codeberg.org/schance995/dwl/commit/c9a0c55daeb5c75cc0defa9b82f82eccd13f06c7.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [schance995](https://codeberg.org/schance995) diff --git a/dwl/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch b/dwl/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch new file mode 100644 index 0000000..e1dfc57 --- /dev/null +++ b/dwl/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch @@ -0,0 +1,102 @@ +From 5c75c67fe49e5ab89e4a61dfb2fe74c768477b90 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 11:13:53 -0500 +Subject: [PATCH] implement relative-muse-resize + +--- + dwl.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- + 1 file changed, 59 insertions(+), 7 deletions(-) + +diff --git a/dwl.c b/dwl.c +index dc0437e..ebf9ef1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -401,7 +401,8 @@ static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; + static Client *grabc; +-static int grabcx, grabcy; /* client-relative */ ++static Client initial_grabc; ++static int grabcx, grabcy, grabx, graby, grabcenterx, grabcentery; /* client-relative */ + + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; +@@ -1821,8 +1822,27 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ if (grabcenterx < grabx) { ++ if (grabcentery < graby) { ++ /* bottom-right */ ++ resize(grabc, (struct wlr_box){.x = initial_grabc.geom.x, .y = initial_grabc.geom.y, ++ .width = (int)round(cursor->x) - initial_grabc.geom.x, .height = (int)round(cursor->y) - initial_grabc.geom.y}, 1); ++ } else { ++ /* top-right */ ++ resize(grabc, (struct wlr_box){.x = initial_grabc.geom.x, .y = (int)round(cursor->y), ++ .width = (int)round(cursor->x) - initial_grabc.geom.x, .height = initial_grabc.geom.y + initial_grabc.geom.height - (int)round(cursor->y)}, 1); ++ } ++ } else { ++ if (grabcentery < graby) { ++ /* bottom-left */ ++ resize(grabc, (struct wlr_box){.x = (int)round(cursor->x), .y = initial_grabc.geom.y, ++ .width = initial_grabc.geom.x + initial_grabc.geom.width - (int)round(cursor->x), .height = (int)round(cursor->y) - initial_grabc.geom.y}, 1); ++ } else { ++ /* top-left */ ++ resize(grabc, (struct wlr_box){.x = (int)round(cursor->x), .y = (int)round(cursor->y), ++ .width = initial_grabc.geom.x + initial_grabc.geom.width - (int)round(cursor->x), .height = initial_grabc.geom.y + initial_grabc.geom.height - (int)round(cursor->y)}, 1); ++ } ++ } + return; + } + +@@ -1870,10 +1890,42 @@ moveresize(const Arg *arg) + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ initial_grabc = *grabc; ++ grabx = (int)round(cursor->x); ++ graby = (int)round(cursor->y); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ grabcenterx = grabc->geom.width / 2 + grabc->geom.x; ++ grabcentery = grabc->geom.height / 2 + grabc->geom.y; ++ if (grabcenterx < grabx) { ++ if (grabcentery < graby) { ++ /* bottom-right */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ } else { ++ /* top-right */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "ne-resize"); ++ } ++ } else { ++ if (grabcentery < graby) { ++ /* bottom-left */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "sw-resize"); ++ } else { ++ /* top-left */ ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x, ++ grabc->geom.y); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "nw-resize"); ++ } ++ } + break; + } + } +-- +2.45.1 \ No newline at end of file diff --git a/dwl/dwl-patches/patches/restore-monitor/README.md b/dwl/dwl-patches/patches/restore-monitor/README.md new file mode 100644 index 0000000..8380967 --- /dev/null +++ b/dwl/dwl-patches/patches/restore-monitor/README.md @@ -0,0 +1,8 @@ +### Description +Moves clients to their old output when it is reattached. + +### Download +- [git branch](https://codeberg.org/eyusupov/dwl/src/branch/restore-monitor) +- [2024-04-07](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/restore-monitor/restore-monitor.patch) +### Authors +- [eyusupov](https://codeberg.org/eyusupov) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/restore-monitor/restore-monitor.patch b/dwl/dwl-patches/patches/restore-monitor/restore-monitor.patch new file mode 100644 index 0000000..0d6da98 --- /dev/null +++ b/dwl/dwl-patches/patches/restore-monitor/restore-monitor.patch @@ -0,0 +1,82 @@ +From e42ca1c539437d3098d80983cfe2ad6f938d7a08 Mon Sep 17 00:00:00 2001 +From: Eldar Yusupov +Date: Sun, 17 Mar 2024 19:12:29 +0300 +Subject: [PATCH] Restore correct montior for client when it is reattached + +--- + dwl.c | 24 ++++++++++++++++++++++-- + 1 file changed, 22 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index bf763df..d8d8139 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -107,6 +107,7 @@ typedef struct { + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; ++ char *output; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; +@@ -869,6 +870,7 @@ createmon(struct wl_listener *listener, void *data) + size_t i; + struct wlr_output_state state; + Monitor *m; ++ Client *c; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; +@@ -938,6 +940,13 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); ++ ++ wl_list_for_each(c, &clients, link) { ++ if (strcmp(wlr_output->name, c->output) == 0) { ++ c->mon = m; ++ } ++ } ++ updatemons(NULL, NULL); + } + + void +@@ -1186,6 +1195,7 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } ++ free(c->output); + free(c); + } + +@@ -1618,6 +1628,10 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ c->output = strdup(c->mon->wlr_output->name); ++ if (c->output == NULL) { ++ die("oom"); ++ } + printstatus(); + + unset_fullscreen: +@@ -2565,8 +2579,14 @@ void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) +- setmon(sel, dirtomon(arg->i), 0); ++ if (!sel) ++ return; ++ setmon(sel, dirtomon(arg->i), 0); ++ free(sel->output); ++ sel->output = strdup(sel->mon->wlr_output->name); ++ if (sel->output == NULL) { ++ die("oom"); ++ } + } + + void +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/right/README.md b/dwl/dwl-patches/patches/right/README.md new file mode 100644 index 0000000..7708ded --- /dev/null +++ b/dwl/dwl-patches/patches/right/README.md @@ -0,0 +1,8 @@ +### Description +Put newly connected monitors on the right, like X does. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/right/right.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () diff --git a/dwl/dwl-patches/patches/right/right.patch b/dwl/dwl-patches/patches/right/right.patch new file mode 100644 index 0000000..baf8eeb --- /dev/null +++ b/dwl/dwl-patches/patches/right/right.patch @@ -0,0 +1,50 @@ +From 9d4b0ebb95381525351b05aad6a98f1747ae96e2 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Thu, 21 Jul 2022 21:14:14 +0300 +Subject: [PATCH] extend the display to the right + +--- + dwl.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index d48bf40..fb795e1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -923,7 +923,8 @@ createmon(struct wl_listener *listener, void *data) + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +- Monitor *m; ++ Monitor *om, *m; ++ int max_x = 0, max_x_y = 0, width, height; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; +@@ -967,6 +968,14 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ wl_list_for_each(om, &mons, link) { ++ wlr_output_effective_resolution(om->wlr_output, &width, &height); ++ if (om->m.x + width > max_x) { ++ max_x = om->m.x + width; ++ max_x_y = om->m.y; ++ } ++ } ++ + wl_list_insert(&mons, &m->link); + printstatus(); + +@@ -990,7 +999,7 @@ createmon(struct wl_listener *listener, void *data) + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) +- wlr_output_layout_add_auto(output_layout, wlr_output); ++ wlr_output_layout_add(output_layout, wlr_output, max_x, max_x_y); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); + } +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/rlimit_max/README.md b/dwl/dwl-patches/patches/rlimit_max/README.md new file mode 100644 index 0000000..2bf7355 --- /dev/null +++ b/dwl/dwl-patches/patches/rlimit_max/README.md @@ -0,0 +1,11 @@ +### Description +Sets the current maximum open file descriptors to the maximum available limit. + +This patch is useful - and solves issue [#628](https://codeberg.org/dwl/dwl/issues/628) for running heavy Xwayland applications on systems that do not provide limits out of the box. + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/rlimit_max/rlimit_max.patch) + +### Authors +- [sewn](https://codeberg.org/sewn) + diff --git a/dwl/dwl-patches/patches/rlimit_max/rlimit_max.patch b/dwl/dwl-patches/patches/rlimit_max/rlimit_max.patch new file mode 100644 index 0000000..81111b3 --- /dev/null +++ b/dwl/dwl-patches/patches/rlimit_max/rlimit_max.patch @@ -0,0 +1,91 @@ +From d25a8222651671613322677d17b2f987135e02cd Mon Sep 17 00:00:00 2001 +From: sewn +Date: Sat, 24 Aug 2024 19:26:26 +0300 +Subject: [PATCH] set max open file descriptors to available max + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f6..163ebdd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -316,6 +317,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); ++static void restorerlimit(void); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -358,6 +360,7 @@ static void zoom(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; ++static struct rlimit og_rlimit; + static pid_t child_pid = -1; + static int locked; + static void *exclusive_focus; +@@ -2096,6 +2099,15 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++restorerlimit(void) ++{ ++ if (og_rlimit.rlim_cur == 0) ++ return; ++ if (setrlimit(RLIMIT_NOFILE, &og_rlimit) < 0) ++ die("setrlimit:"); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +@@ -2232,6 +2244,7 @@ run(char *startup_cmd) + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ restorerlimit(); + setsid(); + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); +@@ -2429,10 +2442,18 @@ setsel(struct wl_listener *listener, void *data) + void + setup(void) + { ++ struct rlimit new_rlimit; + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + ++ if (getrlimit(RLIMIT_NOFILE, &og_rlimit) < 0) ++ die("getrlimit:"); ++ new_rlimit = og_rlimit; ++ new_rlimit.rlim_cur = new_rlimit.rlim_max; ++ if (setrlimit(RLIMIT_NOFILE, &new_rlimit) < 0) ++ die("setrlimit:"); ++ + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + +@@ -2649,6 +2670,7 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ restorerlimit(); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/rotate-clients/README.md b/dwl/dwl-patches/patches/rotate-clients/README.md new file mode 100644 index 0000000..aaf8f67 --- /dev/null +++ b/dwl/dwl-patches/patches/rotate-clients/README.md @@ -0,0 +1,14 @@ +### Description +Rotate clients on current monitor. + +Example Configuration: +```c + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, rotate_clients, {.i = +1} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, rotate_clients, {.i = -1} }, +``` + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/rotate-clients/rotate-clients.patch) + +### Authors +- [folk](https://codeberg.org/folk) diff --git a/dwl/dwl-patches/patches/rotate-clients/rotate-clients.patch b/dwl/dwl-patches/patches/rotate-clients/rotate-clients.patch new file mode 100644 index 0000000..8f5f557 --- /dev/null +++ b/dwl/dwl-patches/patches/rotate-clients/rotate-clients.patch @@ -0,0 +1,56 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..6029666 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, rotate_clients, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, rotate_clients, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..6dfd87d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -355,6 +355,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void rotate_clients(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -3054,6 +3055,30 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++static void rotate_clients(const Arg *arg) { ++ Monitor* m = selmon; ++ Client *c; ++ Client *first = NULL; ++ Client *last = NULL; ++ ++ if (arg->i == 0) ++ return; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ if (first == NULL) first = c; ++ last = c; ++ } ++ } ++ if (first != last) { ++ struct wl_list *append_to = (arg->i > 0) ? &last->link : first->link.prev; ++ struct wl_list *elem = (arg->i > 0) ? &first->link : &last->link; ++ wl_list_remove(elem); ++ wl_list_insert(append_to, elem); ++ arrange(selmon); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) diff --git a/dwl/dwl-patches/patches/rotatetags/README.md b/dwl/dwl-patches/patches/rotatetags/README.md new file mode 100644 index 0000000..f981847 --- /dev/null +++ b/dwl/dwl-patches/patches/rotatetags/README.md @@ -0,0 +1,9 @@ +### Description +This patch provides the ability to rotate the tagset left / right. It implements a new function rotatetags which modifies the current tagset. Same as original dwm patch. Also adds ability to move focused client to left / right adjacent tag by specifying appropriate enum value as argument. + +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/rotatetags) +- [2024-01-23](https://codeberg.org/korei999/dwl-patches/raw/branch/main/rotatetags/rotatetags.patch) + +### Authors +- [korei999](https://codeberg.org/korei999) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/rotatetags/rotatetags.patch b/dwl/dwl-patches/patches/rotatetags/rotatetags.patch new file mode 100644 index 0000000..94e776f --- /dev/null +++ b/dwl/dwl-patches/patches/rotatetags/rotatetags.patch @@ -0,0 +1,91 @@ +From 308c668010bb7526ea40ad12dbaa1af62f9d7421 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Tue, 23 Jan 2024 12:01:48 +0200 +Subject: [PATCH] add rotatetags patch + +--- + config.def.h | 13 ++++++++++++- + dwl.c | 29 +++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..a80207a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,6 +14,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff); + /* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++enum { ++ VIEW_L = -1, ++ VIEW_R = 1, ++ SHIFT_L = -2, ++ SHIFT_R = 2, ++} RotateTags; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -125,7 +132,11 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_i, incnmaster, {.i = -1} }, ++ { MODKEY, XKB_KEY_a, rotatetags, {.i = VIEW_L} }, ++ { MODKEY, XKB_KEY_d, rotatetags, {.i = VIEW_R} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_a, rotatetags, {.i = SHIFT_L} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_d, rotatetags, {.i = SHIFT_R} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index bf02a6d..e737e34 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -332,6 +332,7 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void rotatetags(const Arg *arg); + + /* variables */ + static const char broken[] = "broken"; +@@ -2798,6 +2799,34 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++static void ++rotatetags(const Arg *arg) ++{ ++ Arg newarg; ++ int i = arg->i; ++ int nextseltags = 0, curseltags = selmon->tagset[selmon->seltags]; ++ bool shift = false; ++ ++ switch(abs(i)) { ++ default: break; ++ case SHIFT_R: ++ shift = true; ++ break; ++ }; ++ ++ if (i > 0) ++ nextseltags = (curseltags << 1) | (curseltags >> (TAGCOUNT - 1)); ++ else ++ nextseltags = (curseltags >> 1) | (curseltags << (TAGCOUNT - 1)); ++ ++ newarg.i = nextseltags; ++ ++ if (shift) ++ tag(&newarg); ++ else ++ view(&newarg); ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/scenefx/README.md b/dwl/dwl-patches/patches/scenefx/README.md new file mode 100644 index 0000000..e1054b3 --- /dev/null +++ b/dwl/dwl-patches/patches/scenefx/README.md @@ -0,0 +1,113 @@ +### Description + +Implement https://github.com/wlrfx/scenefx + +```c +/* available options */ + +static const int opacity = 0; /* flag to enable opacity */ +static const float opacity_inactive = 0.5; +static const float opacity_active = 1.0; + +static const int shadow = 1; /* flag to enable shadow */ +static const int shadow_only_floating = 0; /* only apply shadow to floating windows */ +static const float shadow_color[4] = COLOR(0x0000FFff); +static const float shadow_color_focus[4] = COLOR(0xFF0000ff); +static const int shadow_blur_sigma = 20; +static const int shadow_blur_sigma_focus = 40; +static const char *const shadow_ignore_list[] = { NULL }; /* list of app-id to ignore */ + +static const int corner_radius = 8; /* 0 disables corner_radius */ +static const int corner_radius_inner = 8; /* 0 disables corner_radius */ +static const int corner_radius_only_floating = 0; /* only apply corner_radius and corner_radius_inner to floating windows */ + +static const int blur = 1; /* flag to enable blur */ +static const int blur_xray = 0; /* flag to make transparent fs and floating windows display your background */ +static const int blur_ignore_transparent = 1; +static const struct blur_data blur_data = { + .radius = 5, + .num_passes = 3, + .noise = (float)0.02, + .brightness = (float)0.9, + .contrast = (float)0.9, + .saturation = (float)1.1, +}; +``` + +> **NOTE:** If you are using nix with flakes, scenefx has a flake for scenefx https://github.com/wlrfx/scenefx/blob/main/flake.nix + +> **NOTE:** Blur doesn't work on windows with opacity set (opacity_active, opacity_inactive) + +> **NOTE:** In DWL's Makefile `scenefx-0.2` must be placed before `wlroots-0.18`, e.g. `PKGS = scenefx-0.2 wlroots-0.18 wayland-server ...` + +
+Preview +
+
+
+
+ +### Download + +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.8-a/scenefx-b) + +- [0.8-dev](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/scenefx/scenefx.patch) + + **NOTE:** This patch was tested with the `87c0e8b6d5c86557a800445e8e4c322f387fe19c` commit on the `main` branch of `SceneFX`. It supports rounded borders, blur, and shadows. However, it does not add rounded borders or shadows to Xwayland apps. That said, Xwayland apps can have shadows, and they might also support rounded borders, but I was never able to make it work. PRs are welcome! + + **IMPORTANT:** This patch requires you to build DWL with the following dependencies + + - **scenefx** + - libGL + +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/scenefx/scenefx.patch) + + **IMPORTANT:** This patch only works with the `2ec3505248e819191c37cb831197629f373326fb` commit on the `main` branch of `scenefx`, therefore, it does not support **blur**. + + **IMPORTANT:** This patch requires you to build DWL with the following dependencies + + - **scenefx** + - libGL + +
+ Preview +
+  
+  
+  
+
+ +- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/6e3a57ffd16dafa31900b7e89e51672bd7bcc1e8/scenefx/scenefx.patch) + + **IMPORTANT:** This patch only works with the `de4ec10e1ff9347b5833f00f8615d760d9378c99` commit on the `wlr_scene_blur` branch of `scenefx`, as it adds support for **blur**. + + **IMPORTANT:** This patch requires you to build DWL with the dependencies of WLROOTS: + + - **scenefx** + - libGL + - libcap + - libinput + - libpng + - libxkbcommon + - mesa + - pixman + - seatd + - vulkan-loader + - wayland + - wayland-protocols + - xorg.libX11 + - xorg.xcbutilerrors + - xorg.xcbutilimage + - xorg.xcbutilrenderutil + - xorg.xcbutilwm + - xwayland (optional) + - ffmpeg + - hwdata + - libliftoff + - libdisplay-info + +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/7a5c3420822074c544fa102e030b7c30aa6b6be8/scenefx/scenefx.patch) + +### Authors + +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/scenefx/scenefx.patch b/dwl/dwl-patches/patches/scenefx/scenefx.patch new file mode 100644 index 0000000..c556cb4 --- /dev/null +++ b/dwl/dwl-patches/patches/scenefx/scenefx.patch @@ -0,0 +1,700 @@ +From d209aeaee541a045de9dba2154bbbd5475bba2ec Mon Sep 17 00:00:00 2001 +From: wochap +Date: Sun, 9 Mar 2025 17:26:07 -0500 +Subject: [PATCH] implement scenefx + +--- + Makefile | 2 +- + client.h | 18 +++ + config.def.h | 30 +++- + dwl.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 4 files changed, 457 insertions(+), 7 deletions(-) + +diff --git a/Makefile b/Makefile +index 3358bae..8dc79bd 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = scenefx-0.2 wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/client.h b/client.h +index 42f225f..4e8f016 100644 +--- a/client.h ++++ b/client.h +@@ -147,6 +147,13 @@ client_get_clip(Client *c, struct wlr_box *clip) + return; + #endif + ++ *clip = (struct wlr_box){ ++ .x = c->bw, ++ .y = c->bw, ++ .width = c->geom.width - c->bw * 2, ++ .height = c->geom.height - c->bw * 2, ++ }; ++ + wlr_xdg_surface_get_geometry(c->surface.xdg, &xdg_geom); + clip->x = xdg_geom.x; + clip->y = xdg_geom.y; +@@ -328,6 +335,17 @@ static inline void + client_set_border_color(Client *c, const float color[static 4]) + { + int i; ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius > 0) { ++ return; ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ + for (i = 0; i < 4; i++) + wlr_scene_rect_set_color(c->border[i], color); + } +diff --git a/config.def.h b/config.def.h +index 22d2171..8c50932 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -12,7 +12,35 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ +-static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 0.0f}; /* You can also use glsl colors */ ++ ++static const int opacity = 0; /* flag to enable opacity */ ++static const float opacity_inactive = 0.5; ++static const float opacity_active = 1.0; ++ ++static const int shadow = 1; /* flag to enable shadow */ ++static const int shadow_only_floating = 0; /* only apply shadow to floating windows */ ++static const float shadow_color[4] = COLOR(0x0000FFff); ++static const float shadow_color_focus[4] = COLOR(0xFF0000ff); ++static const int shadow_blur_sigma = 20; ++static const int shadow_blur_sigma_focus = 40; ++static const char *const shadow_ignore_list[] = { NULL }; /* list of app-id to ignore */ ++ ++static const int corner_radius = 8; /* 0 disables corner_radius */ ++static const int corner_radius_inner = 9; /* 0 disables corner_radius */ ++static const int corner_radius_only_floating = 0; /* only apply corner_radius and corner_radius_inner to floating windows */ ++ ++static const int blur = 1; /* flag to enable blur */ ++static const int blur_xray = 0; /* flag to make transparent fs and floating windows display your background */ ++static const int blur_ignore_transparent = 1; ++static const struct blur_data blur_data = { ++ .radius = 5, ++ .num_passes = 3, ++ .noise = (float)0.02, ++ .brightness = (float)0.9, ++ .contrast = (float)0.9, ++ .saturation = (float)1.1, ++}; + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +diff --git a/dwl.c b/dwl.c +index 5bf995e..655e175 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -10,8 +10,14 @@ + #include + #include + #include ++#include ++#include ++#include ++#include ++#include + #include + #include ++#include + #include + #include + #include +@@ -43,7 +49,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -83,7 +88,7 @@ + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ +-enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { LyrBg, LyrBlur, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -141,6 +146,12 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ ++ float opacity; ++ int corner_radius; ++ struct wlr_scene_shadow *shadow; ++ int has_shadow_enabled; ++ struct wlr_scene_rect *round_border; + } Client; + + typedef struct { +@@ -208,6 +219,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ struct wlr_scene_optimized_blur *blur_layer; + }; + + typedef struct { +@@ -355,6 +367,18 @@ static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); + static void zoom(const Arg *arg); ++static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_blur(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_opacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void iter_xdg_scene_buffers_corner_radius(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data); ++static void output_configure_scene(struct wlr_scene_node *node, Client *c); ++static int in_shadow_ignore_list(const char *str); ++static void client_set_shadow_blur_sigma(Client *c, int blur_sigma); ++static void update_client_corner_radius(Client *c); ++static void update_client_shadow_color(Client *c); ++static void update_client_focus_decorations(Client *c, int focused, int urgent); ++static void update_client_blur(Client *c); ++static void update_buffer_corner_radius(Client *c, struct wlr_scene_buffer *buffer); + + /* variables */ + static const char broken[] = "broken"; +@@ -413,6 +437,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static float transparent[4] = {0.1f, 0.1f, 0.1f, 0.0f}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -499,6 +525,10 @@ arrange(Monitor *m) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + ++ if (blur) { ++ wlr_scene_node_set_enabled(&m->blur_layer->node, 1); ++ } ++ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while +@@ -722,6 +752,11 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ ++ if (blur) { ++ wlr_scene_node_destroy(&m->blur_layer->node); ++ } ++ + free(m); + } + +@@ -785,6 +820,18 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data) + } + + arrangelayers(l->mon); ++ ++ if (blur) { ++ // Rerender the optimized blur on change ++ struct wlr_layer_surface_v1 *wlr_layer_surface = l->layer_surface; ++ if (wlr_layer_surface->current.layer == ++ ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ++ wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { ++ if (l->mon) { ++ wlr_scene_optimized_blur_mark_dirty(l->mon->blur_layer); ++ } ++ } ++ } + } + + void +@@ -1036,6 +1083,12 @@ createmon(struct wl_listener *listener, void *data) + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + ++ if (blur) { ++ m->blur_layer = wlr_scene_optimized_blur_create(&scene->tree, 0, 0); ++ wlr_scene_node_reparent(&m->blur_layer->node, layers[LyrBlur]); ++ wlr_scene_node_set_enabled(&m->blur_layer->node, 0); ++ } ++ + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the +@@ -1061,6 +1114,9 @@ createnotify(struct wl_listener *listener, void *data) + c->surface.xdg = toplevel->base; + c->bw = borderpx; + ++ c->opacity = opacity; ++ c->corner_radius = corner_radius; ++ + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); +@@ -1369,8 +1425,11 @@ focusclient(Client *c, int lift) + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ ++ update_client_focus_decorations(c, 1, 0); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1389,6 +1448,8 @@ focusclient(Client *c, int lift) + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); + ++ update_client_focus_decorations(old_c, 0, 0); ++ + client_activate_surface(old, 0); + } + } +@@ -1718,6 +1779,38 @@ mapnotify(struct wl_listener *listener, void *data) + c->border[i]->node.data = c; + } + ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers, c); ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius > 0) { ++ c->round_border = wlr_scene_rect_create(c->scene, 0, 0, c->isurgent ? urgentcolor : bordercolor); ++ c->round_border->node.data = c; ++ /* Lower the border below the XDG scene tree */ ++ wlr_scene_node_lower_to_bottom(&c->round_border->node); ++ ++ /* hide original border */ ++ for (i = 0; i < 4; i++) { ++ wlr_scene_rect_set_color(c->border[i], transparent); ++ } ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (shadow) { ++ c->shadow = wlr_scene_shadow_create(c->scene, 0, 0, c->corner_radius, shadow_blur_sigma, shadow_color); ++ /* Lower the shadow below the border */ ++ wlr_scene_node_lower_to_bottom(&c->shadow->node); ++ } ++#ifdef XWAYLAND ++ } ++#endif ++ + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; +@@ -1739,6 +1832,13 @@ mapnotify(struct wl_listener *listener, void *data) + } + printstatus(); + ++ /* TODO: shouldn't we call iter_xdg_scene_buffers_corner_radius? */ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { +@@ -2113,6 +2213,8 @@ rendermon(struct wl_listener *listener, void *data) + goto skip; + } + ++ output_configure_scene(&m->scene_output->scene->tree.node, NULL); ++ + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite +@@ -2207,6 +2309,21 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ if (corner_radius > 0 && c->round_border) { ++ wlr_scene_node_set_position(&c->round_border->node, 0, 0); ++ wlr_scene_rect_set_size(c->round_border, c->geom.width, c->geom.height); ++ wlr_scene_rect_set_clipped_region(c->round_border, (struct clipped_region) { ++ .corner_radius = c->corner_radius, ++ .corners = CORNER_LOCATION_ALL, ++ .area = { c->bw, c->bw, c->geom.width - c->bw * 2, c->geom.height - c->bw * 2 } ++ }); ++ } ++ ++ if (shadow && c->shadow) { ++ /* TODO: shouldn't we call wlr_scene_shadow_set_blur_sigma? */ ++ client_set_shadow_blur_sigma(c, (int)round(c->shadow->blur_sigma)); ++ } + } + + void +@@ -2307,6 +2424,13 @@ setfloating(Client *c, int floating) + { + Client *p = client_get_parent(c); + c->isfloating = floating; ++ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; +@@ -2336,6 +2460,13 @@ setfullscreen(Client *c, int fullscreen) + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } ++ ++ update_client_corner_radius(c); ++ ++ update_client_shadow_color(c); ++ ++ update_client_blur(c); ++ + arrange(c->mon); + printstatus(); + } +@@ -2457,11 +2588,15 @@ setup(void) + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + ++ if (blur) { ++ wlr_scene_set_blur_data(scene, blur_data); ++ } ++ + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ +- if (!(drw = wlr_renderer_autocreate(backend))) ++ if (!(drw = fx_renderer_create(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + +@@ -2870,6 +3005,10 @@ updatemons(struct wl_listener *listener, void *data) + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); ++ ++ if (blur) { ++ wlr_scene_optimized_blur_set_size(m->blur_layer, m->m.width, m->m.height); ++ } + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; +@@ -2940,8 +3079,11 @@ urgent(struct wl_listener *listener, void *data) + c->isurgent = 1; + printstatus(); + +- if (client_surface(c)->mapped) ++ if (client_surface(c)->mapped) { + client_set_border_color(c, urgentcolor); ++ ++ update_client_focus_decorations(c, 1, 1); ++ } + } + + void +@@ -3053,6 +3195,268 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++void ++iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ update_buffer_corner_radius(c, buffer); ++ ++ if (blur) { ++ int blur_optimized = !c->isfloating || blur_xray; ++ wlr_scene_buffer_set_backdrop_blur(buffer, 1); ++ wlr_scene_buffer_set_backdrop_blur_optimized(buffer, blur_optimized); ++ wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, blur_ignore_transparent); ++ } ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_blur(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ if (blur) { ++ int blur_optimized = !c->isfloating || blur_xray; ++ wlr_scene_buffer_set_backdrop_blur_optimized(buffer, blur_optimized); ++ } ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_opacity(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ } ++} ++ ++void ++iter_xdg_scene_buffers_corner_radius(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) ++{ ++ Client *c = user_data; ++ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ struct wlr_xdg_surface *xdg_surface; ++ ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ /* TODO: Be able to set whole decoration_data instead of calling */ ++ /* each individually? */ ++ update_buffer_corner_radius(c, buffer); ++ } ++} ++ ++void ++output_configure_scene(struct wlr_scene_node *node, Client *c) ++{ ++ Client *_c; ++ struct wlr_xdg_surface *xdg_surface; ++ struct wlr_scene_node *_node; ++ ++ if (!node->enabled) { ++ return; ++ } ++ ++ _c = node->data; ++ if (_c) { ++ c = _c; ++ } ++ ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); ++ ++ struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); ++ if (!scene_surface) { ++ return; ++ } ++ ++ xdg_surface = wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface); ++ ++ if (c && xdg_surface && xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { ++ if (opacity) { ++ wlr_scene_buffer_set_opacity(buffer, c->opacity); ++ } ++ ++ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) { ++ update_buffer_corner_radius(c, buffer); ++ } ++ } ++ } else if (node->type == WLR_SCENE_NODE_TREE) { ++ struct wlr_scene_tree *tree = wl_container_of(node, tree, node); ++ wl_list_for_each(_node, &tree->children, link) { ++ output_configure_scene(_node, c); ++ } ++ } ++} ++ ++int ++in_shadow_ignore_list(const char *str) ++{ ++ for (int i = 0; shadow_ignore_list[i] != NULL; i++) { ++ if (strcmp(shadow_ignore_list[i], str) == 0) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++void ++client_set_shadow_blur_sigma(Client *c, int blur_sigma) ++{ ++ wlr_scene_shadow_set_blur_sigma(c->shadow, blur_sigma); ++ wlr_scene_node_set_position(&c->shadow->node, -blur_sigma, -blur_sigma); ++ wlr_scene_shadow_set_size(c->shadow, c->geom.width + blur_sigma * 2, c->geom.height + blur_sigma * 2); ++ wlr_scene_shadow_set_clipped_region(c->shadow, (struct clipped_region) { ++ .corner_radius = c->corner_radius + c->bw, ++ .corners = CORNER_LOCATION_ALL, ++ .area = { blur_sigma, blur_sigma, c->geom.width, c->geom.height } ++ }); ++} ++ ++void ++update_client_corner_radius(Client *c) ++{ ++ if (corner_radius && c->round_border) { ++ int radius = c->corner_radius + c->bw; ++ if ((corner_radius_only_floating && !c->isfloating) || c->isfullscreen) { ++ radius = 0; ++ } ++ wlr_scene_rect_set_corner_radius(c->round_border, radius, CORNER_LOCATION_ALL); ++ } ++ ++#ifdef XWAYLAND ++ if (!client_is_x11(c)) { ++#endif ++ if (corner_radius_inner > 0 && c->scene) { ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_corner_radius, c); ++ } ++#ifdef XWAYLAND ++ } ++#endif ++} ++ ++void ++update_client_blur(Client *c) ++{ ++ if (!blur) { ++ return; ++ } ++ ++ if (c->scene) { ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_blur, c); ++ } ++} ++ ++void ++update_buffer_corner_radius(Client *c, struct wlr_scene_buffer *buffer) ++{ ++ int radius; ++ ++#ifdef XWAYLAND ++ if (client_is_x11(c)) { ++ return; ++ } ++#endif ++ ++ if (!corner_radius_inner) { ++ return; ++ } ++ ++ radius = corner_radius_inner; ++ if ((corner_radius_only_floating && !c->isfloating) || c->isfullscreen) { ++ radius = 0; ++ } ++ wlr_scene_buffer_set_corner_radius(buffer, radius, CORNER_LOCATION_ALL); ++} ++ ++void ++update_client_shadow_color(Client *c) ++{ ++ int has_shadow_enabled = 1; ++ const float *color; ++ ++ if (!shadow || !c->shadow) { ++ return; ++ } ++ ++ color = focustop(c->mon) == c ? shadow_color_focus : shadow_color; ++ ++ if ((shadow_only_floating && !c->isfloating) || ++ in_shadow_ignore_list(client_get_appid(c)) || ++ c->isfullscreen) { ++ color = transparent; ++ has_shadow_enabled = 0; ++ } ++ ++ wlr_scene_shadow_set_color(c->shadow, color); ++ c->has_shadow_enabled = has_shadow_enabled; ++} ++ ++void ++update_client_focus_decorations(Client *c, int focused, int urgent) ++{ ++ if (corner_radius > 0 && c->round_border) { ++ wlr_scene_rect_set_color(c->round_border, urgent ? urgentcolor : (focused ? focuscolor : bordercolor)); ++ } ++ if (shadow && c->shadow) { ++ client_set_shadow_blur_sigma(c, (int)round(focused ? shadow_blur_sigma_focus : shadow_blur_sigma)); ++ if (c->has_shadow_enabled) { ++ wlr_scene_shadow_set_color(c->shadow, focused ? shadow_color_focus : shadow_color); ++ } ++ } ++ if (opacity) { ++ c->opacity = focused ? opacity_active : opacity_inactive; ++ wlr_scene_node_for_each_buffer(&c->scene_surface->node, iter_xdg_scene_buffers_opacity, c); ++ } ++} ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +-- +2.47.2 + + diff --git a/dwl/dwl-patches/patches/setrule/README.md b/dwl/dwl-patches/patches/setrule/README.md new file mode 100644 index 0000000..bdb7408 --- /dev/null +++ b/dwl/dwl-patches/patches/setrule/README.md @@ -0,0 +1,46 @@ +### Description + +This patch adds an ability to add or change client rules at runtime. + +Sometimes it happens that some client rule I've set is not relevant in the +current circumstance and adds major disturbance and annoyance to my work +(e.g., `isfloating` is set or unset and I want the opposite). Changing the rule +is not an option because it will require recompilation and restarting dwl, which +is even worse. Having an option of always being able to change a rule solves +this issue. + +The patch only adds one keybinding (`Alt+Shift+R`) to toggle `isfloating` +option. Upon pressing it, dwl will try to find a matching rule for the focused +client and change its `isfloating` setting. If there's no such a rule, a new +rule will be added. The new rule will inherit an appid of the focused client. + +It's very easy to add support for more rule options from other patches as well. +You just need to define a new function similar to `setruleisfloating` and add a +new keybinding to `config.h`. For example, this is a function I created for my +build to toggle `noswallow` option from the [swallow][swallow] patch: + +```c +void +setrulenoswallow(const Arg *arg) +{ + Rule *r = getrule(focustop(selmon)); + if (!r) + return; + r->noswallow = !r->noswallow; +} +``` + +You can also try [menurule][menurule] patch if you want to be able to edit rules +with dmenu. + +[swallow]: /dwl/dwl-patches/src/branch/main/patches/swallow +[menurule]: /dwl/dwl-patches/src/branch/main/patches/menurule + +### Download + +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/setrule/setrule.patch) +- [2025-02-14 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/268bee3cee239e5bd52cceed88a52bfc21143cc3/patches/setrule/setrule.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/setrule/setrule.patch b/dwl/dwl-patches/patches/setrule/setrule.patch new file mode 100644 index 0000000..6f9a1a3 --- /dev/null +++ b/dwl/dwl-patches/patches/setrule/setrule.patch @@ -0,0 +1,155 @@ +From 3c78308f0d74ac6ef112804333f82c098e33bb40 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Fri, 21 Mar 2025 22:20:54 +0100 +Subject: [PATCH] setrule: add/change rules at runtime + +--- + config.def.h | 4 ++++ + dwl.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 70 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..5b05e52 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,9 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Max amount of dynamically added rules */ ++#define RULES_MAX 100 ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +@@ -142,6 +145,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index def2562..8beac1f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -290,6 +290,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static Rule *getrule(Client *c); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -331,6 +332,7 @@ static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); ++static void setruleisfloating(const Arg *arg); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); +@@ -413,6 +415,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static Rule *drules; ++static size_t druleslen; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -466,7 +471,7 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + +- for (r = rules; r < END(rules); r++) { ++ for (r = drules; r < drules + druleslen; r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; +@@ -1472,6 +1477,53 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++Rule * ++getrule(Client *c) ++{ ++ Rule *r; ++ const Rule *e; ++ const char *appid, *title; ++ ++ if (!c) ++ return NULL; ++ ++ if (!(appid = client_get_appid(c))) ++ appid = broken; ++ if (!(title = client_get_title(c))) ++ title = broken; ++ ++ for (r = drules + druleslen - 1; r >= drules; r--) ++ if ((!r->title || strstr(title, r->title)) ++ && (!r->id || strstr(appid, r->id))) ++ goto found; ++ ++ if (druleslen >= LENGTH(rules) + RULES_MAX) ++ return NULL; /* No free slots left */ ++ ++ r = drules + druleslen++; ++ ++ /* Use [NULL,NULL] as the default rule if exists */ ++ for (e = rules; e < END(rules); e++) ++ if (!e->title && !e->id) { ++ *r = *e; ++ break; ++ } ++ ++ /* No default rule found, set reasoble defaults */ ++ if (e >= END(rules)) { ++ r->monitor = -1; ++ } ++ ++ /* Only set title if appid is unset */ ++ if (appid == broken) ++ r->title = strdup(title); ++ else ++ r->id = strdup(appid); ++ ++found: ++ return r; ++} ++ + void + gpureset(struct wl_listener *listener, void *data) + { +@@ -2417,6 +2469,15 @@ setpsel(struct wl_listener *listener, void *data) + wlr_seat_set_primary_selection(seat, event->source, event->serial); + } + ++void ++setruleisfloating(const Arg *arg) ++{ ++ Rule *r = getrule(focustop(selmon)); ++ if (!r) ++ return; ++ r->isfloating = !r->isfloating; ++} ++ + void + setsel(struct wl_listener *listener, void *data) + { +@@ -2645,6 +2706,10 @@ setup(void) + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } + #endif ++ ++ drules = ecalloc(LENGTH(rules) + RULES_MAX, sizeof(Rule)); ++ memcpy(drules, rules, sizeof(rules)); ++ druleslen = LENGTH(rules); + } + + void +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/setupenv/README.md b/dwl/dwl-patches/patches/setupenv/README.md new file mode 100644 index 0000000..776c328 --- /dev/null +++ b/dwl/dwl-patches/patches/setupenv/README.md @@ -0,0 +1,8 @@ +### Description +Allow configuring environment variables in config.h + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/setupenv) +- [2024-03-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/setupenv/setupenv.patch) +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/setupenv/setupenv.patch b/dwl/dwl-patches/patches/setupenv/setupenv.patch new file mode 100644 index 0000000..17d2ad4 --- /dev/null +++ b/dwl/dwl-patches/patches/setupenv/setupenv.patch @@ -0,0 +1,54 @@ +From 11ee2fc23ef5728d1e132f338c08a7805c6109b2 Mon Sep 17 00:00:00 2001 +From: choc +Date: Tue, 26 Mar 2024 21:02:16 +0800 +Subject: [PATCH] implement setupenv + +--- + config.def.h | 5 +++++ + dwl.c | 7 +++++++ + 2 files changed, 12 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 9009517..b16189a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++static const Env envs[] = { ++ /* variable value */ ++ { "XDG_CURRENT_DESKTOP", "wlroots" }, ++}; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + /* examples: +diff --git a/dwl.c b/dwl.c +index 5867b0c..b7d522b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -230,6 +230,11 @@ typedef struct { + int monitor; + } Rule; + ++typedef struct { ++ const char *variable; ++ const char *value; ++} Env; ++ + typedef struct { + struct wlr_scene_tree *scene; + +@@ -2066,6 +2071,8 @@ run(char *startup_cmd) + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); ++ for (size_t i = 0; i < LENGTH(envs); i++) ++ setenv(envs[i].variable, envs[i].value, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/shiftview/README.md b/dwl/dwl-patches/patches/shiftview/README.md new file mode 100644 index 0000000..2d25820 --- /dev/null +++ b/dwl/dwl-patches/patches/shiftview/README.md @@ -0,0 +1,9 @@ +### Description +Add keybindings to cycle through tags with visible clients. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/shiftview) +- [2024-01-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shiftview/shiftview.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl/dwl-patches/patches/shiftview/shiftview.patch b/dwl/dwl-patches/patches/shiftview/shiftview.patch new file mode 100644 index 0000000..f556916 --- /dev/null +++ b/dwl/dwl-patches/patches/shiftview/shiftview.patch @@ -0,0 +1,76 @@ +From 40f9140742277d0298988990264f4b6a738f8122 Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Sat, 27 Jan 2024 22:43:29 +0100 +Subject: [PATCH] cycle through tags + +--- + config.def.h | 4 ++++ + shiftview.c | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + create mode 100644 shiftview.c + +diff --git a/config.def.h b/config.def.h +index 9009517..8d77ec0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -117,6 +117,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "bemenu-run", NULL }; + ++#include "shiftview.c" ++ + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ +@@ -130,6 +132,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, ++ { MODKEY, XKB_KEY_a, shiftview, { .i = -1 } }, ++ { MODKEY, XKB_KEY_semicolon, shiftview, { .i = 1 } }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, +diff --git a/shiftview.c b/shiftview.c +new file mode 100644 +index 0000000..fa53db0 +--- /dev/null ++++ b/shiftview.c +@@ -0,0 +1,34 @@ ++// "arg->i" stores the number of tags to shift right (positive value) ++// or left (negative value) ++void ++shiftview(const Arg *arg) ++{ ++ Arg a; ++ Client *c; ++ bool visible = false; ++ int i = arg->i; ++ int count = 0; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ ++ do { ++ if (i > 0) // left circular shift ++ nextseltags = (curseltags << i) | (curseltags >> (TAGCOUNT - i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-i) | (curseltags << (TAGCOUNT + i)); ++ ++ // Check if the tag is visible ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == selmon && nextseltags & c->tags) { ++ visible = true; ++ break; ++ } ++ } ++ ++ i += arg->i; ++ } while (!visible && ++count <= TAGCOUNT); ++ ++ if (count <= TAGCOUNT) { ++ a.i = nextseltags; ++ view(&a); ++ } ++} +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/simple_scratchpad/README.md b/dwl/dwl-patches/patches/simple_scratchpad/README.md new file mode 100644 index 0000000..8076d96 --- /dev/null +++ b/dwl/dwl-patches/patches/simple_scratchpad/README.md @@ -0,0 +1,25 @@ +### Description + +# simple_scratchpad — A very simple scratchpad utility. + +Adds functions to add, toggle and remove clients to/from scratchpad client list. +![scratchpad in action](https://github.com/julmajustus/dwl-patches/blob/main/demos/simple_scratchpad_demo.gif?raw=true) + +--- + +1. **addscratchpad** + - Floats client and adds it to scratchpad clients list. + +2. **togglescratchpad** + - Shows or hides the clients in scratchpad client list. + +3. **removescratchpad** + - Removes client from scratchpad client list. + + +### Download +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/simple_scratchpad) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simple_scratchpad/simple_scratchpad-v0.7.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/dwl/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch b/dwl/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch new file mode 100644 index 0000000..a273559 --- /dev/null +++ b/dwl/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch @@ -0,0 +1,211 @@ +From a8d29e03f565b54a68c6c2cb2da103366c627825 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sat, 4 Jan 2025 13:22:12 +0200 +Subject: [PATCH] add simple_scratchpad + +--- + config.def.h | 3 ++ + dwl.c | 34 +++++++++++++++++++++-- + simple_scratchpad.c | 67 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 102 insertions(+), 2 deletions(-) + create mode 100644 simple_scratchpad.c + +diff --git a/config.def.h b/config.def.h +index 22d2171..83f19b3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -136,6 +136,9 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Z, addscratchpad, {0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, togglescratchpad, {0} }, ++ { MODKEY, XKB_KEY_z, removescratchpad, {0} }, + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..85f4a51 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wl_list link_temp; + } Client; + + typedef struct { +@@ -243,6 +244,7 @@ typedef struct { + } SessionLock; + + /* function declarations */ ++static void addscratchpad(const Arg *arg); + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); + static void arrange(Monitor *m); +@@ -317,6 +319,7 @@ static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); ++static void removescratchpad(const Arg *arg); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +@@ -340,6 +343,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglescratchpad(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +417,9 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static struct wl_list scratchpad_clients; ++static int scratchpad_visible = 1; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -432,6 +439,8 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "simple_scratchpad.c" ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1259,10 +1268,20 @@ void + destroynotify(struct wl_listener *listener, void *data) + { + /* Called when the xdg_toplevel is destroyed. */ +- Client *c = wl_container_of(listener, c, destroy); ++ Client *sc, *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* Check if destroyed client was part of scratchpad_clients ++ * and clean it from the list if so. */ ++ if (c && wl_list_length(&scratchpad_clients) > 0) { ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -2306,11 +2325,21 @@ setcursorshape(struct wl_listener *listener, void *data) + void + setfloating(Client *c, int floating) + { +- Client *p = client_get_parent(c); ++ Client *sc, *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; ++ /* Check if unfloated client was part of scratchpad_clients ++ * and remove it from scratchpad_clients list if so */ ++ if (!floating && wl_list_length(&scratchpad_clients) > 0) { ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); +@@ -2534,6 +2563,7 @@ setup(void) + */ + wl_list_init(&clients); + wl_list_init(&fstack); ++ wl_list_init(&scratchpad_clients); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); +diff --git a/simple_scratchpad.c b/simple_scratchpad.c +new file mode 100644 +index 0000000..381f4b5 +--- /dev/null ++++ b/simple_scratchpad.c +@@ -0,0 +1,68 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* simple_scratchpad.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/19 19:35:02 by jmakkone #+# #+# */ ++/* Updated: 2025/01/04 13:35:50 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++void ++addscratchpad(const Arg *arg) ++{ ++ Client *cc, *c = focustop(selmon); ++ ++ if (!c) ++ return; ++ /* Check if the added client is already a scratchpad client */ ++ wl_list_for_each(cc, &scratchpad_clients, link_temp) { ++ if (cc == c) ++ return; ++ } ++ if (!c->isfloating) { ++ setfloating(c, 1); ++ } ++ wl_list_insert(&scratchpad_clients, &c->link_temp); ++} ++ ++void ++togglescratchpad(const Arg *arg) ++{ ++ Client *c; ++ Monitor *m = selmon; ++ ++ scratchpad_visible = !scratchpad_visible; ++ if (scratchpad_visible) { ++ wl_list_for_each(c, &scratchpad_clients, link_temp) { ++ c->mon = m; ++ c->tags = m->tagset[m->seltags]; ++ arrange(m); ++ focusclient(c, 1); ++ } ++ } else { ++ wl_list_for_each(c, &scratchpad_clients, link_temp) { ++ c->tags = 0; ++ focusclient(focustop(m), 1); ++ arrange(m); ++ } ++ } ++} ++ ++void ++removescratchpad(const Arg *arg) ++{ ++ Client *sc, *c = focustop(selmon); ++ ++ if (c && wl_list_length(&scratchpad_clients) > 0) { ++ /* Check if c is in scratchpad_clients */ ++ wl_list_for_each(sc, &scratchpad_clients, link_temp) { ++ if (sc == c) { ++ wl_list_remove(&c->link_temp); ++ break; ++ } ++ } ++ } ++} +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/simpleborders/README.md b/dwl/dwl-patches/patches/simpleborders/README.md new file mode 100644 index 0000000..29dcf5e --- /dev/null +++ b/dwl/dwl-patches/patches/simpleborders/README.md @@ -0,0 +1,13 @@ +### Description + +Like smartborders. Don't put borders when there is only one window on the screen. + +The patch for tag v0.7 below appears to apply cleanly to the current HEAD of +upstream/main as at 2024-10-11. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/simpleborders) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simpleborders/simpleborders-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simpleborders/simpleborders-v0.6.patch) +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch b/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch new file mode 100644 index 0000000..a4797bb --- /dev/null +++ b/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch @@ -0,0 +1,73 @@ +From 681e520deaeee460647de36f5312af3cb0c31f4a Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] simpleborders + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 4d19357..900e651 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -245,6 +245,7 @@ static void cleanupmon(struct wl_listener *listener, void *data); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -286,6 +287,7 @@ static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int needsborder(Client *c); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -739,6 +741,17 @@ commitnotify(struct wl_listener *listener, void *data) + c->resize = 0; + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -1697,6 +1710,14 @@ moveresize(const Arg *arg) + } + } + ++int ++needsborder(Client *c) { ++ return ((countclients(c->mon) > 1 ++ && c->mon->lt[c->mon->sellt]->arrange != monocle) ++ || c->isfloating) ++ && !c->isfullscreen; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -1930,6 +1951,7 @@ resize(Client *c, struct wlr_box geo, int interact) + struct wlr_box clip; + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = needsborder(c) ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch b/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch new file mode 100644 index 0000000..e782d84 --- /dev/null +++ b/dwl/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch @@ -0,0 +1,73 @@ +From 09759c3ef75158c366e9fc63814485fbb31a3ccf Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] simpleborders + +--- + dwl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/dwl.c b/dwl.c +index a2711f67..415fe1a0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -259,6 +259,7 @@ static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void commitpopup(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -308,6 +309,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int needsborder(Client *c); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -849,6 +851,17 @@ commitpopup(struct wl_listener *listener, void *data) + wl_list_remove(&listener->link); + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -1927,6 +1940,14 @@ moveresize(const Arg *arg) + } + } + ++int ++needsborder(Client *c) { ++ return ((countclients(c->mon) > 1 ++ && c->mon->lt[c->mon->sellt]->arrange != monocle) ++ || c->isfloating) ++ && !c->isfullscreen; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2190,6 +2211,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = needsborder(c) ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/singlemaster/README.md b/dwl/dwl-patches/patches/singlemaster/README.md new file mode 100644 index 0000000..99cb1e6 --- /dev/null +++ b/dwl/dwl-patches/patches/singlemaster/README.md @@ -0,0 +1,9 @@ +### Description +Restricts layout to only having one client in the master area. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/singlemaster) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singlemaster/singlemaster.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/singlemaster/singlemaster.patch b/dwl/dwl-patches/patches/singlemaster/singlemaster.patch new file mode 100644 index 0000000..ffa6045 --- /dev/null +++ b/dwl/dwl-patches/patches/singlemaster/singlemaster.patch @@ -0,0 +1,155 @@ +From 610362d2f38260ad2a06ffbd91d089ba738429e9 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 18 Jul 2024 11:03:46 +1000 +Subject: [PATCH] singlemaster + +--- + config.def.h | 8 +++----- + dwl.c | 49 ++++++++++++++++++++++--------------------------- + 2 files changed, 25 insertions(+), 32 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..0ba9ddb8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -43,12 +43,12 @@ static const Layout layouts[] = { + */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ ++ /* name mfact scale layout rotate/reflect x y */ + /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { "eDP-1", 0.5f, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.55f, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + }; + + /* keyboard */ +@@ -129,8 +129,6 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, +- { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +- { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index 145fd018..6a3c84a7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -70,7 +70,6 @@ + + /* macros */ + #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 LENGTH(X) (sizeof X / sizeof X[0]) +@@ -204,7 +203,6 @@ struct Monitor { + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; +- int nmaster; + char ltsymbol[16]; + int asleep; + }; +@@ -212,7 +210,6 @@ struct Monitor { + typedef struct { + const char *name; + float mfact; +- int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; +@@ -288,7 +285,6 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +-static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -955,7 +951,6 @@ createmon(struct wl_listener *listener, void *data) + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; +- m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); +@@ -1467,15 +1462,6 @@ handlesig(int signo) + } + } + +-void +-incnmaster(const Arg *arg) +-{ +- if (!arg || !selmon) +- return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); +- arrange(selmon); +-} +- + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2635,8 +2621,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; +- int i, n = 0; ++ unsigned int i, n = 0, mw, ty; + Client *c; + + wl_list_for_each(c, &clients, link) +@@ -2645,21 +2630,31 @@ tile(Monitor *m) + if (n == 0) + return; + +- if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; +- else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = (n == 1) ? m->w.width : (int)roundf(m->w.width * m->mfact); ++ i = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ if (i == 0) { ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x, ++ .y = m->w.y, ++ .width = mw, ++ .height = (m->w.height) ++ }, ++ 0); + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ resize( ++ c, ++ (struct wlr_box){ ++ .x = m->w.x + mw, ++ .y = m->w.y + ty, ++ .width = m->w.width - mw, ++ .height = (m->w.height - ty) / (n - i) ++ }, ++ 0); + ty += c->geom.height; + } + i++; +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/singletagset-pertag/README.md b/dwl/dwl-patches/patches/singletagset-pertag/README.md new file mode 100644 index 0000000..13ab951 --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset-pertag/README.md @@ -0,0 +1,22 @@ +### Description +Pertag keeps layouts, mfact and nmaster per tag +instead of per output. + +This adapted version of pertag contains one version of +the rules per all outputs, instead of one per output. +This makes switching to tags from other monitors keep +the window layout. + +This patch expects [singletagset](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/singletagset) +patch to be already in your tree committed. It applies onto it. + +This patch is incompatible with [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag). + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset-pertag/singletagset-pertag.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset-pertag) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [wochap (maintainer of pertag patch)](https://codeberg.org/wochap) +- [Guido Cella (creator of pertag patch)](https://codeberg.org/guidocella) diff --git a/dwl/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch b/dwl/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch new file mode 100644 index 0000000..5fe4f47 --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch @@ -0,0 +1,261 @@ +From 4f6bdd3ea1fcc83abd962e9a2dc7737519164a6b Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 19 Jul 2024 16:25:46 +0200 +Subject: [PATCH] singletagset-pertag + +--- + config.def.h | 12 +++++-- + dwl.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 90 insertions(+), 12 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..7feb04d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -43,12 +43,18 @@ static const Layout layouts[] = { + */ + /* NOTE: ALWAYS add a fallback rule, even if you are completely sure it won't be used */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y */ ++ /* name scale rotate/reflect x y */ + /* example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { "eDP-1", 2, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 1, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++}; ++ ++static const TagRule tagrules[] = { ++ /* tag mfact nmaster layout */ ++ /* defaults */ ++ { 0, 0.55, 1, &layouts[0] } + }; + + /* keyboard */ +diff --git a/dwl.c b/dwl.c +index 40a6e48..465d9dd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -104,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct Pertag Pertag; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -209,6 +210,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ unsigned int pertag[2]; /* the tag used for layout via pertag */ + }; + + typedef struct { +@@ -226,6 +228,13 @@ typedef struct { + struct wl_listener destroy; + } PointerConstraint; + ++typedef struct { ++ unsigned int tag; ++ float mfact; ++ int nmaster; ++ const Layout *lt; ++} TagRule; ++ + typedef struct { + const char *id; + const char *title; +@@ -245,6 +254,7 @@ typedef struct { + + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); ++static void applypertag(Monitor *m); + static void applyrules(Client *c); + static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -293,6 +303,7 @@ 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 unsigned int getpertagtag(unsigned int curtagset); + static size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -415,6 +426,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static Pertag pertag; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -435,6 +447,13 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++struct Pertag { ++ int nmasters[TAGCOUNT + 1]; /* number of windows in master area */ ++ float mfacts[TAGCOUNT + 1]; /* mfacts per tag */ ++ unsigned int sellts[TAGCOUNT + 1]; /* selected layouts */ ++ const Layout *ltidxs[TAGCOUNT + 1][2]; /* matrix of tags and layouts indexes */ ++}; ++ + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -1023,6 +1042,33 @@ createlocksurface(struct wl_listener *listener, void *data) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); + } + ++unsigned int ++getpertagtag(unsigned int curtagset) ++{ ++ size_t i; ++ ++ if (curtagset == TAGMASK) { ++ return 0; ++ } ++ ++ if ((curtagset & TAGMASK) == 0) { ++ return 0; // What to do in this case? ++ } ++ ++ for (i = 0; !(curtagset & 1 << i); i++) ; ++ return i + 1; ++} ++ ++void ++applypertag(Monitor *m) ++{ ++ m->nmaster = pertag.nmasters[m->pertag[m->seltags]]; ++ m->mfact = pertag.mfacts[m->pertag[m->seltags]]; ++ m->sellt = pertag.sellts[m->pertag[m->seltags]]; ++ m->lt[m->sellt] = pertag.ltidxs[m->pertag[m->seltags]][m->sellt]; ++ m->lt[m->sellt^1] = pertag.ltidxs[m->pertag[m->seltags]][m->sellt^1]; ++} ++ + void + createmon(struct wl_listener *listener, void *data) + { +@@ -1046,14 +1092,12 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = (1<pertag[0] = m->pertag[1] = getpertagtag(m->tagset[0]); ++ applypertag(m); + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; +- m->mfact = r->mfact; +- m->nmaster = r->nmaster; +- m->lt[0] = r->lt; +- m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); +@@ -1597,7 +1641,7 @@ incnmaster(const Arg *arg) + { + if (!arg || !selmon) + return; +- selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); ++ selmon->nmaster = pertag.nmasters[selmon->pertag[selmon->seltags]] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); + } + +@@ -2433,9 +2477,9 @@ setlayout(const Arg *arg) + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +- selmon->sellt ^= 1; ++ selmon->sellt = pertag.sellts[selmon->pertag[selmon->seltags]] ^= 1; + if (arg && arg->v) +- selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ selmon->lt[selmon->sellt] = pertag.ltidxs[selmon->pertag[selmon->seltags]][selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2452,7 +2496,7 @@ setmfact(const Arg *arg) + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; +- selmon->mfact = f; ++ selmon->mfact = pertag.mfacts[selmon->pertag[selmon->seltags]] = f; + arrange(selmon); + } + +@@ -2504,6 +2548,10 @@ setsel(struct wl_listener *listener, void *data) + void + setup(void) + { ++ const TagRule *r; ++ struct xkb_context *context; ++ struct xkb_keymap *keymap; ++ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); +@@ -2596,6 +2644,19 @@ setup(void) + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + ++ for (i = 0; i <= TAGCOUNT; i++) { ++ for (r = tagrules; r < END(tagrules); r++) { ++ if (!r->tag || r->tag == i) { ++ pertag.mfacts[i] = r->mfact; ++ pertag.nmasters[i] = r->nmaster; ++ pertag.sellts[i] = 0; ++ pertag.ltidxs[i][0] = r->lt; ++ pertag.ltidxs[i][1] = r->lt; ++ break; ++ } ++ } ++ } ++ + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); +@@ -2857,6 +2918,12 @@ toggleview(const Arg *arg) + if (m !=selmon && newtagset & m->tagset[m->seltags]) + return; + ++ // set new pertag tag only if the tag we were at was removed, or if all tags are shown. ++ if (!(newtagset & 1 << (selmon->pertag[selmon->seltags] - 1)) || newtagset == TAGMASK) { ++ selmon->pertag[selmon->seltags] = getpertagtag(newtagset); ++ } ++ ++ applypertag(selmon); + selmon->tagset[selmon->seltags] = newtagset; + attachclients(selmon); + focusclient(focustop(selmon), 1); +@@ -3072,6 +3139,8 @@ view(const Arg *arg) + } + m->seltags ^= 1; + m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ m->pertag[m->seltags] = origm->pertag[origm->seltags]; ++ applypertag(m); + attachclients(m); + /* Beware: this changes selmon */ + focusclient(focustop(m), 1); +@@ -3081,11 +3150,14 @@ view(const Arg *arg) + } + + origm->seltags ^= 1; /* toggle sel tagset */ +- if (arg->ui & TAGMASK) ++ if (arg->ui & TAGMASK) { + origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ origm->pertag[origm->seltags] = getpertagtag(arg->ui & TAGMASK); ++ } + + /* Change selmon back to orig mon */ + selmon = origm; ++ applypertag(origm); + attachclients(origm); + focusclient(focustop(origm), 1); + arrange(origm); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/singletagset-sticky/README.md b/dwl/dwl-patches/patches/singletagset-sticky/README.md new file mode 100644 index 0000000..f1f9fd3 --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset-sticky/README.md @@ -0,0 +1,13 @@ +### Description +Makes sticky work as expected with singletagset. The sticky window will +stay on original output until you explicitely put it to a different monitor. + +This patch expects both [singletagset](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/singletagset) and [sticky](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/sticky) patches to be already in +your tree committed. It applies onto them. + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset-sticky/singletagset-sticky.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset-sticky) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) diff --git a/dwl/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch b/dwl/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch new file mode 100644 index 0000000..ce6a40c --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch @@ -0,0 +1,27 @@ +From a37d65c8601a7c5b03c53bb99956da8a24952628 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 19 Jul 2024 16:51:26 +0200 +Subject: [PATCH] singletagset-sticky + +--- + dwl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 59e56bd..fbbdff9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -73,8 +73,8 @@ + #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)->issticky) +-#define SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) ++#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((((C)->tags & (M)->tagset[(M)->seltags])) || (C)->issticky)) ++#define SVISIBLEON(C, M) ((M) && (C)->mon && (((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->issticky) || ((C)->issticky && (C)->mon == (M))) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/singletagset/README.md b/dwl/dwl-patches/patches/singletagset/README.md new file mode 100644 index 0000000..920d8a4 --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset/README.md @@ -0,0 +1,19 @@ +### Description +Single set of tags shared between multiple monitors. + +This patch allows all the tags to be shared between both (or more) monitors. +So a single set of tags from 1 to 9 can be viewed on any monitor, as opposed to +having separate tag sets 1 to 9 on each monitor. + +Originally based on the dwm single_tagset patch: +https://dwm.suckless.org/patches/single_tagset/ + +### Download +- [git branch (v0.7)](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/singletagset) +- [git branch (v0.6)](https://codeberg.org/bencc/dwl/src/branch/singletagset) +- [2024-07-26 (v0.7)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset/singletagset-v0.7.patch) +- [2024-05-16 (v0.6)](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/singletagset/singletagset-v0.6.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Rutherther](https://codeberg.org/Rutherther) diff --git a/dwl/dwl-patches/patches/singletagset/singletagset-v0.6.patch b/dwl/dwl-patches/patches/singletagset/singletagset-v0.6.patch new file mode 100644 index 0000000..708774b --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset/singletagset-v0.6.patch @@ -0,0 +1,293 @@ +From 140fe587bf34cf43e44acaf365b16ec3e385f742 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 16 May 2024 09:56:58 +1000 +Subject: [PATCH] singletagset + +--- + dwl.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 140 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index bf763dfc..5b1d594f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -72,6 +72,7 @@ + #define ROUND(X) ((int)((X < 0) ? (X - 0.5) : (X + 0.5))) + #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) ++#define SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +@@ -246,6 +247,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void attachclients(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -283,6 +285,7 @@ static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); ++static size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -470,7 +473,18 @@ applyrules(Client *c) + } + } + } ++ wlr_scene_node_reparent(&c->scene->node, layers[c->isfloating ? LyrFloat : LyrTile]); ++ ++ wl_list_for_each(m, &mons, link) { ++ // tag with different monitor selected by rules ++ if (m->tagset[m->seltags] & newtags) { ++ mon = m; ++ break; ++ } ++ } ++ + setmon(c, mon, newtags); ++ attachclients(mon); + } + + void +@@ -558,6 +572,45 @@ arrangelayers(Monitor *m) + } + } + ++void ++attachclients(Monitor *m) ++{ ++ Monitor *tm; ++ unsigned int utags = 0; ++ Client *c; ++ int rmons = 0; ++ ++ if (m == NULL) { ++ return; ++ } ++ ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ utags |= tm->tagset[tm->seltags]; ++ } ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (SVISIBLEON(c, m)) { ++ /* if client is also visible on other tags that are displayed on ++ * other monitors, remove these tags */ ++ if (c->tags & utags) { ++ c->tags = c->tags & m->tagset[m->seltags]; ++ rmons = 1; ++ } ++ setmon(c, m, c->tags); ++ } ++ } ++ ++ if (rmons) { ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ arrange(tm); ++ } ++ } ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -881,7 +934,7 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ +- m->tagset[0] = m->tagset[1] = 1; ++ m->tagset[0] = m->tagset[1] = (1<name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -1354,7 +1407,7 @@ focustop(Monitor *m) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (SVISIBLEON(c, m)) + return c; + } + return NULL; +@@ -1367,6 +1420,29 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++size_t ++getunusedtag(void) ++{ ++ size_t i = 0; ++ Monitor *m; ++ if (wl_list_empty(&mons)) { ++ return i; ++ } ++ for (i=0; i < TAGCOUNT; i++) { ++ int is_used = 0; ++ wl_list_for_each(m, &mons, link) { ++ if ((m->tagset[m->seltags] & (1<mon != m) +- continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; +@@ -2551,6 +2625,7 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; +@@ -2558,15 +2633,25 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ wl_list_for_each(m, &mons, link) { ++ attachclients(m); ++ arrange(m); ++ } + printstatus(); + } + + void + tagmon(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); +- if (sel) ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ wl_list_for_each(m, &mons, link) { ++ arrange(m); ++ } ++ focusclient(focustop(sel->mon), 1); ++ } + } + + void +@@ -2623,12 +2708,18 @@ togglefullscreen(const Arg *arg) + void + toggletag(const Arg *arg) + { ++ Monitor *m; + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m != selmon && newtags & m->tagset[m->seltags]) ++ return; ++ + sel->tags = newtags; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2637,11 +2728,17 @@ toggletag(const Arg *arg) + void + toggleview(const Arg *arg) + { ++ Monitor *m; + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m !=selmon && newtagset & m->tagset[m->seltags]) ++ return; ++ + selmon->tagset[selmon->seltags] = newtagset; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2759,6 +2856,7 @@ updatemons(struct wl_listener *listener, void *data) + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + ++ attachclients(m); + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ +@@ -2825,13 +2923,45 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { +- if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) ++ Monitor *m, *origm = selmon; ++ unsigned int newtags; ++ ++ if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) { + return; +- selmon->seltags ^= 1; /* toggle sel tagset */ ++ } ++ ++ newtags = origm->tagset[origm->seltags ^ 1]; ++ ++ /* swap tags when trying to display a tag from another monitor */ ++ if (arg->ui & TAGMASK) { ++ newtags = arg->ui & TAGMASK; ++ } ++ wl_list_for_each(m, &mons, link) { ++ if (m != origm && newtags & m->tagset[m->seltags]) { ++ /* prevent displaying all tags (MODKEY-0) when multiple monitors ++ * are connected */ ++ if (newtags & origm->tagset[origm->seltags]) { ++ return; ++ } ++ m->seltags ^= 1; ++ m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ attachclients(m); ++ /* Beware: this changes selmon */ ++ focusclient(focustop(m), 1); ++ arrange(m); ++ break; ++ } ++ } ++ ++ origm->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) +- selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); +- arrange(selmon); ++ origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ ++ /* Change selmon back to orig mon */ ++ selmon = origm; ++ attachclients(origm); ++ focusclient(focustop(origm), 1); ++ arrange(origm); + printstatus(); + } + +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/singletagset/singletagset-v0.7.patch b/dwl/dwl-patches/patches/singletagset/singletagset-v0.7.patch new file mode 100644 index 0000000..9c2f98b --- /dev/null +++ b/dwl/dwl-patches/patches/singletagset/singletagset-v0.7.patch @@ -0,0 +1,312 @@ +From bd90ec504369a69dbefc34cb72265d15c7795944 Mon Sep 17 00:00:00 2001 +From: Rutherther +Date: Fri, 26 Jul 2024 12:36:41 +0200 +Subject: [PATCH] singletagset + +--- + dwl.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 145 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5bf995e..40a6e48 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -74,6 +74,7 @@ + #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 SVISIBLEON(C, M) ((M) && (C)->mon && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) +@@ -249,6 +250,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void attachclients(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -291,6 +293,7 @@ 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 size_t getunusedtag(void); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -478,7 +481,17 @@ applyrules(Client *c) + } + } + } ++ ++ wl_list_for_each(m, &mons, link) { ++ // tag with different monitor selected by rules ++ if (m->tagset[m->seltags] & newtags) { ++ mon = m; ++ break; ++ } ++ } ++ + setmon(c, mon, newtags); ++ attachclients(mon); + } + + void +@@ -580,6 +593,45 @@ arrangelayers(Monitor *m) + } + } + ++void ++attachclients(Monitor *m) ++{ ++ Monitor *tm; ++ unsigned int utags = 0; ++ Client *c; ++ int rmons = 0; ++ ++ if (m == NULL) { ++ return; ++ } ++ ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ utags |= tm->tagset[tm->seltags]; ++ } ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (SVISIBLEON(c, m)) { ++ /* if client is also visible on other tags that are displayed on ++ * other monitors, remove these tags */ ++ if (c->tags & utags) { ++ c->tags = c->tags & m->tagset[m->seltags]; ++ rmons = 1; ++ } ++ setmon(c, m, c->tags); ++ } ++ } ++ ++ if (rmons) { ++ wl_list_for_each(tm, &mons, link) { ++ if (tm != m) { ++ arrange(tm); ++ } ++ } ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -750,6 +802,9 @@ closemon(Monitor *m) + if (c->mon == m) + setmon(c, selmon, c->tags); + } ++ ++ m->tagset[0] = m->tagset[1] = 0; ++ + focusclient(focustop(selmon), 1); + printstatus(); + } +@@ -990,7 +1045,7 @@ createmon(struct wl_listener *listener, void *data) + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ +- m->tagset[0] = m->tagset[1] = 1; ++ m->tagset[0] = m->tagset[1] = (1<name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -1456,7 +1511,7 @@ focustop(Monitor *m) + { + Client *c; + wl_list_for_each(c, &fstack, flink) { +- if (VISIBLEON(c, m)) ++ if (SVISIBLEON(c, m)) + return c; + } + return NULL; +@@ -1469,6 +1524,29 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++size_t ++getunusedtag(void) ++{ ++ size_t i = 0; ++ Monitor *m; ++ if (wl_list_empty(&mons)) { ++ return i; ++ } ++ for (i=0; i < TAGCOUNT; i++) { ++ int is_used = 0; ++ wl_list_for_each(m, &mons, link) { ++ if ((m->tagset[m->seltags] & (1<mon != m) +- continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; +@@ -2669,6 +2745,7 @@ startdrag(struct wl_listener *listener, void *data) + void + tag(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; +@@ -2676,15 +2753,25 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); ++ wl_list_for_each(m, &mons, link) { ++ attachclients(m); ++ arrange(m); ++ } + printstatus(); + } + + void + tagmon(const Arg *arg) + { ++ Monitor *m; + Client *sel = focustop(selmon); +- if (sel) ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ wl_list_for_each(m, &mons, link) { ++ arrange(m); ++ } ++ focusclient(focustop(sel->mon), 1); ++ } + } + + void +@@ -2741,12 +2828,18 @@ togglefullscreen(const Arg *arg) + void + toggletag(const Arg *arg) + { ++ Monitor *m; + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m != selmon && newtags & m->tagset[m->seltags]) ++ return; ++ + sel->tags = newtags; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2755,11 +2848,17 @@ toggletag(const Arg *arg) + void + toggleview(const Arg *arg) + { ++ Monitor *m; + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + ++ wl_list_for_each(m, &mons, link) ++ if (m !=selmon && newtagset & m->tagset[m->seltags]) ++ return; ++ + selmon->tagset[selmon->seltags] = newtagset; ++ attachclients(selmon); + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); +@@ -2863,6 +2962,9 @@ updatemons(struct wl_listener *listener, void *data) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + ++ if ((m->tagset[0] & TAGMASK) == 0 && (m->tagset[1] & TAGMASK) == 0) ++ m->tagset[0] = m->tagset[1] = (1 << getunusedtag()) & TAGMASK; ++ + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; +@@ -2877,6 +2979,7 @@ updatemons(struct wl_listener *listener, void *data) + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + ++ attachclients(m); + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ +@@ -2947,13 +3050,45 @@ urgent(struct wl_listener *listener, void *data) + void + view(const Arg *arg) + { +- if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) ++ Monitor *m, *origm = selmon; ++ unsigned int newtags; ++ ++ if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) { + return; +- selmon->seltags ^= 1; /* toggle sel tagset */ ++ } ++ ++ newtags = origm->tagset[origm->seltags ^ 1]; ++ ++ /* swap tags when trying to display a tag from another monitor */ ++ if (arg->ui & TAGMASK) { ++ newtags = arg->ui & TAGMASK; ++ } ++ wl_list_for_each(m, &mons, link) { ++ if (m != origm && newtags & m->tagset[m->seltags]) { ++ /* prevent displaying all tags (MODKEY-0) when multiple monitors ++ * are connected */ ++ if (newtags & origm->tagset[origm->seltags]) { ++ return; ++ } ++ m->seltags ^= 1; ++ m->tagset[m->seltags] = origm->tagset[origm->seltags]; ++ attachclients(m); ++ /* Beware: this changes selmon */ ++ focusclient(focustop(m), 1); ++ arrange(m); ++ break; ++ } ++ } ++ ++ origm->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) +- selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; +- focusclient(focustop(selmon), 1); +- arrange(selmon); ++ origm->tagset[origm->seltags] = arg->ui & TAGMASK; ++ ++ /* Change selmon back to orig mon */ ++ selmon = origm; ++ attachclients(origm); ++ focusclient(focustop(origm), 1); ++ arrange(origm); + printstatus(); + } + +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/skipfocus/README.md b/dwl/dwl-patches/patches/skipfocus/README.md new file mode 100644 index 0000000..4fb6e8a --- /dev/null +++ b/dwl/dwl-patches/patches/skipfocus/README.md @@ -0,0 +1,18 @@ +### Description +Adds a rule-based ability to skip automatically focusing a window on creation. Expected use-case is for transient windows like notifications etc. The window can still be focused by mouse or keyboard movement. + +| `skipfocus` value | effect | +| ----------------- | ------------------ | +| 0 | usual | +| 1 | skipautofocus | +| 2 | skipfocus entirely | + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus.patch) +- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus-20240714.patch) +- [2024-01-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/skipfocus/skipfocus-20240108.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/skipfocus) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) + diff --git a/dwl/dwl-patches/patches/skipfocus/skipfocus-20240714.patch b/dwl/dwl-patches/patches/skipfocus/skipfocus-20240714.patch new file mode 100644 index 0000000..2715fb9 --- /dev/null +++ b/dwl/dwl-patches/patches/skipfocus/skipfocus-20240714.patch @@ -0,0 +1,81 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..9f6599c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 145fd01..ec9d1af 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,7 +138,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -229,6 +229,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -465,6 +466,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1323,6 +1325,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -1692,11 +1701,13 @@ mapnotify(struct wl_listener *listener, void *data) + printstatus(); + + unset_fullscreen: +- m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +- wl_list_for_each(w, &clients, link) { +- if (w != c && w->isfullscreen && m == w->mon && (w->tags & c->tags)) +- setfullscreen(w, 0); +- } ++ if (!c->skipfocus) { ++ m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); ++ wl_list_for_each(w, &clients, link) { ++ if (w != c && w->isfullscreen && m == w->mon && (w->tags & c->tags)) ++ setfullscreen(w, 0); ++ } ++ } + } + + void diff --git a/dwl/dwl-patches/patches/skipfocus/skipfocus.patch b/dwl/dwl-patches/patches/skipfocus/skipfocus.patch new file mode 100644 index 0000000..ed445ec --- /dev/null +++ b/dwl/dwl-patches/patches/skipfocus/skipfocus.patch @@ -0,0 +1,81 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..9f6599c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0c861..b6a4cbb 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,7 +141,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -231,6 +231,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -466,6 +467,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1343,6 +1345,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -1738,11 +1747,13 @@ mapnotify(struct wl_listener *listener, void *data) + printstatus(); + + unset_fullscreen: +- m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +- wl_list_for_each(w, &clients, link) { +- if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) +- setfullscreen(w, 0); +- } ++ if (!c->skipfocus) { ++ m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); ++ wl_list_for_each(w, &clients, link) { ++ if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) ++ setfullscreen(w, 0); ++ } ++ } + } + + void diff --git a/dwl/dwl-patches/patches/skipfocus/skipfocus20240108.patch b/dwl/dwl-patches/patches/skipfocus/skipfocus20240108.patch new file mode 100644 index 0000000..10d5f4f --- /dev/null +++ b/dwl/dwl-patches/patches/skipfocus/skipfocus20240108.patch @@ -0,0 +1,62 @@ +diff --git a/config.def.h b/config.def.h +index a784eb4..5d3a4f9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,10 +21,11 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating skipfocus monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "mako_EXAMPLE", NULL, 0, 1, 1, -1 }, /* Start floating and skip focus + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 6f041a0..90ac57b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -137,7 +137,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, skipfocus; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -228,6 +228,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int skipfocus; + int monitor; + } Rule; + +@@ -464,6 +465,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->skipfocus = r->skipfocus; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -1307,6 +1309,13 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ if (c && c->skipfocus != 0){ ++ if (c -> skipfocus == 1) { ++ c->skipfocus = 0; ++ } ++ return; ++ } ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); diff --git a/dwl/dwl-patches/patches/smartborders/README.md b/dwl/dwl-patches/patches/smartborders/README.md new file mode 100644 index 0000000..6fe0e59 --- /dev/null +++ b/dwl/dwl-patches/patches/smartborders/README.md @@ -0,0 +1,14 @@ +### Description +The borders of a window aren't drawn when the window is the only tiling window +in its tag OR if the window is in a monocle layout. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/smartborders) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/smartborders/smartborders.patch) +- [smartborders-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/smartborders/smartborders-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [fauxmight](https://codeberg.org/fauxmight) +- [Piotr Marendowski](https://github.com/piotr-marendowski) +- Andrey Proskurin diff --git a/dwl/dwl-patches/patches/smartborders/smartborders-0.7.patch b/dwl/dwl-patches/patches/smartborders/smartborders-0.7.patch new file mode 100644 index 0000000..732f06f --- /dev/null +++ b/dwl/dwl-patches/patches/smartborders/smartborders-0.7.patch @@ -0,0 +1,204 @@ +From b10e044d95072994d9b00b31ea70051ab02a026b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 16 Aug 2022 15:28:00 -0500 +Subject: [PATCH] don't draw borders if there is only one window +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: A Frederick Christensen +Co-authored-by: Andrey Proskurin +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 47 +++++++++++++++++++++++++++++++---------------- + 2 files changed, 32 insertions(+), 16 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..632f151f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartborders = 1; + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index a2711f67..647ce38c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -320,7 +320,7 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resize(Client *c, struct wlr_box geo, int interact, int draw_borders); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -746,7 +746,7 @@ closemon(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +- .width = c->geom.width, .height = c->geom.height}, 0); ++ .width = c->geom.width, .height = c->geom.height}, 0, 1); + if (c->mon == m) + setmon(c, selmon, c->tags); + } +@@ -814,8 +814,11 @@ commitnotify(struct wl_listener *listener, void *data) + return; + } + +- if (client_surface(c)->mapped && c->mon) +- resize(c, c->geom, (c->isfloating && !c->isfullscreen)); ++ if (client_surface(c)->mapped && c->mon && c->mon->lt[c->mon->sellt]->arrange ++ && !c->isfullscreen && !c->isfloating) ++ c->mon->lt[c->mon->sellt]->arrange(c->mon); ++ else ++ resize(c, c->geom, (c->isfloating && !c->isfullscreen), (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) +@@ -1775,7 +1778,7 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ resize(c, m->w, 0, !smartborders); + n++; + } + if (n) +@@ -1867,11 +1870,11 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); ++ .width = grabc->geom.width, .height = grabc->geom.height}, 1, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1, 1); + return; + } + +@@ -2178,7 +2181,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resize(Client *c, struct wlr_box geo, int interact, int draw_borders) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2190,6 +2193,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +@@ -2314,6 +2318,8 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); ++ if (c->isfloating && !c->bw) ++ resize(c, c->mon->m, 0, 1); + arrange(c->mon); + printstatus(); + } +@@ -2331,11 +2337,11 @@ setfullscreen(Client *c, int fullscreen) + + if (fullscreen) { + c->prev = c->geom; +- resize(c, c->mon->m, 0); ++ resize(c, c->mon->m, 0, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ +- resize(c, c->prev, 0); ++ resize(c, c->prev, 0, 1); + } + arrange(c->mon); + printstatus(); +@@ -2362,6 +2368,12 @@ setlayout(const Arg *arg) + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ if (!selmon->lt[selmon->sellt]->arrange) { ++ /* floating layout, draw borders around all clients */ ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ resize(c, c->mon->m, 0, 1); ++ } + arrange(selmon); + printstatus(); + } +@@ -2396,7 +2408,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ +- resize(c, c->geom, 0); ++ resize(c, c->geom, 0, 1); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); +@@ -2691,7 +2703,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, draw_borders = 1; + int i, n = 0; + Client *c; + +@@ -2701,6 +2713,9 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (n == smartborders) ++ draw_borders = 0; ++ + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else +@@ -2711,11 +2726,11 @@ tile(Monitor *m) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0, draw_borders); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0, draw_borders); + ty += c->geom.height; + } + i++; +@@ -2884,7 +2899,7 @@ updatemons(struct wl_listener *listener, void *data) + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) +- resize(c, m->m, 0); ++ resize(c, m->m, 0, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ +@@ -3087,7 +3102,7 @@ configurex11(struct wl_listener *listener, void *data) + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, +- .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); ++ .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0, 1); + else + arrange(c->mon); + } +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/smartborders/smartborders.patch b/dwl/dwl-patches/patches/smartborders/smartborders.patch new file mode 100644 index 0000000..5eac3ee --- /dev/null +++ b/dwl/dwl-patches/patches/smartborders/smartborders.patch @@ -0,0 +1,203 @@ +From 38010bdecf63bdb8acfc584825b398838310eed1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Tue, 16 Aug 2022 15:28:00 -0500 +Subject: [PATCH] don't draw borders if there is only one window +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Co-authored-by: A Frederick Christensen +Co-authored-by: Andrey Proskurin +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 46 +++++++++++++++++++++++++++++++--------------- + 2 files changed, 32 insertions(+), 15 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..632f151f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartborders = 1; + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); +diff --git a/dwl.c b/dwl.c +index ad21e1ba..0c56431a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -317,7 +317,7 @@ static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +-static void resize(Client *c, struct wlr_box geo, int interact); ++static void resize(Client *c, struct wlr_box geo, int interact, int draw_borders); + static void run(char *startup_cmd); + static void setcursor(struct wl_listener *listener, void *data); + static void setcursorshape(struct wl_listener *listener, void *data); +@@ -803,7 +803,7 @@ closemon(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +- .width = c->geom.width, .height = c->geom.height}, 0); ++ .width = c->geom.width, .height = c->geom.height}, 0, 1); + if (c->mon == m) + setmon(c, selmon, c->tags); + } +@@ -872,7 +872,11 @@ commitnotify(struct wl_listener *listener, void *data) + return; + } + +- resize(c, c->geom, (c->isfloating && !c->isfullscreen)); ++ if (client_surface(c)->mapped && c->mon && c->mon->lt[c->mon->sellt]->arrange ++ && !c->isfullscreen && !c->isfloating) ++ c->mon->lt[c->mon->sellt]->arrange(c->mon); ++ else ++ resize(c, c->geom, (c->isfloating && !c->isfullscreen), (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) +@@ -1819,7 +1823,7 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ resize(c, m->w, 0, !smartborders); + n++; + } + if (n) +@@ -1911,11 +1915,11 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); ++ .width = grabc->geom.width, .height = grabc->geom.height}, 1, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1, 1); + return; + } + +@@ -2194,7 +2198,7 @@ requestmonstate(struct wl_listener *listener, void *data) + } + + void +-resize(Client *c, struct wlr_box geo, int interact) ++resize(Client *c, struct wlr_box geo, int interact, int draw_borders) + { + struct wlr_box *bbox; + struct wlr_box clip; +@@ -2206,6 +2210,7 @@ resize(Client *c, struct wlr_box geo, int interact) + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ +@@ -2330,6 +2335,8 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); ++ if (c->isfloating && !c->bw) ++ resize(c, c->mon->m, 0, 1); + arrange(c->mon); + printstatus(); + } +@@ -2347,11 +2354,11 @@ setfullscreen(Client *c, int fullscreen) + + if (fullscreen) { + c->prev = c->geom; +- resize(c, c->mon->m, 0); ++ resize(c, c->mon->m, 0, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ +- resize(c, c->prev, 0); ++ resize(c, c->prev, 0, 1); + } + arrange(c->mon); + printstatus(); +@@ -2367,6 +2374,12 @@ setlayout(const Arg *arg) + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ if (!selmon->lt[selmon->sellt]->arrange) { ++ /* floating layout, draw borders around all clients */ ++ Client *c; ++ wl_list_for_each(c, &clients, link) ++ resize(c, c->mon->m, 0, 1); ++ } + arrange(selmon); + printstatus(); + } +@@ -2401,7 +2414,7 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ +- resize(c, c->geom, 0); ++ resize(c, c->geom, 0, 1); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); +@@ -2701,7 +2714,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, draw_borders = 1; + int i, n = 0; + Client *c; + +@@ -2711,6 +2724,9 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (n == smartborders) ++ draw_borders = 0; ++ + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; + else +@@ -2721,11 +2737,11 @@ tile(Monitor *m) + continue; + if (i < m->nmaster) { + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); ++ .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0, draw_borders); + my += c->geom.height; + } else { + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); ++ .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0, draw_borders); + ty += c->geom.height; + } + i++; +@@ -2894,7 +2910,7 @@ updatemons(struct wl_listener *listener, void *data) + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) +- resize(c, m->m, 0); ++ resize(c, m->m, 0, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ +@@ -3103,7 +3119,7 @@ configurex11(struct wl_listener *listener, void *data) + if ((c->isfloating && c != grabc) || !c->mon->lt[c->mon->sellt]->arrange) { + resize(c, (struct wlr_box){.x = event->x - c->bw, + .y = event->y - c->bw, .width = event->width + c->bw * 2, +- .height = event->height + c->bw * 2}, 0); ++ .height = event->height + c->bw * 2}, 0, 1); + } else { + arrange(c->mon); + } +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/snail-gaps/README.md b/dwl/dwl-patches/patches/snail-gaps/README.md new file mode 100644 index 0000000..ba26f13 --- /dev/null +++ b/dwl/dwl-patches/patches/snail-gaps/README.md @@ -0,0 +1,15 @@ +### Description + +Adds support for the [gaps patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) +to the [snail layout patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/snail). + +Install the [gaps patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) +and the [snail patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/snail) first. + +### Download + +- [main 2024-08-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail-gaps/snail-gaps.patch) + +### Authors + +- [JoaoCostaIFG](https://codeberg.org/JoaoCostaIFG) ([joaocosta@posteo.net](mailto:joaocosta@posteo.net)) diff --git a/dwl/dwl-patches/patches/snail-gaps/snail-gaps.patch b/dwl/dwl-patches/patches/snail-gaps/snail-gaps.patch new file mode 100644 index 0000000..51d2812 --- /dev/null +++ b/dwl/dwl-patches/patches/snail-gaps/snail-gaps.patch @@ -0,0 +1,160 @@ +From 804e69ca4bf586bcec46e018630f94c1c4e0b7e7 Mon Sep 17 00:00:00 2001 +From: JoaoCostaIFG +Date: Thu, 8 Aug 2024 00:05:27 +0100 +Subject: [PATCH 2/2] Add gaps support to snail layout + +--- + dwl.c | 105 +++++++++++++++++++++++++++++++++------------------------- + 1 file changed, 59 insertions(+), 46 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 46bdca1..a158dd6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2659,8 +2659,8 @@ void + snail(Monitor *m) + { + int i = 0, n = 0; +- unsigned int mw = m->w.width; +- Client *c, *prev; ++ unsigned int mw = m->w.width, e = m->gaps, w = 0, h = 0, egappx = 0; ++ Client *c, *prev = NULL; + enum wlr_direction dir = WLR_DIRECTION_RIGHT; + + wl_list_for_each(c, &clients, link) +@@ -2668,9 +2668,12 @@ snail(Monitor *m) + n++; + if (n == 0) + return; ++ if (smartgaps == n) ++ e = 0; ++ egappx = e * gappx; + + if (n > m->nmaster) +- mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (unsigned int)round((m->w.width + egappx) * m->mfact) : 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) +@@ -2681,8 +2684,8 @@ snail(Monitor *m) + * master area with this window + */ + if (mw > 0 && i == 0) { +- c->geom = (struct wlr_box){.x = m->w.x, .y = m->w.y, +- .width = mw, .height = m->w.height}; ++ c->geom = (struct wlr_box){.x = m->w.x + egappx, .y = m->w.y + egappx, ++ .width = mw - 2*egappx, .height = m->w.height - 2*egappx}; + /* + * If the first window in the master area is wide, split it + * horizontally and put next one on its right; otherwise, split it +@@ -2694,55 +2697,65 @@ snail(Monitor *m) + * m->nmaster-th window + */ + } else if (i == m->nmaster) { +- c->geom = (struct wlr_box){.x = m->w.x + mw, .y = m->w.y, +- .width = m->w.width - mw, .height = m->w.height}; ++ c->geom = (struct wlr_box){.x = m->w.x + mw + egappx, .y = m->w.y + egappx, ++ .width = m->w.width - mw - 2*egappx, .height = m->w.height - 2*egappx}; + /* + * If the first window in the stack is wide, split it horizontally + * and put next one on its right; otherwise, split it vertically and + * put the next one below it + */ + dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; +- /* +- * Split the previous horizontally and put the current window on the right +- */ +- } else if (dir == WLR_DIRECTION_RIGHT) { +- c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; ++ } else if (prev) { + /* +- * If it's a stack window or the first narrow window in the master +- * area, put the next one below it ++ * Split the previous horizontally and put the current window on the right + */ +- if (i >= m->nmaster || c->geom.width < m->w.height) +- dir = WLR_DIRECTION_DOWN; +- /* +- * Split the previous vertically and put the current window below it +- */ +- } else if (dir == WLR_DIRECTION_DOWN) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- dir = WLR_DIRECTION_LEFT; +- /* +- * Split the previous horizontally and put the current window on the left +- */ +- } else if (dir == WLR_DIRECTION_LEFT) { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, +- .width = prev->geom.width / 2, .height = prev->geom.height}; +- dir = WLR_DIRECTION_UP; +- /* +- * Split the previous vertically and put the current window above it +- */ +- } else { +- c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, +- .width = prev->geom.width, .height = prev->geom.height / 2}; +- dir = WLR_DIRECTION_RIGHT; ++ if (dir == WLR_DIRECTION_RIGHT) { ++ w = prev->geom.width / 2 - egappx; ++ h = prev->geom.height; ++ c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2 + egappx, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ /* ++ * If it's a stack window or the first narrow window in the master ++ * area, put the next one below it ++ */ ++ if (i >= m->nmaster || c->geom.width < m->w.height) ++ dir = WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous vertically and put the current window below it ++ */ ++ } else if (dir == WLR_DIRECTION_DOWN) { ++ w = prev->geom.width; ++ h = prev->geom.height / 2 - egappx; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2 + egappx, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_LEFT; ++ /* ++ * Split the previous horizontally and put the current window on the left ++ */ ++ } else if (dir == WLR_DIRECTION_LEFT) { ++ w = prev->geom.width / 2 - egappx; ++ h = prev->geom.height; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2 + egappx, .y = prev->geom.y, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_UP; ++ /* ++ * Split the previous vertically and put the current window above it ++ */ ++ } else { ++ w = prev->geom.width; ++ h = prev->geom.height / 2 - egappx; ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = w, .height = h}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2 + egappx, ++ .width = w, .height = h}; ++ dir = WLR_DIRECTION_RIGHT; ++ } + } + i++; + prev = c; +-- +2.46.0 + + diff --git a/dwl/dwl-patches/patches/snail/README.md b/dwl/dwl-patches/patches/snail/README.md new file mode 100644 index 0000000..0c94b79 --- /dev/null +++ b/dwl/dwl-patches/patches/snail/README.md @@ -0,0 +1,9 @@ +### Description +Adds a spiral-inspired layout for wide screens. + +### Download +- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail/snail.patch) + +### Authors +- [Dima Krasner](https://codeberg.org/dimkr) () +- [Nikita Ivanov](https://github.com/NikitaIvanovV) (fix for flickering) diff --git a/dwl/dwl-patches/patches/snail/snail.patch b/dwl/dwl-patches/patches/snail/snail.patch new file mode 100644 index 0000000..061030a --- /dev/null +++ b/dwl/dwl-patches/patches/snail/snail.patch @@ -0,0 +1,250 @@ +From 39b6da6082db641191699be906e485f8c8fbc4e6 Mon Sep 17 00:00:00 2001 +From: Dima Krasner +Date: Sun, 11 Feb 2024 09:09:16 +0200 +Subject: [PATCH] add the snail layout +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This layout is a scalable alternative to the "tile" and "spiral" layouts, optimized for wide monitors. Both the master area and the stack are "spirals", but windows in the master area are split horizontally as long as the master area has enough horizontal space, and the first window in the stack is split vertically unless the stack is wide. + +With one window in the master area and mfact = 0.5, it behaves like the spiral layout: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +│ ├───┬───┬────────┤ +│ │ │ │ │ +│ ├───┴───┤ │ +│ │ │ │ +│ │ │ │ +└───────────────┴───────┴────────┘ + +With 2 windows in the master area and 2 in the stack: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───────────────┼────────────────┤ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +└───────────────┴────────────────┘ + +With 3 windows in the master area and 2 in the stack: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───────┬───────┼────────────────┤ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +└───────┴───────┴────────────────┘ + +With many windows in both areas: + +┌───────────────┬────────────────┐ +│ │ │ +│ │ │ +│ │ │ +│ │ │ +├───┬───┬───────┼───┬───┬────────┤ +│ │ │ │ │ │ │ +├───┴───┤ ├───┴───┤ │ +│ │ │ │ │ +│ │ │ │ │ +└───────┴───────┴───────┴────────┘ + +With 2 windows in the master area, many windows in the stack and high mfact: + +┌──────────┬──────────┬──────────┐ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ master ├──┬stack──┤ +│ │ │ │ │ │ +│ │ ├──┴──┤ │ +│ │ │ │ │ +│ │ │ │ │ +└──────────┴──────────┴─────┴────┘ + +With 2 windows in the master area, many windows in the stack and low mfact: + +┌──────────┬──────────┬──────────┐ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +├──master──┤ stack┬──┬────┤ +│ │ │ │ │ │ +│ │ ├──┴──┤ │ +│ │ │ │ │ +│ │ │ │ │ +└──────────┴──────────┴─────┴────┘ +--- + config.def.h | 4 +- + dwl.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 105 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..abf9644 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -34,6 +34,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "@|@", snail }, + }; + + /* monitors */ +@@ -48,7 +49,7 @@ static const MonitorRule monrules[] = { + { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + */ + /* defaults */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ { NULL, 0.64f, 1, 1, &layouts[3], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, + }; + + /* keyboard */ +@@ -139,6 +140,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index 5bf995e..45ac5ad 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -333,6 +333,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void snail(Monitor *m); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -2644,6 +2645,107 @@ setup(void) + #endif + } + ++void ++snail(Monitor *m) ++{ ++ int i = 0, n = 0; ++ unsigned int mw = m->w.width; ++ Client *c, *prev; ++ enum wlr_direction dir = WLR_DIRECTION_RIGHT; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? (int)round(m->w.width * m->mfact) : 0; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ /* ++ * If the master area exists and this is the first window, fill the ++ * master area with this window ++ */ ++ if (mw > 0 && i == 0) { ++ c->geom = (struct wlr_box){.x = m->w.x, .y = m->w.y, ++ .width = mw, .height = m->w.height}; ++ /* ++ * If the first window in the master area is wide, split it ++ * horizontally and put next one on its right; otherwise, split it ++ * vertically and put the next one below it ++ */ ++ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; ++ /* ++ * If the master area is full or doesn't exist, fill the stack with the ++ * m->nmaster-th window ++ */ ++ } else if (i == m->nmaster) { ++ c->geom = (struct wlr_box){.x = m->w.x + mw, .y = m->w.y, ++ .width = m->w.width - mw, .height = m->w.height}; ++ /* ++ * If the first window in the stack is wide, split it horizontally ++ * and put next one on its right; otherwise, split it vertically and ++ * put the next one below it ++ */ ++ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous horizontally and put the current window on the right ++ */ ++ } else if (dir == WLR_DIRECTION_RIGHT) { ++ c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ /* ++ * If it's a stack window or the first narrow window in the master ++ * area, put the next one below it ++ */ ++ if (i >= m->nmaster || c->geom.width < m->w.height) ++ dir = WLR_DIRECTION_DOWN; ++ /* ++ * Split the previous vertically and put the current window below it ++ */ ++ } else if (dir == WLR_DIRECTION_DOWN) { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ dir = WLR_DIRECTION_LEFT; ++ /* ++ * Split the previous horizontally and put the current window on the left ++ */ ++ } else if (dir == WLR_DIRECTION_LEFT) { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y, ++ .width = prev->geom.width / 2, .height = prev->geom.height}; ++ dir = WLR_DIRECTION_UP; ++ /* ++ * Split the previous vertically and put the current window above it ++ */ ++ } else { ++ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2, ++ .width = prev->geom.width, .height = prev->geom.height / 2}; ++ dir = WLR_DIRECTION_RIGHT; ++ } ++ i++; ++ prev = c; ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ ++ resize(c, c->geom, 0); ++ } ++} ++ + void + spawn(const Arg *arg) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/spawninfo/README.md b/dwl/dwl-patches/patches/spawninfo/README.md new file mode 100644 index 0000000..5ee345d --- /dev/null +++ b/dwl/dwl-patches/patches/spawninfo/README.md @@ -0,0 +1,41 @@ +### Description + +This patch adds `spawninfo` function that is very similar to `spawn`, except it +also passes some information about the focused client via stdin. + +The info is passed in this format: + + PID + TITLE + APPID + TAGS + X,Y WIDTHxHEIGHT + +I use it for 2 things: grabbing a screenshot of a focused window and adjusting +volume of audio produced by a focused window (so much simpler than having to +open pulsemixer every time). If you want to have the same functionality, you +need to put these scripts into your PATH and make them executable: + +[screenshotwin](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/screenshotwin) +for taking a screenshot (`grim` is required): + +```c + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_S, spawninfo, SHCMD("screenshotwin") }, +``` + +[pamixerc](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/pamixerc) +for adjusting volume (`pactl` is required): + +```c + { MODKEY, XKB_KEY_XF86AudioRaiseVolume,spawninfo,SHCMD("pamixerc -- -i 5") }, + { MODKEY, XKB_KEY_XF86AudioLowerVolume,spawninfo,SHCMD("pamixerc -- -d 5") }, + { MODKEY, XKB_KEY_XF86AudioMute, spawninfo, SHCMD("pamixerc -- -t") }, +``` + +### Download + +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/spawninfo/spawninfo.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) diff --git a/dwl/dwl-patches/patches/spawninfo/pamixerc b/dwl/dwl-patches/patches/spawninfo/pamixerc new file mode 100755 index 0000000..91a41c4 --- /dev/null +++ b/dwl/dwl-patches/patches/spawninfo/pamixerc @@ -0,0 +1,134 @@ +#!/usr/bin/awk -f + +function get_parent(pid, cmd, ppid, arr) { + cmd = sprintf("/proc/%d/stat", pid) + ppid = "" + if ((getline line < cmd) > 0) { + split(line, arr) + ppid = arr[4] + } + close(cmd) + return ppid +} + +function proc_clients(line, arr) { + if (match(line, /^Client #(.*)/, arr)) { + store_client() + client_index = arr[1] + return + } + else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) { + process_id = arr[1] + return + } + else if (match(line, /^\s*application\.name = "(.*)"/, arr)) { + application_name = arr[1] + return + } +} + +function store_client() { + if (client_index == "") + return + + clients[client_index] = 1 + clients[client_index, "pid"] = process_id + clients[client_index, "parent_pid"] = get_parent(process_id) + clients[client_index, "name"] = application_name + client_index = "" + process_id = "" +} + +function proc_sink_inputs(line, arr) { + if (match(line, /^Sink Input #(.*)/, arr)) { + apply_sink_input() + sink_index = arr[1] + return + } + else if (match(line, /^\s*application\.process\.id = "(.*)"/, arr)) { + process_id = arr[1] + return + } + else if (match(line, /^\s*Client: (.*)/, arr)) { + client_index = arr[1] + return + } + else if (match(line, /^\s*Mute: (.*)/, arr)) { + muted = (arr[1] == "yes") + return + } + else if (match(line, /^\s*Volume:.* ([0-9]+)%/, arr)) { + volume = strtonum(arr[1]) + return + } +} + +function apply_sink_input( cmd, header, msg, inc) { + if (sink_index == "") + return + + # Do stuff if PID matches + if (PID == clients[client_index, "pid"] || PID == clients[client_index, "parent_pid"]) { + switch (ACTION) { + case "-i": + case "-d": + inc = strtonum(ARG) + if (ACTION == "-i") + volume += inc + else + volume -= inc + volume = volume > 100 ? 100 : volume + volume = volume < 0 ? 0 : volume + cmd = sprintf("pactl set-sink-input-volume '%d' '%d'%", sink_index, volume) + system(cmd) + print cmd + break + case "-t": + muted = !muted + cmd = sprintf("pactl set-sink-input-mute '%d' '%s'", sink_index, muted ? "true" : "false") + system(cmd) + print cmd + break + } + # Display a "popup" with new volume + header = sprintf("Client Volume: %d%%%s", volume, muted ? " MUTED" : "") + msg = clients[client_index, "name"] + system(sprintf("notify-send -r 101 -u low -h 'int:value:%d' '%s' '%s'", volume, header, msg)) + } + + sink_index = "" +} + +BEGIN { + # Get arguments + # ACTION: -d, -i or -t (like pamixer) + # ARG: num arg for -d or -i + ACTION = ARGV[1] + ARG = ARGV[2] + for (i = 1; i < ARGC; i++) + delete ARGV[i] + + # Get client info + getline PID + getline TITLE # Not used + getline APPID # Not used + getline TAGS # Not used + getline GEOMETRY # Not used + + if (PID == "") + exit 1 + + # Process PulseAudio clients list and store PIDs for each + cmd = "pactl list clients" + while ((cmd | getline line) > 0) + proc_clients(line) + close(cmd) + store_client() + + # Process PulseAudio sink inputs list and run apply_sink_input() for each + cmd = "pactl list sink-inputs" + while ((cmd | getline line) > 0) + proc_sink_inputs(line) + close(cmd) + apply_sink_input() +} diff --git a/dwl/dwl-patches/patches/spawninfo/screenshotwin b/dwl/dwl-patches/patches/spawninfo/screenshotwin new file mode 100755 index 0000000..c8a3ebf --- /dev/null +++ b/dwl/dwl-patches/patches/spawninfo/screenshotwin @@ -0,0 +1,21 @@ +#!/bin/sh + +# Get client info +read -r PID +read -r TITLE +read -r APPID +read -r TAGS +read -r GEOMETRY + +[ -n "$GEOMETRY" ] || exit 1 + +tempdir="/tmp/screenshots" +mkdir -p "$tempdir" +file="$(mktemp -p "$tempdir" "XXXXXX.png")" + +# Grab the screenshot! Very conviniently, GEOMETRY format matches the one +# expected by grim +grim -g "$GEOMETRY" "$file" || exit + +wl-copy -t image/png < "$file" +notify-send -i "$file" "Screenshot taken!" "Image copied to clipboard and saved to $file" diff --git a/dwl/dwl-patches/patches/spawninfo/spawninfo.patch b/dwl/dwl-patches/patches/spawninfo/spawninfo.patch new file mode 100644 index 0000000..e452a4e --- /dev/null +++ b/dwl/dwl-patches/patches/spawninfo/spawninfo.patch @@ -0,0 +1,110 @@ +From 83b8dc03f5ea40f472e90d452671f8e55faf2c4c Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Sun, 9 Feb 2025 23:27:48 +0100 +Subject: [PATCH] spawninfo: spawn but pass client info via stdin + +--- + client.h | 12 ++++++++++++ + dwl.c | 42 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 54 insertions(+) + +diff --git a/client.h b/client.h +index 42f225f..bc9cad2 100644 +--- a/client.h ++++ b/client.h +@@ -131,6 +131,18 @@ client_get_appid(Client *c) + return c->surface.xdg->toplevel->app_id; + } + ++static inline int ++client_get_pid(Client *c) ++{ ++ pid_t pid; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) ++ return c->surface.xwayland->pid; ++#endif ++ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); ++ return pid; ++} ++ + static inline void + client_get_clip(Client *c, struct wlr_box *clip) + { +diff --git a/dwl.c b/dwl.c +index def2562..859514c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ pid_t pid; + } Client; + + typedef struct { +@@ -334,6 +335,7 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void spawninfo(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -466,6 +468,8 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ c->pid = client_get_pid(c); ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { +@@ -2658,6 +2662,44 @@ spawn(const Arg *arg) + } + } + ++void ++spawninfo(const Arg *arg) ++{ ++ int fd[2]; ++ pid_t pid; ++ const char *title, *appid; ++ Client *c = focustop(selmon); ++ ++ if (pipe(fd) == -1) ++ return; ++ if ((pid = fork()) == -1) ++ return; ++ if (pid == 0) { ++ dup2(fd[0], STDIN_FILENO); ++ close(fd[0]); ++ close(fd[1]); ++ dup2(STDERR_FILENO, STDOUT_FILENO); ++ setsid(); ++ execvp(((char **)arg->v)[0], (char **)arg->v); ++ die("dwl: execvp %s failed:", ((char **)arg->v)[0]); ++ } ++ ++ close(fd[0]); ++ ++ if (c) { ++ if (!(title = client_get_title(c))) ++ title = ""; ++ if (!(appid = client_get_appid(c))) ++ appid = ""; ++ dprintf(fd[1], "%d\n%s\n%s\n%"PRIu32"\n%d,%d %dx%d\n", c->pid, ++ title, appid, c->tags, ++ c->geom.x + c->bw, c->geom.y + c->bw, ++ c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw); ++ } ++ ++ close(fd[1]); ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/stacker/README.md b/dwl/dwl-patches/patches/stacker/README.md new file mode 100644 index 0000000..c7778a0 --- /dev/null +++ b/dwl/dwl-patches/patches/stacker/README.md @@ -0,0 +1,28 @@ +### Description +Stacker is a patch that allows moving around the stack more freely. With only +one keybinding, quickly move, swap and jump around the window stack. + +1. Focus any window of the stack with a single key binding. +2. Swap the currently focused windows with any other window in the stack. +3. Move the selected window in the stack with `relativeswap`. + +This patch is heavily inspired by the original [stacker](https://dwm.suckless.org/patches/stacker/) dwm patch. + +### Keybinding + +- `MODKEY` + {`q`, `w`, `e`, `r`}: jump to the first, second, third and last + window of the stack +- `MODKEY` + `SHIFT` + {`Q`, `W`, `E`, `R`}: swap the selected with the first, second, third and last + window of the stack +- `MODKEY` + `SHIFT` + {`J`, `K`}: move the selected window up & down the stack + +### Missing feature + +Jumping to the last selected window is not yet implemented. + +### Download +- [git branch](https://codeberg.org/jeromecst/dwl/src/branch/stacker) +- [2024-05-17](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/stacker/stacker.patch) + +### Authors +- [jeromecst](https://codeberg.org/jeromecst) diff --git a/dwl/dwl-patches/patches/stacker/stacker.patch b/dwl/dwl-patches/patches/stacker/stacker.patch new file mode 100644 index 0000000..dba1cb0 --- /dev/null +++ b/dwl/dwl-patches/patches/stacker/stacker.patch @@ -0,0 +1,212 @@ +From b8f39c0710eb3cfdf4c619f532222d356ec58140 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?J=C3=A9r=C3=B4me?= +Date: Fri, 17 May 2024 23:06:19 +0200 +Subject: [PATCH] stacker + +--- + config.def.h | 10 +++++ + dwl.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 114 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8f498d2..44cbcd8 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -120,10 +120,20 @@ static const char *menucmd[] = { "wmenu-run", NULL }; + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ + /* modifier key function argument */ ++ { MODKEY, XKB_KEY_q, focusto, {.i = 0} }, ++ { MODKEY, XKB_KEY_w, focusto, {.i = 1} }, ++ { MODKEY, XKB_KEY_e, focusto, {.i = 2} }, ++ { MODKEY, XKB_KEY_r, focusto, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, swapstack, {.i = 0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_W, swapstack, {.i = 1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, swapstack, {.i = 2} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, swapstack, {.i = -1} }, + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_J, relativeswap, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_K, relativeswap, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index bf763df..be88c6d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -279,6 +279,7 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); ++static void focusto(const Arg *arg); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); +@@ -308,6 +309,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); + static void quit(const Arg *arg); ++static void relativeswap(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -327,6 +329,8 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static void swapstack(const Arg *arg); ++static void relativeswap(const Arg *arg); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); +@@ -343,9 +347,11 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static Client *nextvisible(int i, struct wl_list *from, Monitor *m); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); ++static void wl_list_swap(struct wl_list *a, struct wl_list *b); + static void zoom(const Arg *arg); + + /* variables */ +@@ -1308,6 +1314,21 @@ focusclient(Client *c, int lift) + client_activate_surface(client_surface(c), 1); + } + ++void ++focusto(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ int i; ++ if (!sel || (sel->isfullscreen && !client_has_children(sel))) ++ return; ++ ++ i = arg->i > -1 ? arg->i + 1 : arg->i; ++ ++ c = nextvisible(i, &clients, selmon); ++ if (c) ++ focusclient(c , 1); ++} ++ + void + focusmon(const Arg *arg) + { +@@ -1951,6 +1972,24 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++relativeswap(const Arg *arg) ++{ ++ Client *trgt, *sel = focustop(selmon); ++ ++ if (!sel || !selmon) ++ return; ++ ++ trgt = nextvisible(arg->i, &sel->link, selmon); ++ if (!trgt || trgt == sel) ++ return; ++ ++ wl_list_swap(&sel->link, &trgt->link); ++ ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +@@ -2548,6 +2587,26 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++void ++swapstack(const Arg *arg) ++{ ++ Client *trgt, *sel = focustop(selmon); ++ int i; ++ ++ if (!sel || !selmon) ++ return; ++ ++ i = arg->i > -1 ? arg->i + 1 : arg->i; ++ trgt = nextvisible(i, &clients, selmon); ++ if (!trgt || trgt == sel) ++ return; ++ ++ wl_list_swap(&sel->link, &trgt->link); ++ ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ + void + tag(const Arg *arg) + { +@@ -2858,6 +2917,28 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++Client * ++nextvisible(int i, struct wl_list *from, Monitor *m) ++{ ++ Client *c; ++ if (i >= 0){ ++ wl_list_for_each(c, from, link) { ++ // if (VISIBLEON(c , m) && &c->link != from && i--) ++ if (VISIBLEON(c , m)) { ++ if (--i == 0) ++ return c; ++ } ++ } ++ } else if (i < 0) { ++ wl_list_for_each_reverse(c, from, link) { ++ if (VISIBLEON(c , m)) ++ if (++i == 0) ++ return c; ++ } ++ } ++ return NULL; ++} ++ + Monitor * + xytomon(double x, double y) + { +@@ -2865,6 +2946,29 @@ xytomon(double x, double y) + return o ? o->data : NULL; + } + ++void ++wl_list_swap(struct wl_list *a, struct wl_list *b) ++{ ++ struct wl_list *prev_a = a->prev; ++ struct wl_list *prev_b = b->prev; ++ ++ if (prev_b == a) { ++ wl_list_remove(a); ++ wl_list_insert(b, a); ++ return; ++ } ++ if (prev_a == b) { ++ wl_list_remove(b); ++ wl_list_insert(a, b); ++ return; ++ } ++ wl_list_remove(a); ++ wl_list_insert(prev_b, a); ++ ++ wl_list_remove(b); ++ wl_list_insert(prev_a, b); ++} ++ + void + xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/stairs/README.md b/dwl/dwl-patches/patches/stairs/README.md new file mode 100644 index 0000000..403a276 --- /dev/null +++ b/dwl/dwl-patches/patches/stairs/README.md @@ -0,0 +1,42 @@ +### Description +Port of the [stairs](https://dwm.suckless.org/patches/stairs/) patch from dwm. + +The remaining text is from the description on the dwm site: + +This layout can be modified using the 3 variables provided in config.h: stairpx, stairdirection and stairsamesize. + +``` ++-------------+--------+----------------+ +| | | | +| | +--| S1 | +| | |S2| | +| | +--| | | +| | |S3| | | +| M |--| | +----------------+ +| |S4| | | | +| | | +-----------------+ | +| | | | | +| | +------------------+ | +| | | | ++-------------+-------------------+-----+ + stairsamesize = 1 + ++-------------+--------+----------------+ +| | | | +| | +--| S1 | +| | |S2| | +| | +--| | | +| | |S3| | | +| M |--| | | | +| |S4| | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | ++-------------+--+--+--+----------------+ + stairsamesize = 0 +``` +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/stairs/stairs.patch) +### Authors +- [MayOrMayNotBeACat](https://codeberg.org/MayOrMayNotBeACat) diff --git a/dwl/dwl-patches/patches/stairs/stairs.patch b/dwl/dwl-patches/patches/stairs/stairs.patch new file mode 100644 index 0000000..d39932e --- /dev/null +++ b/dwl/dwl-patches/patches/stairs/stairs.patch @@ -0,0 +1,136 @@ +From d5bbe6d54d044fa11c76e2e20b96a44897319424 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Tue, 4 Feb 2025 20:51:06 +0100 +Subject: [PATCH 1/2] Fix crash disabling monitor with locked surface + +(cherry picked from commit d1880b44223701c91b51b319fc69a0f63044f861) +--- + dwl.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dwl.c b/dwl.c +index cf3ef70..c717c1d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -716,6 +716,8 @@ cleanupmon(struct wl_listener *listener, void *data) + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); ++ if (m->lock_surface) ++ destroylocksurface(&m->destroy_lock_surface, NULL); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); +-- +2.50.1 + + +From b6e96ab6281eddacdda26c39e6446b72ce05de39 Mon Sep 17 00:00:00 2001 +From: JustinWayland +Date: Fri, 6 Jun 2025 22:14:31 -0400 +Subject: [PATCH 2/2] Add stairs layout. + +--- + config.def.h | 5 +++++ + dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 53 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..e81568c 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,9 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int stairpx = 20; /* depth of the stairs layout */ ++static const int stairdirection = 1; /* 0: left-aligned, 1: right-aligned */ ++static const int stairsamesize = 1; /* 1 means shrink all the staired windows to the same size */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -34,6 +37,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "[S]", stairs }, + }; + + /* monitors */ +@@ -139,6 +143,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} }, ++ { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} }, + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, +diff --git a/dwl.c b/dwl.c +index cf3ef70..4debf15 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -334,6 +334,7 @@ static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); ++static void stairs(Monitor* m); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); +@@ -2657,6 +2658,53 @@ spawn(const Arg *arg) + } + } + ++void ++stairs(Monitor *m) ++{ ++ int i, n, h, mw, my; ++ int ox, oy, ow, oh; ++ Client *c; ++ ++ n = 0; ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ if (n == 0) ++ return; ++ ++ if (n > m->nmaster) ++ mw = m->nmaster ? ROUND(m->w.width * m->mfact) : 0; ++ else ++ mw = m->w.width; ++ ++ i = my = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c,m) || c->isfloating || c->fullscreen) ++ continue; ++ if (i < m->nmaster) { ++ h = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, (struct wlr_box){ ++ .x=m->w.x,.y=m->w.y + my,.width=mw,.height=h ++ }, 0); ++ my += c->geom.height; ++ } else { ++ oy = i - m->nmaster; ++ ox = stairdirection ? n - i - 1 : (stairsamesize ? i - m->nmaster: 0); ++ ow = stairsamesize ? n - m->nmaster - 1 : n - i - 1; ++ oh = stairsamesize ? ow : i - m->nmaster; ++ resize(c, (struct wlr_box){ ++ .x=m->w.x + mw + (ox * stairpx), ++ .y=m->w.y + (oy * stairpx), ++ .width=m->w.width - mw - (ow * stairpx), ++ .height=m->w.height - (oh * stairpx) ++ }, 0); ++ if (c == focustop(selmon)) ++ wlr_scene_node_raise_to_top(&c->scene->node); ++ } ++ i++; ++ } ++} ++ + void + startdrag(struct wl_listener *listener, void *data) + { +-- +2.50.1 + diff --git a/dwl/dwl-patches/patches/startargv/README.md b/dwl/dwl-patches/patches/startargv/README.md new file mode 100644 index 0000000..bc15225 --- /dev/null +++ b/dwl/dwl-patches/patches/startargv/README.md @@ -0,0 +1,13 @@ +### Description +allow passing startup command on argv + +e.g. `dwl -s foot -s` launches `foot -s` + +put `sh -c` right after `dwl -s` to emulate normal behaviour + +### Download +- [git branch](https://codeberg.org/notchoc/dwl/src/branch/startargv) +- [2024-07-03](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/startargv/startargv.patch) + +### Authors +- [notchoc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/startargv/startargv.patch b/dwl/dwl-patches/patches/startargv/startargv.patch new file mode 100644 index 0000000..5697b9c --- /dev/null +++ b/dwl/dwl-patches/patches/startargv/startargv.patch @@ -0,0 +1,58 @@ +From a823f7923f8d2006eedc013ca1c1ba7a496543d5 Mon Sep 17 00:00:00 2001 +From: choc +Date: Wed, 3 Jul 2024 11:21:33 +0800 +Subject: [PATCH] startargv: allow passing startup command on argv + +--- + dwl.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 9fb50a7..43b1ac1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -320,7 +320,7 @@ static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); +-static void run(char *startup_cmd); ++static void run(char **startup_cmd); + 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); +@@ -2158,7 +2158,7 @@ resize(Client *c, struct wlr_box geo, int interact) + } + + void +-run(char *startup_cmd) ++run(char **startup_cmd) + { + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); +@@ -2183,7 +2183,7 @@ run(char *startup_cmd) + dup2(piperw[0], STDIN_FILENO); + close(piperw[0]); + close(piperw[1]); +- execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); ++ execvp(startup_cmd[0], startup_cmd); + die("startup: execl:"); + } + dup2(piperw[1], STDOUT_FILENO); +@@ -3124,12 +3124,12 @@ xwaylandready(struct wl_listener *listener, void *data) + int + main(int argc, char *argv[]) + { +- char *startup_cmd = NULL; ++ char **startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') +- startup_cmd = optarg; ++ *(startup_cmd = &argv[optind-1]) = optarg, argc = 0; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/sticky/README.md b/dwl/dwl-patches/patches/sticky/README.md new file mode 100644 index 0000000..af0e81f --- /dev/null +++ b/dwl/dwl-patches/patches/sticky/README.md @@ -0,0 +1,13 @@ +### Description +Adds a toggleable function that makes a sticky client that is visible on all tags. + +Originally based on [dwm sticky patch](https://dwm.suckless.org/patches/sticky). + +### Download +- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch) +- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-sticky.patch) +- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/sticky) + +### Authors +- [Rutherther](https://codeberg.org/Rutherther) +- [Dmitry Zakharchenko](https://github.com/dm1tz) diff --git a/dwl/dwl-patches/patches/sticky/sticky.patch b/dwl/dwl-patches/patches/sticky/sticky.patch new file mode 100644 index 0000000..ece8308 --- /dev/null +++ b/dwl/dwl-patches/patches/sticky/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/dwl/dwl-patches/patches/swallow/README.md b/dwl/dwl-patches/patches/swallow/README.md new file mode 100644 index 0000000..6c16162 --- /dev/null +++ b/dwl/dwl-patches/patches/swallow/README.md @@ -0,0 +1,53 @@ +### Description + +This patch adds "window swallowing" to dwl. + +If a user runs a graphical program from the terminal (e.g., `mpv`), the terminal +will be hidden and only a window of the newly spawned graphical program will +be visible. The terminal stays hidden until the graphical program is closed. +This patch helps users spawning a lot of graphical programs from their command +line by avoiding cluttering the screen with many unusable terminals. + +`foot` is the terminal by default, you can change it in client rules in config.h. + +In `2025-03-03 v0.7` version and above, the patch had been rewritten from +scratch to make it more robust and add a few more features: + +- "dynamically swallow" windows by pressing `Alt+a` (a focused window will + swallow/unswallow the previously focused one) +- toggle automatic swallowing by `Alt+Shift+a` +- if a window has swallowed another, it get thicker borders + +### Download + +#### swallow.patch + +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow.patch) +- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/2e5748edfe1129f95c7bb1bf9dd590a897f55f57/patches/swallow/swallow.patch) (added "dynamic swallowing" support) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow.patch) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f64d701bab2f9f52d3637edd091684f920407d87/patches/swallow/swallow.patch) +- [2024-05-02](https://codeberg.org/dwl/dwl-patches/raw/commit/9c5d5d85f3ac780e7a14d5d0535e3349ce8b8f53/patches/swallow/swallow.patch) +- [2024-04-03](https://codeberg.org/dwl/dwl-patches/raw/commit/3c9a8e3232a8531871924484cef1ef0938730e15/swallow/swallow.patch) +- [2024-01-01](https://codeberg.org/dwl/dwl-patches/raw/commit/8a352a1b27a64821ba9fbfda52fe82463ac84c66/swallow/swallow.patch) +- [2023-10-26](https://github.com/djpohly/dwl/compare/main...youbitchoc:swallow.patch) +- [2023-08-16](https://github.com/djpohly/dwl/compare/main...mewkl:swallowx.patch) (added XWayland support) +- [2023-07-15](https://github.com/djpohly/dwl/compare/main...NikitaIvanovV:swallow.patch) +- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-swallow.patch) +- [2021-12-03](https://github.com/djpohly/dwl/compare/main...dm1tz:swallow.patch) + +#### swallow-freebsd.patch + +Apply this patch on top of the swallow.patch if you use FreeBSD. + +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow-freebsd.patch) +- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/2e5748edfe1129f95c7bb1bf9dd590a897f55f57/patches/swallow/swallow-freebsd.patch) (added "dynamic swallowing" support) +- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow-freebsd.patch) + +### Authors + +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV)) +- [Dmitry Zakharchenko](https://github.com/dm1tz) +- [Palanix](https://codeberg.org/Palanix) +- [Connor Worrell](https://github.com/ConnorWorrell) +- [Mewkl](https://github.com/mewkl) +- [Choc](https://codeberg.org/notchoc) diff --git a/dwl/dwl-patches/patches/swallow/swallow-freebsd.patch b/dwl/dwl-patches/patches/swallow/swallow-freebsd.patch new file mode 100644 index 0000000..795a5e1 --- /dev/null +++ b/dwl/dwl-patches/patches/swallow/swallow-freebsd.patch @@ -0,0 +1,54 @@ +From 301e1b368d92a32a7bdcd4bd1f0ede0295977e3e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Mon, 3 Mar 2025 19:49:07 +0100 +Subject: [PATCH] swallow: add FreeBSD support + +--- + dwl.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 71d500a..52fdd9c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -67,6 +67,14 @@ + #include + #endif + ++#ifdef __FreeBSD__ ++#define __BSD_VISIBLE ++#include ++#include ++#include ++#include ++#endif ++ + #include "util.h" + + /* macros */ +@@ -2032,6 +2040,7 @@ outputmgrtest(struct wl_listener *listener, void *data) + pid_t + parentpid(pid_t pid) + { ++#ifdef __linux__ + unsigned int v = 0; + FILE *f; + char buf[256]; +@@ -2041,6 +2050,14 @@ parentpid(pid_t pid) + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); + return (pid_t)v; ++#elif defined(__FreeBSD__) ++ struct kinfo_proc kip; ++ size_t len = sizeof(struct kinfo_proc); ++ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, p }; ++ if (sysctl(mib, 4, &kip, &len, NULL, 0) < 0 || len == 0) ++ return 0; ++ return kip.ki_ppid; ++#endif + } + + void +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/swallow/swallow.patch b/dwl/dwl-patches/patches/swallow/swallow.patch new file mode 100644 index 0000000..ee1a1c1 --- /dev/null +++ b/dwl/dwl-patches/patches/swallow/swallow.patch @@ -0,0 +1,351 @@ +From a220e1ed4b04a66c837dfc8e3363d3e696cbf541 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Wed, 5 Feb 2025 02:34:39 +0100 +Subject: [PATCH] Swallow: hide the terminal when it spawns a client + +--- + client.h | 12 ++++ + config.def.h | 11 +++- + dwl.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 168 insertions(+), 7 deletions(-) + +diff --git a/client.h b/client.h +index 42f225f..bc9cad2 100644 +--- a/client.h ++++ b/client.h +@@ -131,6 +131,18 @@ client_get_appid(Client *c) + return c->surface.xdg->toplevel->app_id; + } + ++static inline int ++client_get_pid(Client *c) ++{ ++ pid_t pid; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) ++ return c->surface.xwayland->pid; ++#endif ++ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL); ++ return pid; ++} ++ + static inline void + client_get_clip(Client *c, struct wlr_box *clip) + { +diff --git a/config.def.h b/config.def.h +index 22d2171..42342f1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,8 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static int enableautoswallow = 1; /* enables autoswallowing newly spawned clients */ ++static float swallowborder = 1.0f; /* add this multiplied by borderpx to border when a client is swallowed */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -22,10 +24,11 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating isterm noswallow monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "foot", NULL, 0, 0, 1, 1, -1 }, ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, 0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +@@ -142,6 +145,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_a, toggleswallow, {0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, toggleautoswallow,{0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index def2562..71d500a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -73,12 +73,13 @@ + #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)->swallowedby) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) + #define TAGMASK ((1u << TAGCOUNT) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) ++#define BORDERPX(C) (borderpx + ((C)->swallowing ? (int)ceilf(swallowborder * (C)->swallowing->bw) : 0)) + + /* enums */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +@@ -104,7 +105,8 @@ typedef struct { + } Button; + + typedef struct Monitor Monitor; +-typedef struct { ++typedef struct Client Client; ++struct Client { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ +@@ -140,8 +142,12 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int isterm, noswallow; + uint32_t resize; /* configure serial of a pending resize */ +-} Client; ++ pid_t pid; ++ Client *swallowing; /* client being hidden */ ++ Client *swallowedby; ++}; + + typedef struct { + uint32_t mod; +@@ -230,6 +236,8 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int isterm; ++ int noswallow; + int monitor; + } Rule; + +@@ -311,6 +319,7 @@ static void moveresize(const Arg *arg); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); ++static pid_t parentpid(pid_t pid); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); +@@ -335,11 +344,15 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static void swallow(Client *c, Client *toswallow); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static Client *termforwin(Client *c); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void toggleswallow(const Arg *arg); ++static void toggleautoswallow(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -466,11 +479,15 @@ applyrules(Client *c) + if (!(title = client_get_title(c))) + title = broken; + ++ c->pid = client_get_pid(c); ++ + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; ++ c->isterm = r->isterm; ++ c->noswallow = r->noswallow; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) +@@ -478,6 +495,12 @@ applyrules(Client *c) + } + } + } ++ if (enableautoswallow && !c->noswallow && !c->isfloating && ++ !c->surface.xdg->initial_commit) { ++ Client *p = termforwin(c); ++ if (p) ++ swallow(c, p); ++ } + setmon(c, mon, newtags); + } + +@@ -2006,6 +2029,20 @@ outputmgrtest(struct wl_listener *listener, void *data) + outputmgrapplyortest(config, 1); + } + ++pid_t ++parentpid(pid_t pid) ++{ ++ unsigned int v = 0; ++ FILE *f; ++ char buf[256]; ++ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)pid); ++ if (!(f = fopen(buf, "r"))) ++ return 0; ++ fscanf(f, "%*u %*s %*c %u", &v); ++ fclose(f); ++ return (pid_t)v; ++} ++ + void + pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +@@ -2326,7 +2363,7 @@ setfullscreen(Client *c, int fullscreen) + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; +- c->bw = fullscreen ? 0 : borderpx; ++ c->bw = fullscreen ? 0 : BORDERPX(c); + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); +@@ -2404,6 +2441,9 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); ++ ++ if (c->swallowing) ++ setmon(c->swallowing, m, newtags); + } + + void +@@ -2669,6 +2709,44 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++void ++swallow(Client *c, Client *toswallow) ++{ ++ /* Do not allow a client to swallow itself */ ++ if (c == toswallow) ++ return; ++ ++ /* Swallow */ ++ if (toswallow && !c->swallowing) { ++ c->swallowing = toswallow; ++ toswallow->swallowedby = c; ++ toswallow->mon = c->mon; ++ toswallow->mon = c->mon; ++ wl_list_remove(&c->link); ++ wl_list_insert(&c->swallowing->link, &c->link); ++ wl_list_remove(&c->flink); ++ wl_list_insert(&c->swallowing->flink, &c->flink); ++ c->bw = BORDERPX(c); ++ c->tags = toswallow->tags; ++ c->isfloating = toswallow->isfloating; ++ c->geom = toswallow->geom; ++ setfullscreen(toswallow, 0); ++ } ++ ++ /* Unswallow */ ++ else if (c->swallowing) { ++ wl_list_remove(&c->swallowing->link); ++ wl_list_insert(&c->link, &c->swallowing->link); ++ wl_list_remove(&c->swallowing->flink); ++ wl_list_insert(&c->flink, &c->swallowing->flink); ++ c->swallowing->tags = c->tags; ++ c->swallowing->swallowedby = NULL; ++ c->swallowing = NULL; ++ c->bw = BORDERPX(c); ++ setfullscreen(c, 0); ++ } ++} ++ + void + tag(const Arg *arg) + { +@@ -2690,6 +2768,40 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++Client * ++termforwin(Client *c) ++{ ++ Client *p; ++ pid_t pid; ++ pid_t pids[32]; ++ size_t i, pids_len; ++ ++ if (!c->pid || c->isterm) ++ return NULL; ++ ++ /* Get all parent pids */ ++ pids_len = 0; ++ pid = c->pid; ++ while (pids_len < LENGTH(pids)) { ++ pid = parentpid(pid); ++ if (!pid) ++ break; ++ pids[pids_len++] = pid; ++ } ++ ++ /* Find closest parent */ ++ for (i = 0; i < pids_len; i++) { ++ wl_list_for_each(p, &clients, link) { ++ if (!p->pid || !p->isterm || p->swallowedby) ++ continue; ++ if (pids[i] == p->pid) ++ return p; ++ } ++ } ++ ++ return NULL; ++} ++ + void + tile(Monitor *m) + { +@@ -2741,6 +2853,32 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++toggleswallow(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ if (!sel) ++ return; ++ ++ if (sel->swallowing) { ++ swallow(sel, NULL); ++ } else { ++ wl_list_for_each(c, &sel->flink, flink) { ++ if (&c->flink == &fstack) ++ continue; /* wrap past the sentinel node */ ++ if (VISIBLEON(c, selmon)) ++ break; /* found it */ ++ } ++ swallow(sel, c); ++ } ++} ++ ++void ++toggleautoswallow(const Arg *arg) ++{ ++ enableautoswallow = !enableautoswallow; ++} ++ + void + toggletag(const Arg *arg) + { +@@ -2801,6 +2939,12 @@ unmapnotify(struct wl_listener *listener, void *data) + grabc = NULL; + } + ++ if (c->swallowing) { ++ swallow(c, NULL); ++ } else if (c->swallowedby) { ++ swallow(c->swallowedby, NULL); ++ } ++ + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/swapandfocusdir/README.md b/dwl/dwl-patches/patches/swapandfocusdir/README.md new file mode 100644 index 0000000..2861a07 --- /dev/null +++ b/dwl/dwl-patches/patches/swapandfocusdir/README.md @@ -0,0 +1,14 @@ +### Description +Focus the window (floating or no) to the left, right, above, or below the current focused window. + +Swap the focused window with the window (no floating) to the left, right, above, or below. + +**NOTE:** this patch uses the same algorithm that River uses to select the window in the given direction. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/swapandfocusdir) +- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/swapandfocusdir/swapandfocusdir.patch) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swapandfocusdir/swapandfocusdir.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) diff --git a/dwl/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch b/dwl/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch new file mode 100644 index 0000000..3636387 --- /dev/null +++ b/dwl/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch @@ -0,0 +1,221 @@ +From 285470897406b653e77d732a77356aaf9a70b799 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Fri, 5 Jul 2024 12:37:39 -0500 +Subject: [PATCH] implement swapandfocusdir + +--- + config.def.h | 8 +++ + dwl.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 172 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..724e15e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -129,6 +129,14 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, focusdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, focusdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, focusdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, focusdir, {.ui = 3} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapdir, {.ui = 0} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapdir, {.ui = 1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapdir, {.ui = 2} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapdir, {.ui = 3} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index dc0437e..844c1f5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -241,6 +241,11 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct { ++ int x; ++ int y; ++} Vector; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -285,6 +290,8 @@ static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); ++static void focusdir(const Arg *arg); ++static void swapdir(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -1425,6 +1432,163 @@ focusstack(const Arg *arg) + focusclient(c, 1); + } + ++Vector ++position_of_box(const struct wlr_box *box) ++{ ++ return (Vector){ ++ .x = box->x + box->width / 2, ++ .y = box->y + box->height / 2, ++ }; ++} ++ ++Vector ++diff_of_vectors(Vector *a, Vector *b) ++{ ++ return (Vector){ ++ .x = b->x - a->x, ++ .y = b->y - a->y, ++ }; ++} ++ ++const char * ++direction_of_vector(Vector *vector) ++{ ++ // A zero length vector has no direction ++ if (vector->x == 0 && vector->y == 0) return ""; ++ ++ if (abs(vector->y) > abs(vector->x)) { ++ // Careful: We are operating in a Y-inverted coordinate system. ++ return (vector->y > 0) ? "bottom" : "top"; ++ } else { ++ return (vector->x > 0) ? "right" : "left"; ++ } ++} ++ ++uint32_t ++vector_length(Vector *vector) ++{ ++ // Euclidean distance formula ++ return (uint32_t)sqrt(vector->x * vector->x + vector->y * vector->y); ++} ++ ++// Spatial direction, based on focused client position. ++Client * ++client_in_direction(const char *direction, const int *skipfloat) ++{ ++ Client *cfocused = focustop(selmon); ++ Vector cfocusedposition; ++ Client *ctarget = NULL; ++ double targetdistance = INFINITY; ++ Client *c; ++ ++ if (!cfocused || cfocused->isfullscreen || (skipfloat && cfocused->isfloating)) ++ return NULL; ++ ++ cfocusedposition = position_of_box(&cfocused->geom); ++ ++ wl_list_for_each(c, &clients, link) { ++ Vector cposition; ++ Vector positiondiff; ++ uint32_t distance; ++ ++ if (c == cfocused) ++ continue; ++ ++ if (skipfloat && c->isfloating) ++ continue; ++ ++ if (!VISIBLEON(c, selmon)) ++ continue; ++ ++ cposition = position_of_box(&c->geom); ++ positiondiff = diff_of_vectors(&cfocusedposition, &cposition); ++ ++ if (strcmp(direction, direction_of_vector(&positiondiff)) != 0) ++ continue; ++ ++ distance = vector_length(&positiondiff); ++ ++ if (distance < targetdistance) { ++ ctarget = c; ++ targetdistance = distance; ++ } ++ } ++ ++ return ctarget; ++} ++ ++void ++focusdir(const Arg *arg) ++{ ++ Client *c = NULL; ++ ++ if (arg->ui == 0) ++ c = client_in_direction("left", (int *)0); ++ if (arg->ui == 1) ++ c = client_in_direction("right", (int *)0); ++ if (arg->ui == 2) ++ c = client_in_direction("top", (int *)0); ++ if (arg->ui == 3) ++ c = client_in_direction("bottom", (int *)0); ++ ++ if (c != NULL) ++ focusclient(c, 1); ++} ++ ++void ++wl_list_swap(struct wl_list *list1, struct wl_list *list2) ++{ ++ struct wl_list *prev1, *next1, *prev2, *next2; ++ struct wl_list temp; ++ ++ if (list1 == list2) { ++ // No need to swap the same list ++ return; ++ } ++ ++ // Get the lists before and after list1 ++ prev1 = list1->prev; ++ next1 = list1->next; ++ ++ // Get the lists before and after list2 ++ prev2 = list2->prev; ++ next2 = list2->next; ++ ++ // Update the next and previous pointers of adjacent lists ++ prev1->next = list2; ++ next1->prev = list2; ++ prev2->next = list1; ++ next2->prev = list1; ++ ++ // Swap the next and previous pointers of the lists to actually swap them ++ temp = *list1; ++ *list1 = *list2; ++ *list2 = temp; ++} ++ ++void ++swapdir(const Arg *arg) ++{ ++ Client *c = NULL; ++ Client *cfocused; ++ ++ if (arg->ui == 0) ++ c = client_in_direction("left", (int *)1); ++ if (arg->ui == 1) ++ c = client_in_direction("right", (int *)1); ++ if (arg->ui == 2) ++ c = client_in_direction("top", (int *)1); ++ if (arg->ui == 3) ++ c = client_in_direction("bottom", (int *)1); ++ ++ if (c == NULL) ++ return; ++ ++ cfocused = focustop(selmon); ++ wl_list_swap(&cfocused->link, &c->link); ++ arrange(selmon); ++} ++ + /* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/switchtotag/README.md b/dwl/dwl-patches/patches/switchtotag/README.md new file mode 100644 index 0000000..55175d1 --- /dev/null +++ b/dwl/dwl-patches/patches/switchtotag/README.md @@ -0,0 +1,9 @@ +### Description +Add a rule option to switch to the configured tag when a window opens, then switch back when it closes. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/switchtotag) +- [2024-09-30](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/switchtotag/switchtotag.patch) + +### Authors +- [Guido Cella](https://codeberg.org/guidocella) diff --git a/dwl/dwl-patches/patches/switchtotag/switchtotag.patch b/dwl/dwl-patches/patches/switchtotag/switchtotag.patch new file mode 100644 index 0000000..bbe1638 --- /dev/null +++ b/dwl/dwl-patches/patches/switchtotag/switchtotag.patch @@ -0,0 +1,116 @@ +From 08cfc2eceb34316cfd7eaf591a8a631e3d58ff3a Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Mon, 30 Sep 2024 08:40:25 +0200 +Subject: [PATCH] allow switching to the configured tag when a window opens + +Add a rule option to switch to the configured tag when a window opens, +then switch back when it closes. +--- + config.def.h | 6 +++--- + dwl.c | 23 +++++++++++++++++++---- + 2 files changed, 22 insertions(+), 7 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..52ea128 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -22,10 +22,10 @@ static int log_level = WLR_ERROR; + + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask switchtotag isfloating monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 1, 0, -1 }, /* Start on ONLY tag "9" */ + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index dc0c861..621b614 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -142,6 +142,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int switchtotag; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -230,6 +231,7 @@ typedef struct { + const char *id; + const char *title; + uint32_t tags; ++ bool switchtotag; + int isfloating; + int monitor; + } Rule; +@@ -245,7 +247,7 @@ typedef struct { + + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); +-static void applyrules(Client *c); ++static void applyrules(Client *c, bool map); + static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +@@ -449,7 +451,7 @@ applybounds(Client *c, struct wlr_box *bbox) + } + + void +-applyrules(Client *c) ++applyrules(Client *c, bool map) + { + /* rule matching */ + const char *appid, *title; +@@ -472,6 +474,11 @@ applyrules(Client *c) + if (r->monitor == i++) + mon = m; + } ++ if (r->switchtotag && map) { ++ c->switchtotag = selmon->tagset[selmon->seltags]; ++ mon->seltags ^= 1; ++ mon->tagset[selmon->seltags] = r->tags & TAGMASK; ++ } + } + } + setmon(c, mon, newtags); +@@ -795,7 +802,7 @@ commitnotify(struct wl_listener *listener, void *data) + * a different monitor based on its title this will likely select + * a wrong monitor. + */ +- applyrules(c); ++ applyrules(c, false); + if (c->mon) { + client_set_scale(client_surface(c), c->mon->wlr_output->scale); + } +@@ -1733,7 +1740,7 @@ mapnotify(struct wl_listener *listener, void *data) + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { +- applyrules(c); ++ applyrules(c, true); + } + printstatus(); + +@@ -2769,6 +2776,14 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (c->switchtotag) { ++ Arg a = { .ui = c->switchtotag }; ++ // Call view() -> arrange() -> checkidleinhibitor() before ++ // wlr_scene_node_destroy() to prevent a rare use after free of ++ // tree->node. ++ view(&a); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/systemd/README.md b/dwl/dwl-patches/patches/systemd/README.md new file mode 100644 index 0000000..a861b5e --- /dev/null +++ b/dwl/dwl-patches/patches/systemd/README.md @@ -0,0 +1,33 @@ +### Description +This is a simple patch that runs `systemctl --user import-environment WAYLAND_DISPLAY DISPLAY`, and `systemctl --user start dwl-session.target` after Dwl initializes, and then `systemctl --user stop dwl-session.target` when Dwl quits. This allows you to handle graceful startup and shutdown of your graphical systemd services, with the proper environment variables set. This is similar to [uwsm](https://github.com/Vladimir-csp/uwsm?tab=readme-ov-file), but it integrates natively with your existing systemd user services, and doesn't have uwsm's runtime overhead. + +You must have a `dwl-session.target` created that binds to `graphical-session.target`. You can then set the WantedBy of your systemd services to either `graphical-session.target`, or `dwl-session.target`, depending on whether you want them to start for just Dwl, or for every graphical session. + +Below is the Nix home-manager configuration I use to generate `dwl-session.target`, and its resulting output. +``` +systemd.user.targets.dwl-session.Unit = { + Description = "dwl compositor session"; + Documentation = [ "man:systemd.special(7)" ]; + BindsTo = [ "graphical-session.target" ]; + Wants = [ "graphical-session-pre.target" ]; + After = [ "graphical-session-pre.target" ]; +}; +``` + +``` +[Unit] +After=graphical-session-pre.target +BindsTo=graphical-session.target +Description=dwl compositor session +Documentation=man:systemd.special(7) +Wants=graphical-session-pre.target +``` + +### Download +- [git branch](https://github.com/Shringe/dwl/tree/systemd) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/systemd/systemd-0.7.patch) + +### Authors +- [Shringe](https://codeberg.org/Shringe) +- shringe_ at [dwl Discord](https://discord.gg/jJxZnrGPWN) + diff --git a/dwl/dwl-patches/patches/systemd/systemd-0.7.patch b/dwl/dwl-patches/patches/systemd/systemd-0.7.patch new file mode 100644 index 0000000..f911997 --- /dev/null +++ b/dwl/dwl-patches/patches/systemd/systemd-0.7.patch @@ -0,0 +1,64 @@ +From 43b3026b0744d54287eada71c84ea8be174950c3 Mon Sep 17 00:00:00 2001 +From: Shringe +Date: Sat, 12 Jul 2025 01:00:46 -0500 +Subject: [PATCH] basic implementation + +--- + dwl.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/dwl.c b/dwl.c +index c717c1d..4ba134d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -680,6 +680,15 @@ cleanup(void) + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif ++ /* Stop systemd target */ ++ if (fork() == 0) { ++ setsid(); ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "stop", "dwl-session.target", NULL ++ }); ++ exit(1); ++ } ++ + wl_display_destroy_clients(dpy); + if (child_pid > 0) { + kill(-child_pid, SIGTERM); +@@ -2222,6 +2231,31 @@ run(char *startup_cmd) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + ++ /* Import environment variables then start systemd target */ ++ if (fork() == 0) { ++ setsid(); ++ ++ /* First: import environment variables */ ++ pid_t import_pid = fork(); ++ if (import_pid == 0) { ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "import-environment", ++ "DISPLAY", "WAYLAND_DISPLAY", NULL ++ }); ++ exit(1); ++ } ++ ++ /* Wait for import to complete */ ++ waitpid(import_pid, NULL, 0); ++ ++ /* Second: start target */ ++ execvp("systemctl", (char *const[]) { ++ "systemctl", "--user", "start", "dwl-session.target", NULL ++ }); ++ ++ exit(1); ++ } ++ + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/tab/README.md b/dwl/dwl-patches/patches/tab/README.md new file mode 100644 index 0000000..9f08765 --- /dev/null +++ b/dwl/dwl-patches/patches/tab/README.md @@ -0,0 +1,24 @@ +### Description +Add tabs or a title bar to the top or bottom of windows. + +Much of the code was taken from the [bar patch](/dwl/dwl-patches/src/branch/main/patches/bar). + +**This is the new version using `drwl`. The [old version](/dwl/dwl-patches/src/branch/main/_STALE_PATCHES/tab-pango) is deprecated.** + +### Dependencies +- tllist (build dependency, required & pulled automatically by fcft) +- fcft +- pixman + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tab/tab-0.7.patch) +- [0.7 + bar](/dwl/dwl-patches/raw/branch/main/patches/tab/tab-0.7-bar.patch) +- [main 2024-11-15](/dwl/dwl-patches/raw/branch/main/patches/tab/tab.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) + +### Credits +- [sewn](https://codeberg.org/sewn) +- [MadcowOG](https://codeberg.org/MadcowOG) +- [kolumni](https://codeberg.org/kolumni) diff --git a/dwl/dwl-patches/patches/tab/tab-0.7-bar.patch b/dwl/dwl-patches/patches/tab/tab-0.7-bar.patch new file mode 100644 index 0000000..4a79bbc --- /dev/null +++ b/dwl/dwl-patches/patches/tab/tab-0.7-bar.patch @@ -0,0 +1,767 @@ +From 3944cc81d0683700405cd106edf965293abbb87c Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:27:19 -0500 +Subject: [PATCH] Add tab support + +--- + config.def.h | 25 +++- + dwl.c | 392 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 386 insertions(+), 31 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..52fcfee 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -10,6 +10,13 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ + static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ +@@ -20,6 +27,13 @@ static uint32_t colors[][3] = { + [SchemeUrg] = { 0, 0, 0x770000ff }, + }; + ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +@@ -34,12 +48,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/dwl.c b/dwl.c +index 1e199f3..e7754d3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -90,6 +91,7 @@ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -110,6 +112,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -119,6 +129,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + union { +@@ -146,7 +161,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -190,17 +205,11 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +-typedef struct { +- struct wlr_buffer base; +- struct wl_listener release; +- bool busy; +- Img *image; +- uint32_t data[]; +-} Buffer; +- + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; +@@ -275,10 +284,11 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); + static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +-static void bufdestroy(struct wlr_buffer *buffer); +-static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +-static void bufdataend(struct wlr_buffer *buffer); ++static void bufdataend(struct wlr_buffer *wlr_buffer); + static Buffer *bufmon(Monitor *m); + static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); +@@ -316,12 +326,15 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void drawbar(Monitor *m); + static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -529,14 +542,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -561,6 +576,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -650,6 +666,36 @@ baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) + return true; + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ + void + bufdestroy(struct wlr_buffer *wlr_buffer) + { +@@ -827,6 +873,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -1230,6 +1291,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1579,6 +1644,166 @@ drawbars(void) + drawbar(m); + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1635,6 +1860,12 @@ focusclient(Client *c, int lift) + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ + drawbars(); + + if (!c) { +@@ -1705,6 +1936,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1764,6 +2009,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1932,6 +2178,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1988,6 +2235,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2012,18 +2273,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2384,32 +2655,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2505,6 +2781,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2528,6 +2806,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + drawbars(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2553,6 +2832,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2568,6 +2848,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2898,14 +3179,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2979,6 +3268,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2992,6 +3283,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -3022,6 +3315,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -3038,6 +3334,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -3058,9 +3368,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -3124,6 +3437,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -3195,6 +3525,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -3208,6 +3539,7 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + drawbars(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +@@ -3322,8 +3654,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3375,6 +3709,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.47.0 + diff --git a/dwl/dwl-patches/patches/tab/tab-0.7.patch b/dwl/dwl-patches/patches/tab/tab-0.7.patch new file mode 100644 index 0000000..b7edd4f --- /dev/null +++ b/dwl/dwl-patches/patches/tab/tab-0.7.patch @@ -0,0 +1,1252 @@ +From 419fa4d6212806d9e4a3439568f8a103a57de912 Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:19:42 -0500 +Subject: [PATCH] Add tab support + +--- + Makefile | 2 +- + config.def.h | 37 ++++- + drwl.h | 320 ++++++++++++++++++++++++++++++++++++ + dwl.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 777 insertions(+), 31 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 3358bae..9bc67db 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d33d89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,13 +7,31 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; ++ ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -28,12 +46,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..02eb8e9 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,320 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static unsigned int ++drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n) ++{ ++ unsigned int tmp = 0; ++ if (drwl && drwl->font && text && n) ++ tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n); ++ return tmp < n ? tmp : n; ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index def2562..893b1f1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -12,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +60,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -68,6 +71,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -81,9 +85,11 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -103,6 +109,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -112,6 +126,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + union { +@@ -139,7 +158,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -183,6 +202,8 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +@@ -250,6 +271,12 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *wlr_buffer); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -285,10 +312,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -413,6 +443,12 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -485,14 +521,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -517,6 +555,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -595,6 +634,74 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { +@@ -676,6 +783,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -697,6 +819,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -1063,6 +1187,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1338,6 +1466,166 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1373,13 +1661,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1390,11 +1678,18 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ ++ + printstatus(); + + if (!c) { +@@ -1465,6 +1760,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1524,6 +1833,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1692,6 +2002,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1717,7 +2028,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1740,6 +2051,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ + printstatus(); + + unset_fullscreen: +@@ -1748,6 +2060,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -1772,18 +2098,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2184,32 +2520,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2318,6 +2659,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2341,6 +2684,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2366,6 +2710,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2381,6 +2726,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2627,6 +2973,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2680,14 +3028,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2753,6 +3109,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2766,6 +3124,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2796,6 +3156,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -2812,6 +3175,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2832,9 +3209,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -2898,6 +3278,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -2929,6 +3326,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2942,9 +3340,10 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + printstatus(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -3052,8 +3451,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3105,6 +3506,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +@@ -3150,7 +3555,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.47.0 + diff --git a/dwl/dwl-patches/patches/tab/tab.patch b/dwl/dwl-patches/patches/tab/tab.patch new file mode 100644 index 0000000..49276ad --- /dev/null +++ b/dwl/dwl-patches/patches/tab/tab.patch @@ -0,0 +1,1252 @@ +From d722eef53d488c9e9ed04991112376035e8c9b25 Mon Sep 17 00:00:00 2001 +From: dev-gm +Date: Sat, 16 Nov 2024 23:22:14 -0500 +Subject: [PATCH] Add tab support + +--- + Makefile | 2 +- + config.def.h | 37 ++++- + drwl.h | 320 ++++++++++++++++++++++++++++++++++++ + dwl.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++--- + 4 files changed, 777 insertions(+), 31 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d33d89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,13 +7,31 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +-static const float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++static const char *tbar_fonts[] = {"monospace:size=10"}; ++static const int tbar_top = 0; ++static const int tbar_height = -1; ++static const int tbar_borderpx = 1; ++static const int tbar_padding = 10; ++static const float tbar_scale = -1; /* -1 means use monitor scale */ ++static const int tbar_float_sel_sep = 0; /* should tbar be highlighted only on the currently selected window or on both the last selected floating window and the laste selected tiling window */ ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; ++ ++static uint32_t tbar_colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x555555ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x555555ff }, ++ [SchemeUrg] = { 0xc7c7c7ff, 0x222222ff, 0x770000ff }, ++}; ++ + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -28,12 +46,15 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++static const unsigned int floating_tbar_type = TBarLabel; ++static const int floating_tbar_only_top = 0; ++ + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol tbar type tbar only on top arrange function */ ++ { "[]=", TBarLabel, 0, tile }, ++ { "><>", TBarLabel, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", TBarMultiple, 1, monocle }, + }; + + /* monitors */ +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..02eb8e9 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,320 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2024 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ fcft_set_scaling_filter(FCFT_SCALING_FILTER_LANCZOS3); ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static unsigned int ++drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n) ++{ ++ unsigned int tmp = 0; ++ if (drwl && drwl->font && text && n) ++ tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n); ++ return tmp < n ? tmp : n; ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 9acb898..bde4009 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -12,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +61,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +72,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -82,9 +86,11 @@ + #define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) + + /* enums */ ++enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ ++enum { TBarNone, TBarLabel, TBarMultiple, TBarAlways }; /* tbar types */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -104,6 +110,14 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -113,6 +127,11 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *tbar_buffer; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int tbar_height, tbar_real_height; ++ float tbar_scale; + struct wl_list link; + struct wl_list flink; + struct wlr_box geom; /* layout-relative, includes border */ +@@ -141,7 +160,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, tbar_enabled, resize_tbar_enabled; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -184,6 +203,8 @@ typedef struct { + + typedef struct { + const char *symbol; ++ unsigned int tbar_type; /* type of tbar */ ++ int tbar_only_top; + void (*arrange)(Monitor *); + } Layout; + +@@ -251,6 +272,12 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static Buffer *bufclient(Client *c); ++static void bufdestroy(struct wlr_buffer *wlr_buffer); ++static bool bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *wlr_buffer); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -286,10 +313,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawtbars(Monitor *m, int floating, int clients_changed); ++static void drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); +@@ -411,6 +441,12 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -481,14 +517,16 @@ void + arrange(Monitor *m) + { + Client *c; ++ int enabled; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { +- wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); +- client_set_suspended(c, !VISIBLEON(c, m)); ++ enabled = VISIBLEON(c, m); ++ wlr_scene_node_set_enabled(&c->scene->node, enabled); ++ client_set_suspended(c, !enabled); + } + } + +@@ -513,6 +551,7 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -589,6 +628,74 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++Buffer * ++bufclient(Client *c) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ if (c->pool[i]->busy) ++ continue; ++ buf = c->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (int)(c->geom.width * c->tbar_scale * 4 * (c->tbar_height + 2*tbar_borderpx))); ++ buf->image = drwl_image_create(NULL, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, (int)(c->geom.width * c->tbar_scale), c->tbar_height + 2*tbar_borderpx); ++ c->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(c->drw, buf->image); ++ return buf; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { +@@ -670,6 +777,21 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ Client *c; ++ unsigned int i; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!c->tbar_enabled) ++ continue; ++ ++ for (i = 0; i < LENGTH(c->pool); i++) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +@@ -691,6 +813,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -1057,6 +1181,10 @@ createnotify(struct wl_listener *listener, void *data) + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); +@@ -1332,6 +1460,166 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawtbars(Monitor *m, int floating, int clients_changed) ++{ ++ unsigned int tbar_type, tbar_only_top, len = 0; ++ int nodraw = 0, ismonocle; ++ Client *c, *sel = NULL, *sel_in_layout = NULL; ++ ++ if (!m) ++ return; ++ ++ tbar_type = floating ? floating_tbar_type : m->lt[m->sellt]->tbar_type; ++ tbar_only_top = floating ? floating_tbar_only_top : m->lt[m->sellt]->tbar_only_top; ++ ismonocle = m->lt[m->sellt]->arrange == monocle; ++ ++ if (!clients_changed && !floating && ismonocle && (sel = focustop_onlytiled(m, 1))) { ++ wlr_scene_node_raise_to_top(&sel->scene->node); ++ if (sel->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel->tbar_buffer->node); ++ return; ++ } ++ ++ if (tbar_type == TBarNone) ++ nodraw = 2; ++ ++ if (!nodraw) { ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m)) ++ continue; ++ if (!sel && (!tbar_float_sel_sep || c->isfloating == floating)) ++ sel = c; ++ if (!sel_in_layout && c->isfloating == floating) ++ sel_in_layout = c; ++ if (c->isfloating == floating && !c->isfullscreen) ++ len++; ++ } ++ ++ if (len == 0) ++ return; ++ } ++ ++ if (tbar_type == TBarMultiple && len <= 1) ++ nodraw = 2; ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating != floating) ++ continue; ++ if (c->tbar_buffer && c->tbar_enabled && (nodraw == 2 || c->isfullscreen)) { ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 0); ++ c->tbar_enabled = 0; ++ if (m->lt[m->sellt]->arrange != tile) ++ resize(c, c->geom, 0); ++ } ++ if (!nodraw && !c->isfullscreen) { ++ drawtbar(c, tbar_type, len, ismonocle ? c : sel_in_layout, sel_in_layout); ++ if (!ismonocle && tbar_only_top) ++ nodraw = 1; ++ } ++ if (c == sel_in_layout) { ++ wlr_scene_node_raise_to_top(&sel_in_layout->scene->node); ++ if (sel_in_layout->tbar_buffer) ++ wlr_scene_node_raise_to_top(&sel_in_layout->tbar_buffer->node); ++ } ++ } ++ ++ if (m->lt[m->sellt]->arrange == tile) ++ m->lt[m->sellt]->arrange(m); ++} ++ ++void ++drawtbar(Client *c, unsigned int tbar_type, unsigned int len, Client *sel, Client *sel_in_layout) ++{ ++ Buffer *buf; ++ float width = c->geom.width * c->tbar_scale; ++ unsigned int i, scheme; ++ Client *l; ++ uint32_t tbar_border_colors[][3] = { ++ [SchemeNorm] = { [ColBg] = tbar_colors[SchemeNorm][2] }, ++ [SchemeSel] = { [ColBg] = tbar_colors[SchemeSel][2] }, ++ [SchemeUrg] = { [ColBg] = tbar_colors[SchemeUrg][2] } ++ }; ++ ++ if (!c->tbar_buffer) ++ c->tbar_buffer = wlr_scene_buffer_create(c->scene, NULL); ++ ++ for (i = 0; i < LENGTH(c->pool); i++) { ++ if (c->pool[i]) { ++ wlr_buffer_drop(&c->pool[i]->base); ++ c->pool[i] = NULL; ++ } ++ } ++ drwl_setimage(c->drw, NULL); ++ ++ if (!(buf = bufclient(c))) ++ return; ++ ++ switch (tbar_type) { ++ case TBarLabel: ++ scheme = c->isurgent ? SchemeUrg : (c == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, 0, 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, c->tbar_height + tbar_borderpx, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, 0, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ drwl_rect(c->drw, (unsigned int)width - tbar_borderpx, tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - 2*tbar_borderpx, c->tbar_height, ++ tbar_padding, client_get_title(c), 0); ++ ++ break; ++ ++ case TBarMultiple: ++ case TBarAlways: ++ width /= len; ++ i = 0; ++ ++ wl_list_for_each(l, &clients, link) { ++ if (!VISIBLEON(l, c->mon) || l->isfullscreen || l->isfloating != c->isfloating) ++ continue; ++ ++ scheme = l->isurgent ? SchemeUrg : (l == sel ? SchemeSel : SchemeNorm); ++ ++ if (tbar_borderpx) { ++ drwl_setscheme(c->drw, tbar_border_colors[scheme]); ++ drwl_rect(c->drw, (unsigned int)(width*i), 0, (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), c->tbar_height + tbar_borderpx, ++ (unsigned int)width, tbar_borderpx, 1, 1); ++ drwl_rect(c->drw, (unsigned int)(width*i), tbar_borderpx, tbar_borderpx, c->tbar_height, 1, 1); ++ if (i == len-1) ++ drwl_rect(c->drw, (unsigned int)(width*(i+1)) - tbar_borderpx, tbar_borderpx, ++ tbar_borderpx, c->tbar_height + 2*tbar_borderpx, 1, 1); ++ } ++ ++ drwl_setscheme(c->drw, tbar_colors[scheme]); ++ drwl_text(c->drw, (unsigned int)(width*i) + tbar_borderpx, tbar_borderpx, ++ (unsigned int)width - (i == len-1 ? 2*tbar_borderpx : tbar_borderpx), ++ c->tbar_height, tbar_padding, client_get_title(l), 0); ++ ++ i += 1; ++ } ++ ++ break; ++ } ++ ++ c->tbar_enabled = 1; ++ if (!c->resize_tbar_enabled) ++ resize(c, c->geom, 0); ++ ++ wlr_scene_buffer_set_dest_size(c->tbar_buffer, c->geom.width, ++ c->tbar_real_height + 2*tbar_borderpx); ++ wlr_scene_node_set_position(&c->tbar_buffer->node, 0, tbar_top ? 0 ++ : (c->geom.height - c->tbar_real_height - 2*tbar_borderpx)); ++ wlr_scene_buffer_set_buffer(c->tbar_buffer, &buf->base); ++ wlr_scene_node_set_enabled(&c->tbar_buffer->node, 1); ++ wlr_buffer_unlock(&buf->base); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1366,13 +1654,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1383,11 +1671,18 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + + client_activate_surface(old, 0); + } + } ++ ++ if (c && c->mon) ++ drawtbars(c->mon, c->isfloating, 0); ++ if (c && old_c && old_c->mon && (old_c->mon != c->mon || old_c->isfloating != c->isfloating)) ++ drawtbars(old_c->mon, old_c->isfloating, 0); ++ ++ + printstatus(); + + if (!c) { +@@ -1458,6 +1753,20 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && !c->isfloating && m->lt[m->sellt]->arrange)) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -1517,6 +1826,7 @@ incnmaster(const Arg *arg) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -1685,6 +1995,7 @@ mapnotify(struct wl_listener *listener, void *data) + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; ++ char fontattrs[12]; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); +@@ -1712,7 +2023,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1735,6 +2046,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } ++ + printstatus(); + + unset_fullscreen: +@@ -1743,6 +2055,20 @@ unset_fullscreen: + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } ++ ++ if (!(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ c->tbar_scale = tbar_scale > 0 ? tbar_scale : m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ c->tbar_real_height = (int)((float)c->tbar_height / c->tbar_scale); ++ } ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -1767,18 +2093,28 @@ void + monocle(Monitor *m) + { + Client *c; +- int n = 0; ++ int n = 0, old_tbar_enabled; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); ++ if (!n) ++ wlr_scene_node_raise_to_top(&c->scene->node); + n++; ++ if (n > 1) ++ break; ++ } ++ wl_list_for_each(c, &fstack, flink) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ continue; ++ old_tbar_enabled = c->tbar_enabled; ++ c->tbar_enabled = m->lt[m->sellt]->tbar_type != TBarNone ++ && m->lt[m->sellt]->tbar_type == TBarMultiple ? n > 1 : 1; ++ resize(c, m->w, 0); ++ c->tbar_enabled = old_tbar_enabled; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +- if ((c = focustop(m))) +- wlr_scene_node_raise_to_top(&c->scene->node); + } + + void +@@ -2150,32 +2486,37 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + struct wlr_box clip; ++ unsigned int th; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + ++ th = c->tbar_enabled ? (unsigned int)(c->tbar_real_height + 2*tbar_borderpx) : c->bw; ++ + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, tbar_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (c->tbar_enabled && tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (c->tbar_enabled && !tbar_top) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - th - c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - th - c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, tbar_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, tbar_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - th - c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); ++ ++ c->resize_tbar_enabled = c->tbar_enabled; + } + + void +@@ -2284,6 +2625,8 @@ setfloating(Client *c, int floating) + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, 0, 1); ++ drawtbars(c->mon, 1, 1); + } + + void +@@ -2307,6 +2650,7 @@ setfullscreen(Client *c, int fullscreen) + } + arrange(c->mon); + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2321,6 +2665,7 @@ setlayout(const Arg *arg) + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2336,6 +2681,7 @@ setmfact(const Arg *arg) + return; + selmon->mfact = f; + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + void +@@ -2585,6 +2931,8 @@ setup(void) + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + ++ drwl_init(); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2638,14 +2986,22 @@ tag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void + tagmon(const Arg *arg) + { + Client *sel = focustop(selmon); +- if (sel) ++ Monitor *old_mon = selmon; ++ if (sel) { + setmon(sel, dirtomon(arg->i), 0); ++ if (selmon) ++ drawtbars(selmon, sel->isfloating, 1); ++ if (old_mon && selmon != old_mon) ++ drawtbars(old_mon, sel->isfloating, 1); ++ } + } + + void +@@ -2711,6 +3067,8 @@ toggletag(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2724,6 +3082,8 @@ toggleview(const Arg *arg) + focusclient(focustop(selmon), 1); + arrange(selmon); + printstatus(); ++ drawtbars(selmon, 0, 1); ++ drawtbars(selmon, 1, 1); + } + + void +@@ -2754,6 +3114,9 @@ unmapnotify(struct wl_listener *listener, void *data) + { + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); ++ Monitor *m = c->mon; ++ unsigned int i; ++ + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; +@@ -2770,6 +3133,20 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } + ++ if (m && !c->isfloating && m->lt[m->sellt]->arrange == monocle) ++ drawtbars(m, c->isfloating, 1); ++ ++ if (c->tbar_enabled) { ++ for (i = 0; i < LENGTH(c->pool); i++) ++ if (c->pool[i]) ++ wlr_buffer_drop(&c->pool[i]->base); ++ ++ drwl_setimage(c->drw, NULL); ++ drwl_destroy(c->drw); ++ ++ wlr_scene_node_destroy(&c->tbar_buffer->node); ++ } ++ + wlr_scene_node_destroy(&c->scene->node); + printstatus(); + motionnotify(0, NULL, 0, 0, 0, 0); +@@ -2790,9 +3167,12 @@ updatemons(struct wl_listener *listener, void *data) + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; ++ char fontattrs[12]; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { ++ if (m->asleep) ++ continue; + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); +@@ -2856,6 +3236,23 @@ updatemons(struct wl_listener *listener, void *data) + if (!selmon) { + selmon = m; + } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || (c->drw && (tbar_scale > 0 || c->tbar_scale == m->wlr_output->scale))) ++ continue; ++ if (!c->drw && !(c->drw = drwl_create())) ++ die("failed to create drwl context"); ++ drwl_font_destroy(c->drw->font); ++ c->tbar_scale = m->wlr_output->scale; ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * c->tbar_scale); ++ if (!(drwl_font_create(c->drw, LENGTH(tbar_fonts), tbar_fonts, fontattrs))) ++ die("Could not load font"); ++ if (!c->tbar_height) { ++ c->tbar_height = c->drw->font->height; ++ if (tbar_height > c->tbar_height) ++ c->tbar_height = tbar_height; ++ } ++ } + } + + if (selmon && selmon->wlr_output->enabled) { +@@ -2887,6 +3284,7 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ drawtbars(c->mon, c->isfloating, 1); + } + + void +@@ -2900,9 +3298,10 @@ urgent(struct wl_listener *listener, void *data) + + c->isurgent = 1; + printstatus(); ++ drawtbars(c->mon, 1, 1); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -3010,8 +3409,10 @@ zoom(const Arg *arg) + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + ++ drawtbars(selmon, 0, 1); + focusclient(sel, 1); + arrange(selmon); ++ drawtbars(selmon, 0, 0); + } + + #ifdef XWAYLAND +@@ -3070,6 +3471,10 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; ++ c->tbar_enabled = 0; ++ c->resize_tbar_enabled = 0; ++ c->tbar_height = 0; ++ c->tbar_buffer = NULL; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +@@ -3115,7 +3520,7 @@ sethints(struct wl_listener *listener, void *data) + printstatus(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.47.0 + diff --git a/dwl/dwl-patches/patches/tablet-input/README.md b/dwl/dwl-patches/patches/tablet-input/README.md new file mode 100644 index 0000000..3b3be09 --- /dev/null +++ b/dwl/dwl-patches/patches/tablet-input/README.md @@ -0,0 +1,25 @@ +### Description +implements wlr-tablet-v2 for drawing tablets and supports cursor emulation + +inspired by @guyuming76's [branch](https://codeberg.org/guyuming76/dwl/commits/branch/graphic_tablet), with coding help from @Palanix and testing by @Thanatos + +**Please note:** wlroots modified the order of arguments to the function `wlr_surface_accepts_tablet_v2` +In versions less than or equal to 0.18.1 (against which, dwl 0.7 is typically built), the order is `tablet, surface`. +In the master branch and (anticipated) in versions later than 0.18.1, the order is `surface, tablet`. +The git branch for this patch uses the `surface, tablet` order. + +If you are attempting to use this codebase to make your own modifications to the patch, there is only one call to the `wlr_surface_accepts_tablet_v2` function, but you will need to make the argument order change if you expect to build against wlroots 0.18.1 or earlier. + +The `0.7` patch linked here accounts for the change and uses the `tablet, surface` order, but the patch is orphaned from a codebase. + +There was discussion of this matter in [Issue #141](https://codeberg.org/dwl/dwl-patches/issues/141). + +### Download +- [git branch](https://codeberg.org/fauxmight/dwl/src/branch/tablet-input) +- [main 2025-05-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-input/tablet-input.patch) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-input/tablet-input-0.7.patch) + +### Authors +- [fauxmight](https://codeberg.org/fauxmight) +- [notchoc](https://codeberg.org/notchoc) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/tablet-input/tablet-input-0.7.patch b/dwl/dwl-patches/patches/tablet-input/tablet-input-0.7.patch new file mode 100644 index 0000000..37fdf8a --- /dev/null +++ b/dwl/dwl-patches/patches/tablet-input/tablet-input-0.7.patch @@ -0,0 +1,355 @@ +From e504dc0fccfc3994962f03dc824d8907c6afc64f Mon Sep 17 00:00:00 2001 +From: choc +Date: Sat, 4 May 2024 01:16:12 +0800 +Subject: [PATCH] implement wlr-tablet-v2 + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 230 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index f955e7b..ce1b556 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,8 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ tablet-v2-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tablet-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..3ad98ef 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -4,6 +4,7 @@ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } + /* appearance */ ++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +diff --git a/dwl.c b/dwl.c +index ac9c36b..b8d129f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -50,6 +50,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -270,6 +273,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtablet(struct wlr_input_device *device); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -284,6 +288,9 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroytablet(struct wl_listener *listener, void *data); ++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); ++static void destroytablettool(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -337,6 +344,11 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy); ++static void tablettoolproximity(struct wl_listener *listener, void *data); ++static void tablettoolaxis(struct wl_listener *listener, void *data); ++static void tablettoolbutton(struct wl_listener *listener, void *data); ++static void tablettooltip(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -396,6 +408,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; + ++static struct wlr_tablet_manager_v2 *tablet_mgr; ++static struct wlr_tablet_v2_tablet *tablet = NULL; ++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; ++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; ++static struct wlr_surface *tablet_curr_surface = NULL; ++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify}; ++ + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; +@@ -1133,6 +1152,28 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtablet(struct wlr_input_device *device) ++{ ++ if (!tablet) { ++ struct libinput_device *device_handle = NULL; ++ if (!wlr_input_device_is_libinput(device) || ++ !(device_handle = wlr_libinput_get_device_handle(device))) ++ return; ++ ++ tablet = wlr_tablet_create(tablet_mgr, seat, device); ++ LISTEN_STATIC(&tablet->wlr_device->events.destroy, destroytablet); ++ if (libinput_device_config_send_events_get_modes(device_handle)) { ++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode); ++ wlr_cursor_attach_input_device(cursor, device); ++ } ++ } else if (device == tablet->wlr_device) { ++ wlr_log(WLR_ERROR, "createtablet: duplicate device"); ++ } else { ++ wlr_log(WLR_ERROR, "createtablet: already have one tablet"); ++ } ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1321,6 +1362,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void ++destroytablet(struct wl_listener *listener, void *data) ++{ ++ tablet = NULL; ++} ++ ++void ++destroytabletsurfacenotify(struct wl_listener *listener, void *data) ++{ ++ if (tablet_curr_surface) ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ tablet_curr_surface = NULL; ++} ++ ++void ++destroytablettool(struct wl_listener *listener, void *data) ++{ ++ destroytabletsurfacenotify(NULL, NULL); ++ tablet_tool = NULL; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1540,6 +1602,12 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TABLET: ++ createtablet(device); ++ break; ++ case WLR_INPUT_DEVICE_TABLET_PAD: ++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -2567,6 +2635,8 @@ setup(void) + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + ++ tablet_mgr = wlr_tablet_v2_create(dpy); ++ + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. +@@ -2596,6 +2666,10 @@ setup(void) + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); ++ LISTEN_STATIC(&cursor->events.tablet_tool_proximity, tablettoolproximity); ++ LISTEN_STATIC(&cursor->events.tablet_tool_axis, tablettoolaxis); ++ LISTEN_STATIC(&cursor->events.tablet_tool_button, tablettoolbutton); ++ LISTEN_STATIC(&cursor->events.tablet_tool_tip, tablettooltip); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); +@@ -2689,6 +2763,156 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tabletapplymap(double x, double y, struct wlr_input_device *dev) ++{ ++ Client *p; ++ struct wlr_box geom = {0}; ++ if (tabletmaptosurface && tablet_curr_surface) { ++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); ++ if (p) { ++ for (; client_get_parent(p); p = client_get_parent(p)); ++ geom.x = p->geom.x + p->bw; ++ geom.y = p->geom.y + p->bw; ++ geom.width = p->geom.width - 2 * p->bw; ++ geom.height = p->geom.height - 2 * p->bw; ++ } ++ } ++ wlr_cursor_map_input_to_region(cursor, dev, &geom); ++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); ++} ++ ++void ++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, ++ double x, double y, double dx, double dy) ++{ ++ struct wlr_surface *surface = NULL; ++ double sx, sy; ++ ++ if (!change_x && !change_y) ++ return; ++ ++ tabletapplymap(x, y, tablet->wlr_device); ++ ++ // TODO: apply constraints ++ switch (tablet_tool->wlr_tool->type) { ++ case WLR_TABLET_TOOL_TYPE_LENS: ++ case WLR_TABLET_TOOL_TYPE_MOUSE: ++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); ++ break; ++ default: ++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN); ++ break; ++ } ++ ++ motionnotify(0, NULL, 0, 0, 0, 0); ++ ++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); ++ if (surface && !wlr_surface_accepts_tablet_v2(tablet, surface)) ++ surface = NULL; ++ ++ if (surface != tablet_curr_surface) { ++ if (tablet_curr_surface) { ++ // TODO: wait until all buttons released before leaving ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface); ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ } ++ if (surface) { ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface); ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface); ++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener); ++ } ++ tablet_curr_surface = surface; ++ } ++ ++ if (surface) ++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); ++} ++ ++void ++tablettoolproximity(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_proximity_event *event = data; ++ struct wlr_tablet_tool *tool = event->tool; ++ ++ if (!tablet_tool) { ++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); ++ LISTEN_STATIC(&tablet_tool->wlr_tool->events.destroy, destroytablettool); ++ LISTEN_STATIC(&tablet_tool->events.set_cursor, setcursor); ++ } ++ ++ switch (event->state) { ++ case WLR_TABLET_TOOL_PROXIMITY_OUT: ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ destroytabletsurfacenotify(NULL, NULL); ++ break; ++ case WLR_TABLET_TOOL_PROXIMITY_IN: ++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); ++ break; ++ } ++} ++ ++void ++tablettoolaxis(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_axis_event *event = data; ++ ++ tablettoolmotion(tablet_tool, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, ++ event->x, event->y, event->dx, event->dy); ++ ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) ++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) ++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); ++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) ++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, event->tilt_y); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) ++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) ++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) ++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0); ++} ++ ++void ++tablettoolbutton(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_button_event *event = data; ++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button, ++ (enum zwp_tablet_pad_v2_button_state)event->state); ++} ++ ++void ++tablettooltip(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_tip_event *event = data; ++ ++ if (!tablet_curr_surface) { ++ struct wlr_pointer_button_event fakeptrbtnevent = { ++ .button = BTN_LEFT, ++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ? ++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED, ++ .time_msec = event->time_msec, ++ }; ++ buttonpress(NULL, (void *)&fakeptrbtnevent); ++ } ++ ++ if (event->state == WLR_TABLET_TOOL_TIP_UP) { ++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); ++ return; ++ } ++ ++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); ++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); ++} ++ + void + tile(Monitor *m) + { +-- +2.43.0 + diff --git a/dwl/dwl-patches/patches/tablet-input/tablet-input.patch b/dwl/dwl-patches/patches/tablet-input/tablet-input.patch new file mode 100644 index 0000000..e7d1211 --- /dev/null +++ b/dwl/dwl-patches/patches/tablet-input/tablet-input.patch @@ -0,0 +1,372 @@ +From 0659663b7eb9cafbd4f86779084765aa838e29cd Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Sat, 17 May 2025 23:26:15 -0500 +Subject: [PATCH] Add tablet input + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 237 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index 578194f..e0d1835 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,8 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ tablet-v2-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tablet-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..3ad98ef 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -4,6 +4,7 @@ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } + /* appearance */ ++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ +diff --git a/dwl.c b/dwl.c +index 4816159..e8d36ac 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -268,6 +271,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtablet(struct wlr_input_device *device); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -281,6 +285,9 @@ static void destroynotify(struct wl_listener *listener, void *data); + static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroytablet(struct wl_listener *listener, void *data); ++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); ++static void destroytablettool(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -333,6 +340,11 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy); ++static void tablettoolproximity(struct wl_listener *listener, void *data); ++static void tablettoolaxis(struct wl_listener *listener, void *data); ++static void tablettoolbutton(struct wl_listener *listener, void *data); ++static void tablettooltip(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -390,6 +402,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; + ++static struct wlr_tablet_manager_v2 *tablet_mgr; ++static struct wlr_tablet_v2_tablet *tablet = NULL; ++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; ++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; ++static struct wlr_surface *tablet_curr_surface = NULL; ++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify}; ++ + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; +@@ -412,6 +431,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress}; + static struct wl_listener cursor_frame = {.notify = cursorframe}; + static struct wl_listener cursor_motion = {.notify = motionrelative}; + static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; ++static struct wl_listener tablet_device_destroy = {.notify = destroytablet}; ++static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; ++static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; ++static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool}; ++static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity}; ++static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; + static struct wl_listener gpu_reset = {.notify = gpureset}; + static struct wl_listener layout_change = {.notify = updatemons}; + static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +@@ -1196,6 +1221,28 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtablet(struct wlr_input_device *device) ++{ ++ if (!tablet) { ++ struct libinput_device *device_handle = NULL; ++ if (!wlr_input_device_is_libinput(device) || ++ !(device_handle = wlr_libinput_get_device_handle(device))) ++ return; ++ ++ tablet = wlr_tablet_create(tablet_mgr, seat, device); ++ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy); ++ if (libinput_device_config_send_events_get_modes(device_handle)) { ++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode); ++ wlr_cursor_attach_input_device(cursor, device); ++ } ++ } else if (device == tablet->wlr_device) { ++ wlr_log(WLR_ERROR, "createtablet: duplicate device"); ++ } else { ++ wlr_log(WLR_ERROR, "createtablet: already have one tablet"); ++ } ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1380,6 +1427,27 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void ++destroytablet(struct wl_listener *listener, void *data) ++{ ++ tablet = NULL; ++} ++ ++void ++destroytabletsurfacenotify(struct wl_listener *listener, void *data) ++{ ++ if (tablet_curr_surface) ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ tablet_curr_surface = NULL; ++} ++ ++void ++destroytablettool(struct wl_listener *listener, void *data) ++{ ++ destroytabletsurfacenotify(NULL, NULL); ++ tablet_tool = NULL; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1587,6 +1655,12 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TABLET: ++ createtablet(device); ++ break; ++ case WLR_INPUT_DEVICE_TABLET_PAD: ++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -2580,6 +2654,8 @@ setup(void) + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + ++ tablet_mgr = wlr_tablet_v2_create(dpy); ++ + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. +@@ -2609,6 +2685,11 @@ setup(void) + wl_signal_add(&cursor->events.button, &cursor_button); + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); ++ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity); ++ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); ++ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); ++ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); ++ + + 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); +@@ -2704,6 +2785,159 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tabletapplymap(double x, double y, struct wlr_input_device *dev) ++{ ++ Client *p; ++ struct wlr_box geom = {0}; ++ if (tabletmaptosurface && tablet_curr_surface) { ++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); ++ if (p) { ++ for (; client_get_parent(p); p = client_get_parent(p)); ++ geom.x = p->geom.x + p->bw; ++ geom.y = p->geom.y + p->bw; ++ geom.width = p->geom.width - 2 * p->bw; ++ geom.height = p->geom.height - 2 * p->bw; ++ } ++ } ++ wlr_cursor_map_input_to_region(cursor, dev, &geom); ++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); ++} ++ ++void ++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, ++ double x, double y, double dx, double dy) ++{ ++ struct wlr_surface *surface = NULL; ++ double sx, sy; ++ ++ if (!change_x && !change_y) ++ return; ++ ++ tabletapplymap(x, y, tablet->wlr_device); ++ ++ // TODO: apply constraints ++ switch (tablet_tool->wlr_tool->type) { ++ case WLR_TABLET_TOOL_TYPE_LENS: ++ case WLR_TABLET_TOOL_TYPE_MOUSE: ++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); ++ break; ++ default: ++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN); ++ break; ++ } ++ ++ motionnotify(0, NULL, 0, 0, 0, 0); ++ ++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); ++ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) ++ surface = NULL; ++ ++ if (surface != tablet_curr_surface) { ++ if (tablet_curr_surface) { ++ // TODO: wait until all buttons released before leaving ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface); ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ } ++ if (surface) { ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface); ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface); ++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener); ++ } ++ tablet_curr_surface = surface; ++ } ++ ++ if (surface) ++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); ++} ++ ++void ++tablettoolproximity(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_proximity_event *event = data; ++ struct wlr_tablet_tool *tool = event->tool; ++ ++ if (!tablet_tool) { ++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); ++ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy); ++ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor); ++ } ++ ++ switch (event->state) { ++ case WLR_TABLET_TOOL_PROXIMITY_OUT: ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ destroytabletsurfacenotify(NULL, NULL); ++ break; ++ case WLR_TABLET_TOOL_PROXIMITY_IN: ++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); ++ break; ++ } ++} ++ ++void ++tablettoolaxis(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_axis_event *event = data; ++ ++ tablettoolmotion(tablet_tool, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, ++ event->x, event->y, event->dx, event->dy); ++ ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) ++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) ++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); ++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) ++ { ++ printf("DEBUGGING: In axis event handling\n"); ++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, event->tilt_x, event->tilt_y); ++ } ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) ++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) ++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) ++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0); ++} ++ ++void ++tablettoolbutton(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_button_event *event = data; ++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button, ++ (enum zwp_tablet_pad_v2_button_state)event->state); ++} ++ ++void ++tablettooltip(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_tip_event *event = data; ++ ++ if (!tablet_curr_surface) { ++ struct wlr_pointer_button_event fakeptrbtnevent = { ++ .button = BTN_LEFT, ++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ? ++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED, ++ .time_msec = event->time_msec, ++ }; ++ buttonpress(NULL, (void *)&fakeptrbtnevent); ++ } ++ ++ if (event->state == WLR_TABLET_TOOL_TIP_UP) { ++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); ++ return; ++ } ++ ++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); ++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); ++} ++ + void + tile(Monitor *m) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/tagshift/README.md b/dwl/dwl-patches/patches/tagshift/README.md new file mode 100644 index 0000000..e3d7a03 --- /dev/null +++ b/dwl/dwl-patches/patches/tagshift/README.md @@ -0,0 +1,10 @@ +### Description +Port of the [tagshift](https://dwm.suckless.org/patches/tagshift/) patch into dwl. + +Allows a user to change his view and/or focused client into the next or previous tag. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tagshift/tagshift.patch) +### Authors +- [h3nc4](https://codeberg.org/h3nc4) + me@h3nc4.com diff --git a/dwl/dwl-patches/patches/tagshift/tagshift.patch b/dwl/dwl-patches/patches/tagshift/tagshift.patch new file mode 100644 index 0000000..fac6033 --- /dev/null +++ b/dwl/dwl-patches/patches/tagshift/tagshift.patch @@ -0,0 +1,83 @@ +From 938c63ad0a8a706fba0b4db1c66397e9defdcb92 Mon Sep 17 00:00:00 2001 +From: h3nc4 +Date: Mon, 17 Mar 2025 17:38:22 -0300 +Subject: [PATCH] port the tagshift patch from dwm + +--- + config.def.h | 4 ++++ + dwl.c | 37 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..72dbaa1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -131,6 +131,10 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, ++ { MODKEY, XKB_KEY_Left, shiftview, {.i = -1 } }, ++ { MODKEY, XKB_KEY_Right, shiftview, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, shifttag, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, shifttag, {.i = +1 } }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, +diff --git a/dwl.c b/dwl.c +index cf3ef70..be1e89e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -333,6 +333,8 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void shiftview(const Arg *arg); ++static void shifttag(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -2646,6 +2648,41 @@ setup(void) + #endif + } + ++void ++shiftview(const Arg *arg) ++{ ++ Arg a; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ if (arg->i > 0) // left circular shift ++ nextseltags = (curseltags << arg->i) | (curseltags >> (TAGCOUNT - arg->i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-arg->i) | (curseltags << (TAGCOUNT + arg->i)); ++ ++ a.i = nextseltags; // Change view to the new tag ++ view(&a); ++} ++ ++void ++shifttag(const Arg *arg) ++{ ++ Arg a; ++ int nextseltags, curseltags = selmon->tagset[selmon->seltags]; ++ Client *sel = focustop(selmon); ++ if (!sel) ++ return; ++ if (arg->i > 0) // left circular shift ++ nextseltags = (curseltags << arg->i) | (curseltags >> (TAGCOUNT - arg->i)); ++ else // right circular shift ++ nextseltags = curseltags >> (-arg->i) | (curseltags << (TAGCOUNT + arg->i)); ++ ++ sel->tags = nextseltags & TAGMASK;// Apply new tag to the client ++ a.i = nextseltags; // Change view to the new tag ++ view(&a); ++ ++ arrange(selmon); ++ printstatus(); // change to 'drawbars();' if using "bars" patch ++} ++ + void + spawn(const Arg *arg) + { +-- +2.47.2 + diff --git a/dwl/dwl-patches/patches/tearing/README.md b/dwl/dwl-patches/patches/tearing/README.md new file mode 100644 index 0000000..45efbf9 --- /dev/null +++ b/dwl/dwl-patches/patches/tearing/README.md @@ -0,0 +1,17 @@ +### Description +This patch adds support for tearing protocol. To get it working `export WLR_DRM_NO_ATOMIC=1` is probably required. +Some apps would send ASYNC hint and tearing will "just work", otherwise it's possible to force specified clients to tear with a rule. + +Set rules in the config.h (exact string match): +``` +static const ForceTearingRule force_tearing[] = { + {.title = "", .appid = "hl_linux"}, + {.title = "Warcraft III", .appid = ""}, + {.title = "", .appid = "gamescope"}, +}; +``` +### Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/tearing) +- [2025-04-22](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tearing/tearing.patch) +### Authors +- [korei999](https://codeberg.org/korei999) diff --git a/dwl/dwl-patches/patches/tearing/tearing.patch b/dwl/dwl-patches/patches/tearing/tearing.patch new file mode 100644 index 0000000..feea1fd --- /dev/null +++ b/dwl/dwl-patches/patches/tearing/tearing.patch @@ -0,0 +1,362 @@ +From c5216442cfb22eed7c2d4196f46c9ad8db3a5425 Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Sun, 2 Feb 2025 13:44:41 +0200 +Subject: [PATCH] implement tearing protocol + +--- + Makefile | 5 +- + config.def.h | 8 +++ + dwl.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++---- + 3 files changed, 174 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index 578194f..7d24970 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,7 @@ dwl: dwl.o util.o + $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h tearing-control-v1-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +45,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tearing-control-v1-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/staging/tearing-control/tearing-control-v1.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 22d2171..52d38d3 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -28,6 +28,14 @@ static const Rule rules[] = { + { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ + }; + ++/* tearing */ ++static int tearing_allowed = 1; ++static const ForceTearingRule force_tearing[] = { ++ {.title = "", .appid = "hl_linux"}, ++ {.title = "Warcraft III", .appid = ""}, ++ {.title = "", .appid = "gamescope"}, ++}; ++ + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ +diff --git a/dwl.c b/dwl.c +index 4816159..747e16f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -86,6 +87,11 @@ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ + ++typedef struct ForceTearingRule { ++ const char* title; ++ const char* appid; ++} ForceTearingRule; ++ + typedef union { + int i; + uint32_t ui; +@@ -139,6 +145,7 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ enum wp_tearing_control_v1_presentation_hint tearing_hint; + } Client; + + typedef struct { +@@ -239,6 +246,17 @@ typedef struct { + struct wl_listener destroy; + } SessionLock; + ++typedef struct TearingController { ++ struct wlr_tearing_control_v1 *tearing_control; ++ struct wl_listener set_hint; ++ struct wl_listener destroy; ++} TearingController; ++ ++typedef struct SendFrameDoneData { ++ struct timespec when; ++ struct Monitor *mon; ++} SendFrameDoneData; ++ + /* function declarations */ + static void applybounds(Client *c, struct wlr_box *bbox); + static void applyrules(Client *c); +@@ -305,6 +323,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++static int moncantear(Monitor* m); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -319,6 +338,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data); + 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); +@@ -333,6 +353,9 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tearingcontrollersethint(struct wl_listener *listener, void *data); ++static void tearingcontrollerdestroy(struct wl_listener *listener, void *data); ++static void tearingnewhint(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -395,6 +418,8 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; + static struct wlr_session_lock_v1 *cur_lock; + ++static struct wlr_tearing_control_manager_v1 *tearing_control_v1; ++ + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; + static unsigned int cursor_mode; +@@ -435,6 +460,7 @@ static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; + static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; + static struct wl_listener new_session_lock = {.notify = locksession}; ++static struct wl_listener tearing_control_new_object = {.notify = tearingnewhint}; + + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); +@@ -779,6 +805,7 @@ cleanuplisteners(void) + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); + wl_list_remove(&new_session_lock.link); ++ wl_list_remove(&tearing_control_new_object.link); + #ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); + wl_list_remove(&xwayland_ready.link); +@@ -1563,6 +1590,63 @@ handlesig(int signo) + quit(NULL); + } + ++void ++tearingcontrollersethint(struct wl_listener *listener, void *data) ++{ ++ Client *c = NULL, *i = NULL; ++ TearingController *controller = wl_container_of(listener, controller, set_hint); ++ ++ struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#ifdef XWAYLAND ++ struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(controller->tearing_control->surface); ++#endif ++ ++ wl_list_for_each(i, &fstack, flink) { ++ if (i->surface.xdg == surface ++#ifdef XWAYLAND ++ || i->surface.xwayland == xsurface ++#endif ++ ) { ++ c = i; ++ break; ++ } ++ } ++ ++ if (c) { ++ enum wp_tearing_control_v1_presentation_hint hint = controller->tearing_control->current; ++ fprintf( ++ stderr, "TEARING: found surface: %p(appid: '%s', title: '%s'), hint: %d(%s)\n", ++ (void*)c, client_get_appid(c), client_get_title(c), hint, hint ? "ASYNC" : "VSYNC" ++ ); ++ c->tearing_hint = controller->tearing_control->current; ++ } ++} ++ ++void ++tearingcontrollerdestroy(struct wl_listener *listener, void *data) ++{ ++ TearingController *controller = wl_container_of(listener, controller, destroy); ++ ++ wl_list_remove(&controller->set_hint.link); ++ wl_list_remove(&controller->destroy.link); ++ free(controller); ++} ++ ++void ++tearingnewhint(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tearing_control_v1 *tearing_control = data; ++ TearingController *controller = ecalloc(1, sizeof(*controller)); ++ ++ controller->tearing_control = tearing_control; ++ ++ controller->set_hint.notify = tearingcontrollersethint; ++ wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); ++ ++ controller->destroy.notify = tearingcontrollerdestroy; ++ wl_signal_add(&tearing_control->events.destroy, &controller->destroy); ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1730,6 +1814,35 @@ locksession(struct wl_listener *listener, void *data) + wlr_session_lock_v1_send_locked(session_lock); + } + ++static inline void ++forcetearingrule(Client *c) ++{ ++ int success = 0; ++ const char* appid = client_get_appid(c); ++ const char* title = client_get_title(c); ++ ++ for (unsigned i = 0; i < LENGTH(force_tearing); ++i) { ++ if (appid) { ++ if (strcmp(force_tearing[i].appid, appid) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ ++ if (title) { ++ if (strcmp(force_tearing[i].title, title) == 0) { ++ success = 1; ++ break; ++ } ++ } ++ } ++ ++ if (success) { ++ c->tearing_hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; ++ fprintf(stderr, "Forcing tearing for appid: '%s', title: '%s'\n", appid, title); ++ } ++} ++ + void + mapnotify(struct wl_listener *listener, void *data) + { +@@ -1739,6 +1852,8 @@ mapnotify(struct wl_listener *listener, void *data) + Monitor *m; + int i; + ++ forcetearingrule(c); ++ + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + /* Enabled later by a call to arrange() */ +@@ -1977,6 +2092,13 @@ moveresize(const Arg *arg) + } + } + ++int ++moncantear(Monitor* m) ++{ ++ Client *c = focustop(m); ++ return (c && c->isfullscreen && c->tearing_hint); /* 1 == ASYNC */ ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2147,27 +2269,40 @@ quit(const Arg *arg) + void + rendermon(struct wl_listener *listener, void *data) + { +- /* This function is called every time an output is ready to display a frame, +- * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); +- Client *c; ++ struct wlr_scene_output *scene_output = m->scene_output; + struct wlr_output_state pending = {0}; +- struct timespec now; ++ SendFrameDoneData frame_done_data = {0}; + +- /* Render if no XDG clients have an outstanding resize and are visible on +- * this monitor. */ +- wl_list_for_each(c, &clients, link) { +- if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) +- goto skip; ++ m->wlr_output->frame_pending = false; ++ ++ if (!wlr_scene_output_needs_frame(scene_output)) { ++ goto skip; + } + +- wlr_scene_output_commit(m->scene_output, NULL); ++ wlr_output_state_init(&pending); ++ if (!wlr_scene_output_build_state(m->scene_output, &pending, NULL)) { ++ goto skip; ++ } ++ ++ if (tearing_allowed && moncantear(m)) { ++ pending.tearing_page_flip = true; ++ ++ if (!wlr_output_test_state(m->wlr_output, &pending)) { ++ fprintf(stderr, "Output test failed on '%s', retrying without tearing page-flip\n", m->wlr_output->name); ++ pending.tearing_page_flip = false; ++ } ++ } ++ ++ if (!wlr_output_commit_state(m->wlr_output, &pending)) ++ fprintf(stderr, "Page-flip failed on output %s", m->wlr_output->name); + +-skip: +- /* Let clients know a frame has been rendered */ +- clock_gettime(CLOCK_MONOTONIC, &now); +- wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); ++ ++skip: ++ clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when); ++ frame_done_data.mon = m; ++ wlr_scene_output_for_each_buffer(m->scene_output, sendframedoneiterator, &frame_done_data); + } + + void +@@ -2291,6 +2426,16 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++void ++sendframedoneiterator(struct wlr_scene_buffer *buffer, int x, int y, void *user_data) ++{ ++ SendFrameDoneData *data = user_data; ++ if (buffer->primary_output != data->mon->scene_output) ++ return; ++ ++ wlr_scene_buffer_send_frame_done(buffer, &data->when); ++} ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2641,6 +2786,9 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ tearing_control_v1 = wlr_tearing_control_manager_v1_create(dpy, 1); ++ wl_signal_add(&tearing_control_v1->events.new_object, &tearing_control_new_object); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/titleurgent/README.md b/dwl/dwl-patches/patches/titleurgent/README.md new file mode 100644 index 0000000..aa4c99d --- /dev/null +++ b/dwl/dwl-patches/patches/titleurgent/README.md @@ -0,0 +1,11 @@ +### Description +Whenever a client title changes set the client's urgent flag. + +Hacky solution I use to deal with qutebrowser not setting urgent flag when a new tab is opened. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/titleurgent) +- [2024-01-02](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/titleurgent/titleurgent.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/titleurgent/titleurgent.patch b/dwl/dwl-patches/patches/titleurgent/titleurgent.patch new file mode 100644 index 0000000..9a73f66 --- /dev/null +++ b/dwl/dwl-patches/patches/titleurgent/titleurgent.patch @@ -0,0 +1,80 @@ +From ab002204c8bb7c8c36828aeb89ef3cf9b0447ea6 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Fri, 29 Dec 2023 19:02:11 +1000 +Subject: [PATCH] titleurgent + +--- + config.def.h | 8 +++++--- + dwl.c | 8 ++++++++ + 2 files changed, 13 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..a5000901 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,10 +21,12 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating titleurgent monitor */ + /* examples: */ +- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "Gimp_EXAMPLE", NULL, 0, 1, 0, -1 }, /* Start on currently visible tags floating, not tiled */ ++ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, -1 }, /* Start on ONLY tag "9" */ ++ { "org.qutebrowser.qutebrowser_EXAMPLE", ++ NULL, 0, 0, 1, -1 }, + }; + + /* layout(s) */ +diff --git a/dwl.c b/dwl.c +index 6f041a0d..9486c435 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -138,6 +138,7 @@ typedef struct { + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; ++ int titleurgent; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -228,6 +229,7 @@ typedef struct { + const char *title; + uint32_t tags; + int isfloating; ++ int titleurgent; + int monitor; + } Rule; + +@@ -455,6 +457,7 @@ applyrules(Client *c) + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); ++ c->titleurgent = 0; + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) +@@ -464,6 +467,7 @@ applyrules(Client *c) + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; ++ c->titleurgent = r->titleurgent; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { +@@ -2821,6 +2825,10 @@ updatetitle(struct wl_listener *listener, void *data) + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + printstatus(); ++ else if (c->titleurgent) { ++ c->isurgent = 1; ++ printstatus(); ++ } + } + + void +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/tmux-borders/README.md b/dwl/dwl-patches/patches/tmux-borders/README.md new file mode 100644 index 0000000..e203e76 --- /dev/null +++ b/dwl/dwl-patches/patches/tmux-borders/README.md @@ -0,0 +1,15 @@ +### Description +This patch replaces the window borders of tiled windows with borders that are similar to those found in tmux. The result is that there are no more unnecessary borders along the monitor edges in tiled mode. Borders of floating windows are not affected. + +### Preview +![two clients](screenshot1.png) + +![three clients](screenshot2.png) + +### Download +- [git branch](/kerberoge/dwl/src/branch/tmux-borders) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/tmux-borders/tmux-borders-0.7.patch) + +### Authors +- [kerberoge](https://codeberg.org/kerberoge)\ + kerberoge at [dwl Discord](https://discord.gg/jJxZnrGPWN) diff --git a/dwl/dwl-patches/patches/tmux-borders/screenshot1.png b/dwl/dwl-patches/patches/tmux-borders/screenshot1.png new file mode 100644 index 0000000..ed12cc5 Binary files /dev/null and b/dwl/dwl-patches/patches/tmux-borders/screenshot1.png differ diff --git a/dwl/dwl-patches/patches/tmux-borders/screenshot2.png b/dwl/dwl-patches/patches/tmux-borders/screenshot2.png new file mode 100644 index 0000000..83ae2e2 Binary files /dev/null and b/dwl/dwl-patches/patches/tmux-borders/screenshot2.png differ diff --git a/dwl/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch b/dwl/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch new file mode 100644 index 0000000..f6839a8 --- /dev/null +++ b/dwl/dwl-patches/patches/tmux-borders/tmux-borders-0.7.patch @@ -0,0 +1,489 @@ +From 464259603cb3b7346670cc33fd9afa0f8fe0cd49 Mon Sep 17 00:00:00 2001 +From: kerberoge +Date: Sun, 1 Jun 2025 17:03:09 +0200 +Subject: [PATCH 1/1] Created tmux-borders patch for stock dwl + +--- + client.h | 6 ++ + dwl.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 280 insertions(+), 24 deletions(-) + +diff --git a/client.h b/client.h +index 42f225f..007ab7d 100644 +--- a/client.h ++++ b/client.h +@@ -291,6 +291,12 @@ client_is_unmanaged(Client *c) + return 0; + } + ++static inline int ++client_needs_borders(Client *c) ++{ ++ return c->isfloating || !c->mon->lt[c->mon->sellt]->arrange; ++} ++ + static inline void + client_notify_enter(struct wlr_surface *s, struct wlr_keyboard *kb) + { +diff --git a/dwl.c b/dwl.c +index a2711f6..c092c0e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -191,6 +191,7 @@ struct Monitor { + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ ++ struct wlr_scene_tree *borders, *fborders, *uborders; + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; +@@ -255,10 +256,12 @@ static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); + static void cleanup(void); + static void cleanupmon(struct wl_listener *listener, void *data); ++static int clientindex(Monitor *m, Client *c); + static void closemon(Monitor *m); + static void commitlayersurfacenotify(struct wl_listener *listener, void *data); + static void commitnotify(struct wl_listener *listener, void *data); + static void commitpopup(struct wl_listener *listener, void *data); ++static int countclients(Monitor *m); + static void createdecoration(struct wl_listener *listener, void *data); + static void createidleinhibitor(struct wl_listener *listener, void *data); + static void createkeyboard(struct wlr_keyboard *keyboard); +@@ -273,6 +276,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void destroyborders(struct wlr_scene_tree *t); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -285,6 +289,13 @@ static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroysessionmgr(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawclientborders(struct wlr_scene_tree *t, Client *c, ++ int cidx, int n, const float color[static 4]); ++static void drawrect(struct wlr_scene_tree *t, ++ int x, int y, int w, int h, const float color[static 4]); ++static void drawborders(Monitor *m); ++static void drawfborders(Monitor *m); ++static void drawuborders(Monitor *m); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -517,6 +528,11 @@ arrange(Monitor *m) + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); ++ ++ drawborders(m); ++ drawuborders(m); ++ drawfborders(m); ++ + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); + } +@@ -721,10 +737,34 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_scene_output_destroy(m->scene_output); + + closemon(m); ++ wlr_scene_node_destroy(&m->borders->node); ++ wlr_scene_node_destroy(&m->fborders->node); ++ wlr_scene_node_destroy(&m->uborders->node); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); + } + ++int ++clientindex(Monitor *m, Client *c) ++{ ++ unsigned int i = 0; ++ Client *ci; ++ ++ if (!c || m->lt[m->sellt]->arrange != tile) ++ return -1; ++ ++ wl_list_for_each(ci, &clients, link) { ++ if (VISIBLEON(ci, m) && !ci->isfloating && !ci->isfullscreen) { ++ if (ci == c) ++ return i; ++ else ++ i++; ++ } ++ } ++ ++ return -1; ++} ++ + void + closemon(Monitor *m) + { +@@ -743,6 +783,10 @@ closemon(Monitor *m) + selmon = NULL; + } + ++ destroyborders(m->borders); ++ destroyborders(m->fborders); ++ destroyborders(m->uborders); ++ + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, +@@ -849,6 +893,19 @@ commitpopup(struct wl_listener *listener, void *data) + wl_list_remove(&listener->link); + } + ++int ++countclients(Monitor *m) ++{ ++ unsigned int n = 0; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ n++; ++ ++ return n; ++} ++ + void + createdecoration(struct wl_listener *listener, void *data) + { +@@ -985,6 +1042,9 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->borders = wlr_scene_tree_create(layers[LyrTile]); ++ m->fborders = wlr_scene_tree_create(layers[LyrTile]); ++ m->uborders = wlr_scene_tree_create(layers[LyrTile]); + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -1171,6 +1231,15 @@ cursorwarptohint(void) + } + } + ++void ++destroyborders(struct wlr_scene_tree *t) ++{ ++ struct wlr_scene_node *node, *tmp; ++ ++ wl_list_for_each_safe(node, tmp, &t->children, link) ++ wlr_scene_node_destroy(node); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1336,6 +1405,154 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawclientborders(struct wlr_scene_tree *t, Client *c, int cidx, int n, const float color[static 4]) ++{ ++ Monitor *m; ++ int mw, x, y, w, h; ++ ++ m = c->mon; ++ mw = (int)round(m->w.width * m->mfact - 0.5 * borderpx); ++ ++ if (m->nmaster == 1 && n == 2) { ++ /* Half vertical center line */ ++ y = m->w.y + (cidx == 1 ? m->w.height / 2 : 0); ++ h = (int)round(0.5 * m->w.height); ++ drawrect(t, m->w.x + mw, y, borderpx, h, color); ++ } else if (m->nmaster != 1 && n == 2) { ++ /* Half horizontal center line */ ++ x = m->w.x + (cidx == 1 ? m->w.width / 2 : 0); ++ y = m->w.y + (int)round(0.5 * m->w.height - 0.5 * borderpx); ++ w = (int)round(0.5 * m->w.width); ++ drawrect(t, x, y, w, borderpx, color); ++ } else { ++ if (m->nmaster && n > m->nmaster) ++ /* Vertical line next to client */ ++ drawrect(t, m->w.x + mw, c->geom.y, borderpx, c->geom.height, color); ++ ++ if (n > m->nmaster && cidx < m->nmaster) { ++ /* Left half */ ++ x = m->w.x; ++ w = mw + borderpx; ++ } else if (m->nmaster && cidx >= m->nmaster) { ++ /* Right half */ ++ x = m->w.x + mw; ++ w = m->w.width - mw; ++ } else { ++ /* Full width */ ++ x = m->w.x; ++ w = m->w.width; ++ } ++ ++ if ((cidx > 0 && cidx < m->nmaster) || (cidx > m->nmaster)) ++ /* Line above client */ ++ drawrect(t, x, c->geom.y - borderpx, w, borderpx, color); ++ ++ if ((cidx < m->nmaster - 1) || (cidx >= m->nmaster && cidx < n - 1)) ++ /* Line below client */ ++ drawrect(t, x, c->geom.y + c->geom.height, w, borderpx, color); ++ } ++} ++ ++void ++drawrect(struct wlr_scene_tree *t, int x, int y, int w, int h, const float color[static 4]) ++{ ++ struct wlr_scene_rect *r; ++ ++ r = wlr_scene_rect_create(t, w, h, color); ++ wlr_scene_node_set_position(&r->node, x, y); ++} ++ ++void ++drawborders(Monitor *m) ++{ ++ int n, i; ++ int mw, tw, my = 0, ty = 0; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->borders); ++ n = countclients(m); ++ ++ if (n <= 1 || m->lt[m->sellt]->arrange != tile) ++ return; ++ ++ if (m->nmaster > 0 && n > m->nmaster) ++ mw = (int)round(m->w.width * m->mfact - 0.5 * borderpx); ++ else if (n <= m->nmaster) ++ mw = m->w.width; ++ else ++ mw = 0; ++ ++ if (mw > 0) ++ tw = m->w.width - mw - borderpx; ++ else ++ tw = m->w.width; ++ ++ /* Vertical center line */ ++ if (mw > 0 && mw < m->w.width) ++ drawrect(m->borders, m->w.x + mw, m->w.y, borderpx, m->w.height, bordercolor); ++ ++ /* Lines between master clients */ ++ for (i = 0; i < MIN(n, m->nmaster) - 1; i++) { ++ my += (m->w.height - my - borderpx * (MIN(n, m->nmaster) - i - 1)) ++ / (MIN(n, m->nmaster) - i); ++ drawrect(m->borders, m->w.x, m->w.y + my, mw, borderpx, bordercolor); ++ my += borderpx; ++ } ++ ++ /* Lines between clients on the stack */ ++ for (i = m->nmaster; i < n - 1; i++) { ++ ty += (m->w.height - ty - borderpx * (n - i - 1)) / (n - i); ++ drawrect(m->borders, m->m.x + (mw ? mw + borderpx : 0), m->w.y + ty, ++ tw, borderpx, bordercolor); ++ ty += borderpx; ++ } ++} ++ ++void ++drawfborders(Monitor *m) ++{ ++ Client *fc; ++ int n, cidx; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->fborders); ++ n = countclients(m); ++ fc = focustop(m); ++ cidx = clientindex(m, fc); ++ ++ if (n <= 1 || cidx == -1) ++ return; ++ ++ drawclientborders(m->fborders, fc, cidx, n, focuscolor); ++} ++ ++void ++drawuborders(Monitor *m) ++{ ++ Client *c; ++ int n, cidx; ++ ++ if (!m) ++ return; ++ ++ destroyborders(m->uborders); ++ n = countclients(m); ++ ++ if (n <= 1) ++ return; ++ ++ wl_list_for_each(c, &clients, link) { ++ cidx = clientindex(m, c); ++ if (cidx != -1 && c->isurgent) ++ drawclientborders(m->uborders, c, cidx, n, urgentcolor); ++ } ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1366,12 +1583,15 @@ focusclient(Client *c, int lift) + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; ++ drawuborders(c->mon); + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ +- if (!exclusive_focus && !seat->drag) ++ if (!exclusive_focus && !seat->drag) { + client_set_border_color(c, focuscolor); ++ drawfborders(c->mon); ++ } + } + + /* Deactivate old client if focus is changing */ +@@ -1389,7 +1609,8 @@ focusclient(Client *c, int lift) + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, bordercolor); +- ++ if (old_c->mon && (!c || c->mon != old_c->mon)) ++ destroyborders(old_c->mon->fborders); + client_activate_surface(old, 0); + } + } +@@ -2193,15 +2414,20 @@ resize(Client *c, struct wlr_box geo, int interact) + applybounds(c, bbox); + + /* Update scene-graph, including borders */ ++ c->bw = client_needs_borders(c) ? borderpx : 0; + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ for (int i = 0; i < 4; i++) ++ wlr_scene_node_set_enabled(&c->border[i]->node, c->bw); ++ if (c->bw) { ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); ++ wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ } + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +@@ -2359,8 +2585,13 @@ setlayout(const Arg *arg) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; ++ else ++ return; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; ++ if (selmon->lt[selmon->sellt ^ 1]->arrange == tile && !selmon->lt[selmon->sellt]->arrange) ++ /* Tiled -> floating, remove monitor borders and enable client borders */ ++ tile(selmon); + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + printstatus(); +@@ -2692,31 +2923,46 @@ void + tile(Monitor *m) + { + unsigned int mw, my, ty; +- int i, n = 0; ++ int i, n; + Client *c; ++ struct wlr_box wb; ++ int borders = m->lt[m->sellt]->arrange == tile ? 1 : 0; + +- wl_list_for_each(c, &clients, link) +- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) +- n++; ++ n = countclients(m); + if (n == 0) + return; + +- if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; +- else ++ if (n > m->nmaster && m->nmaster) { ++ mw = (int)round(m->w.width * m->mfact - (borders ? 0.5 * borderpx : 0)); ++ } else if (m->nmaster) + mw = m->w.width; ++ else ++ mw = 0; + i = my = ty = 0; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ wb.x = m->w.x; ++ wb.y = m->w.y + my; ++ wb.width = mw; ++ if (borders) ++ wb.height = (m->w.height - my - borderpx * (MIN(n, m->nmaster) - i - 1)) ++ / (MIN(n, m->nmaster) - i); ++ else ++ wb.height = (m->w.height - my) / (MIN(n, m->nmaster) - i); ++ resize(c, wb, 0); ++ my += wb.height + (borders ? borderpx : 0); + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ wb.x = m->w.x + mw + (mw && borders ? borderpx : 0); ++ wb.y = m->w.y + ty; ++ wb.width = m->w.width - mw - (mw && borders ? borderpx : 0); ++ if (borders) ++ wb.height = (m->w.height - ty - borderpx * (n - i - 1)) / (n - i); ++ else ++ wb.height = (m->w.height - ty) / (n - i); ++ resize(c, wb, 0); ++ ty += wb.height + (borders ? borderpx : 0); + } + i++; + } +@@ -2941,8 +3187,10 @@ urgent(struct wl_listener *listener, void *data) + c->isurgent = 1; + printstatus(); + +- if (client_surface(c)->mapped) ++ if (client_surface(c)->mapped) { + client_set_border_color(c, urgentcolor); ++ drawuborders(c->mon); ++ } + } + + void +@@ -3147,8 +3395,10 @@ sethints(struct wl_listener *listener, void *data) + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + printstatus(); + +- if (c->isurgent && surface && surface->mapped) ++ if (c->isurgent && surface && surface->mapped) { + client_set_border_color(c, urgentcolor); ++ drawuborders(c->mon); ++ } + } + + void +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/toggle_constraints/README.md b/dwl/dwl-patches/patches/toggle_constraints/README.md new file mode 100644 index 0000000..7f2be77 --- /dev/null +++ b/dwl/dwl-patches/patches/toggle_constraints/README.md @@ -0,0 +1,17 @@ +### Description +Adds a function called togglepointerconstraints to turn pointer constraint enforcement on and off with a keybind. + +### Usage +Add a binding for the togglepointerconstraints function in the keys[] array of config.h. The function does not take any argument. Pointer constraints default to enabled, and can be toggled on and off with the function from there. + +Example: +``` +{ MODKEY, XKB_KEY_c, togglepointerconstraints, {0}}, +``` + +### Download +- [Git branch](https://codeberg.org/thanatos/dwl/src/branch/toggle_constraints) +- [2024-03-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/toggle_constraints/toggle_constraints) + +### Authors +- [thanatos](https://codeberg.org/thanatos) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/toggle_constraints/toggle_constraints.patch b/dwl/dwl-patches/patches/toggle_constraints/toggle_constraints.patch new file mode 100644 index 0000000..e823377 --- /dev/null +++ b/dwl/dwl-patches/patches/toggle_constraints/toggle_constraints.patch @@ -0,0 +1,86 @@ +From 13bb9d86a0da2ebc82c20ee721b3eeb28592b0a0 Mon Sep 17 00:00:00 2001 +From: David Donahue +Date: Tue, 26 Mar 2024 17:45:26 -0600 +Subject: [PATCH] Added function to toggle pointer constraints via a keybind + +--- + dwl.c | 41 ++++++++++++++++++++++++++--------------- + 1 file changed, 26 insertions(+), 15 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..04d377b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -332,6 +332,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglepointerconstraints(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -400,6 +401,8 @@ static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ + ++static int enable_constraints = 1; ++ + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; +@@ -1700,22 +1703,24 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + +- wl_list_for_each(constraint, &pointer_constraints->constraints, link) +- cursorconstrain(constraint); +- +- if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { +- toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); +- if (c && active_constraint->surface == seat->pointer_state.focused_surface) { +- sx = cursor->x - c->geom.x - c->bw; +- sy = cursor->y - c->geom.y - c->bw; +- if (wlr_region_confine(&active_constraint->region, sx, sy, +- sx + dx, sy + dy, &sx_confined, &sy_confined)) { +- dx = sx_confined - sx; +- dy = sy_confined - sy; ++ if (enable_constraints){ ++ wl_list_for_each(constraint, &pointer_constraints->constraints, link) ++ cursorconstrain(constraint); ++ ++ if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { ++ toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); ++ if (c && active_constraint->surface == seat->pointer_state.focused_surface) { ++ sx = cursor->x - c->geom.x - c->bw; ++ sy = cursor->y - c->geom.y - c->bw; ++ if (wlr_region_confine(&active_constraint->region, sx, sy, ++ sx + dx, sy + dy, &sx_confined, &sy_confined)) { ++ dx = sx_confined - sx; ++ dy = sy_confined - sy; ++ } ++ ++ if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) ++ return; + } +- +- if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) +- return; + } + } + +@@ -2620,6 +2625,12 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglepointerconstraints(const Arg *arg) ++{ ++ enable_constraints = !enable_constraints; ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.43.2 + diff --git a/dwl/dwl-patches/patches/togglekblayoutandoptions/README.md b/dwl/dwl-patches/patches/togglekblayoutandoptions/README.md new file mode 100644 index 0000000..883dd6d --- /dev/null +++ b/dwl/dwl-patches/patches/togglekblayoutandoptions/README.md @@ -0,0 +1,9 @@ +### Description +Switch between multiple keyboard layouts, variants, and options at runtime. + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch) + +### Authors +- [LaKato](https://codeberg.org/LaKato) +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch b/dwl/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch new file mode 100644 index 0000000..dc7fd81 --- /dev/null +++ b/dwl/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch @@ -0,0 +1,160 @@ +From 9ee76adc7c6567650e7b34273f953fed03191c05 Mon Sep 17 00:00:00 2001 +From: LaKato +Date: Sat, 8 Mar 2025 12:14:41 -0500 +Subject: [PATCH] Update for v0.7 + +Additional changes: +- incxkbrules now uses its argument +- A new setxkbrules function has been added +- Keyboard groups are no longer looped over because only one is created +--- + config.def.h | 17 +++++++++++------ + dwl.c | 44 +++++++++++++++++++++++++++++++++----------- + 2 files changed, 44 insertions(+), 17 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..b3b4699 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -52,12 +52,15 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ +- /* example: +- .options = "ctrl:nocaps", +- */ +- .options = NULL, ++static const struct xkb_rule_names xkb_rules[] = { ++ { ++ .layout = "us", ++ }, ++ /*{ ++ .layout = "us", ++ .variant = "dvp", ++ .options = "compose:102,numpad:shift3,kpdl:semi,keypad:atm,caps:super" ++ }*/ + }; + + static const int repeat_rate = 25; +@@ -148,6 +151,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, incxkbrules, {.i = +1} }, ++ /*{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, setxkbrules, {.i = +1} },*/ + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index a2711f6..a2413e9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -249,6 +249,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void assignkeymap(struct wlr_keyboard *keyboard); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -293,6 +294,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incxkbrules(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -333,6 +335,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setxkbrules(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -404,6 +407,7 @@ static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; ++static unsigned int kblayout = 0; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ +@@ -580,6 +584,20 @@ arrangelayers(Monitor *m) + } + } + ++void ++assignkeymap(struct wlr_keyboard *keyboard) { ++ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &xkb_rules[kblayout], ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ ++ if (!keymap) ++ die("failed to compile keymap"); ++ ++ wlr_keyboard_set_keymap(keyboard, keymap); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -885,21 +903,11 @@ KeyboardGroup * + createkeyboardgroup(void) + { + KeyboardGroup *group = ecalloc(1, sizeof(*group)); +- struct xkb_context *context; +- struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + +- /* Prepare an XKB keymap and assign it to the keyboard group. */ +- context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); +- if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, +- XKB_KEYMAP_COMPILE_NO_FLAGS))) +- die("failed to compile keymap"); +- +- wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); +- xkb_keymap_unref(keymap); +- xkb_context_unref(context); ++ assignkeymap(&group->wlr_group->keyboard); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + +@@ -1524,6 +1532,13 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incxkbrules(const Arg *arg) ++{ ++ kblayout = (kblayout + arg->i) % LENGTH(xkb_rules); ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2645,6 +2660,13 @@ setup(void) + #endif + } + ++void ++setxkbrules(const Arg *arg) ++{ ++ kblayout = arg->i; ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + spawn(const Arg *arg) + { +-- +2.48.1 + diff --git a/dwl/dwl-patches/patches/touch-input/README.md b/dwl/dwl-patches/patches/touch-input/README.md new file mode 100644 index 0000000..3d37fc2 --- /dev/null +++ b/dwl/dwl-patches/patches/touch-input/README.md @@ -0,0 +1,20 @@ +### Description +Adds touchscreen functionality. + +KNOWN BUGS: +- Sometimes, the pointer moves to where the screen is pressed, but the button press doesn't occur until the screen is touched AGAIN. This means that if you touch to click button 'Q' on the screen (for instance), nothing happens; then you touch elsewhere on the screen and THEN button 'Q' registers a click. This is annoying, doesn't always happen, and I don't yet know how to fix it. + +### Download +- [git branch](https://codeberg.org/fauxmight/dwl/src/branch/touch-input) +- [2025-05-17](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/touch-input/touch-input.patch) + +### Authors +- [fauxmight](https://codeberg.org/fauxmight) +- [minego](https://codeberg.org/minego) +- [Unprex](https://github.com/Unprex) + +### Changelog +- 2025-01-01 @fauxmight took over maintenance. Previous maintainer @minego notes [lack of available time](https://codeberg.org/dwl/dwl-patches/pulls/102#issuecomment-2557944) +- 2024-02-11 Corrected issue where motion events where not sending notifications for unfocused clients such as an on screen keyboard +- 2024-03-26 Rebased, and removed #ifdef's for the pointer constraints patch which has been merged into upstream +- 2024-03-28 Removed debug diff --git a/dwl/dwl-patches/patches/touch-input/touch-input.patch b/dwl/dwl-patches/patches/touch-input/touch-input.patch new file mode 100644 index 0000000..bfd33e7 --- /dev/null +++ b/dwl/dwl-patches/patches/touch-input/touch-input.patch @@ -0,0 +1,260 @@ +From f133af7120e28f3d8bda4d4e14b3bfd477b46426 Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Sat, 17 May 2025 21:59:37 -0500 +Subject: [PATCH] Add support for touchscreen input devices + +and send the appropriate events to clients +--- + dwl.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 158 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 4816159..3a378f9 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -161,6 +162,12 @@ typedef struct { + struct wl_listener destroy; + } KeyboardGroup; + ++typedef struct TouchGroup { ++ struct wl_list link; ++ struct wlr_touch *touch; ++ Monitor *m; ++} TouchGroup; ++ + typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ +@@ -268,7 +275,9 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtouch(struct wlr_touch *touch); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); ++static void createtouch(struct wlr_touch *touch); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); + static void destroydecoration(struct wl_listener *listener, void *data); +@@ -338,6 +347,10 @@ static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); ++static void touchdown(struct wl_listener *listener, void *data); ++static void touchup(struct wl_listener *listener, void *data); ++static void touchframe(struct wl_listener *listener, void *data); ++static void touchmotion(struct wl_listener *listener, void *data); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); +@@ -405,6 +418,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static struct wl_list touches; + + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; +@@ -434,6 +448,10 @@ static struct wl_listener request_set_sel = {.notify = setsel}; + static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; + static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; ++static struct wl_listener touch_down = {.notify = touchdown}; ++static struct wl_listener touch_frame = {.notify = touchframe}; ++static struct wl_listener touch_motion = {.notify = touchmotion}; ++static struct wl_listener touch_up = {.notify = touchup}; + static struct wl_listener new_session_lock = {.notify = locksession}; + + #ifdef XWAYLAND +@@ -778,6 +796,10 @@ cleanuplisteners(void) + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); ++ wl_list_remove(&touch_down.link); ++ wl_list_remove(&touch_frame.link); ++ wl_list_remove(&touch_motion.link); ++ wl_list_remove(&touch_up.link); + wl_list_remove(&new_session_lock.link); + #ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); +@@ -1196,6 +1218,16 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtouch(struct wlr_touch *wlr_touch) ++{ ++ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); ++ ++ touch->touch = wlr_touch; ++ wl_list_insert(&touches, &touch->link); ++ wlr_cursor_attach_input_device(cursor, &wlr_touch->base); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1587,6 +1619,9 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TOUCH: ++ createtouch(wlr_touch_from_input_device(device)); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -1599,6 +1634,8 @@ inputdevice(struct wl_listener *listener, void *data) + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ if (!wl_list_empty(&touches)) ++ caps |= WL_SEAT_CAPABILITY_TOUCH; + wlr_seat_set_capabilities(seat, caps); + } + +@@ -2610,6 +2647,13 @@ setup(void) + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); + ++ wl_list_init(&touches); ++ ++ wl_signal_add(&cursor->events.touch_down, &touch_down); ++ wl_signal_add(&cursor->events.touch_frame, &touch_frame); ++ wl_signal_add(&cursor->events.touch_motion, &touch_motion); ++ wl_signal_add(&cursor->events.touch_up, &touch_up); ++ + 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); + +@@ -2782,6 +2826,120 @@ toggleview(const Arg *arg) + printstatus(); + } + ++void ++touchdown(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_down_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ uint32_t serial = 0; ++ Monitor *m; ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ ++ // Map the input to the appropriate output, to ensure that rotation is ++ // handled. ++ wl_list_for_each(m, &mons, link) { ++ if (m == NULL || m->wlr_output == NULL) { ++ continue; ++ } ++ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { ++ continue; ++ } ++ ++ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output); ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ ++ /* Find the client under the pointer and send the event along. */ ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ if (sloppyfocus) ++ focusclient(c, 0); ++ ++ if (surface != NULL) { ++ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } ++ ++ if (serial && wlr_seat_touch_num_points(seat) == 1) { ++ /* Emulate a mouse click if the touch event wasn't handled */ ++ struct wlr_pointer_button_event *button_event = data; ++ struct wlr_pointer_motion_absolute_event *motion_event = data; ++ double dx, dy; ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly); ++ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); ++ dx = lx - cursor->x; ++ dy = ly - cursor->y; ++ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy); ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; ++ buttonpress(listener, button_event); ++ } ++} ++ ++void ++touchup(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_up_event *event = data; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ if (wlr_seat_touch_num_points(seat) == 1) { ++ struct wlr_pointer_button_event *button_event = data; ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; ++ buttonpress(listener, button_event); ++ } ++ ++ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchframe(struct wl_listener *listener, void *data) ++{ ++ wlr_seat_touch_notify_frame(seat); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchmotion(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_motion_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ ++ if (c != NULL && surface != NULL) { ++ if (sloppyfocus) ++ focusclient(c, 0); ++ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } else { ++ if (sloppyfocus) ++ focusclient(NULL, 0); ++ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); ++ } ++ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy); ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++ + void + unlocksession(struct wl_listener *listener, void *data) + { +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/unclutter/README.md b/dwl/dwl-patches/patches/unclutter/README.md new file mode 100644 index 0000000..48bf85a --- /dev/null +++ b/dwl/dwl-patches/patches/unclutter/README.md @@ -0,0 +1,11 @@ +### Description +Hide the mouse cursor if it isn't being used for a certain period of time. + +### Download +- [git branch](https://codeberg.org/guidocella/dwl/src/branch/unclutter) +- [2025-06-09](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/unclutter/unclutter.patch) + +### Authors +- [Guido Cella](https://github.com/guidocella) +- [dm1tz](https://github.com/dm1tz) +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/unclutter/unclutter.patch b/dwl/dwl-patches/patches/unclutter/unclutter.patch new file mode 100644 index 0000000..4c9aede --- /dev/null +++ b/dwl/dwl-patches/patches/unclutter/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 + diff --git a/dwl/dwl-patches/patches/ungroup-keyboards/README.md b/dwl/dwl-patches/patches/ungroup-keyboards/README.md new file mode 100644 index 0000000..e56f94b --- /dev/null +++ b/dwl/dwl-patches/patches/ungroup-keyboards/README.md @@ -0,0 +1,15 @@ +### Description +Ungroup keyboard input devices based on device name. + +I wrote this patch was because keyboard device grouping breaks the behaviour of +the ydotool virtual device. This patch fixes my issue #558 in the codeberg +issue tracker. + +See the inputdevicerules patch for a more generalised version of this idea. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/ungroup-keyboards) +- [2024-06-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/ungroup-keyboards/ungroup-keyboards.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch b/dwl/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch new file mode 100644 index 0000000..b3249b6 --- /dev/null +++ b/dwl/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch @@ -0,0 +1,97 @@ +From d9b9797680ae58bdb910e3bc1f71408f6b67c0d5 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 15 Jun 2024 12:34:01 +1000 +Subject: [PATCH] ungroup-keyboards + +Ungroup keyboards based on device name. My use case is keeping the +ydotool virtual keyboard from from being grouped with other keyboards. +--- + config.def.h | 7 +++++++ + dwl.c | 29 ++++++++++++++++++++++++++++- + 2 files changed, 35 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..9ad1c256 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,13 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* keyboard input devices - used to ungroup named keyboard devices */ ++static const KBInputRule kbinputrules[] = { ++ /* name kbcreate */ ++ { "ydotoold virtual device", createungroupedkeyboard }, ++ { NULL, createkeyboard }, ++}; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index 5a31aeef..41db830b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -141,6 +141,11 @@ typedef struct { + uint32_t resize; /* configure serial of a pending resize */ + } Client; + ++typedef struct { ++ const char *name; ++ void (*kbcreate)(struct wlr_keyboard *); ++} KBInputRule; ++ + typedef struct { + uint32_t mod; + xkb_keysym_t keysym; +@@ -266,6 +271,7 @@ static void createmon(struct wl_listener *listener, void *data); + static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); ++static void createungroupedkeyboard(struct wlr_keyboard *keyboard); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -1089,6 +1095,20 @@ createpointerconstraint(struct wl_listener *listener, void *data) + &pointer_constraint->destroy, destroypointerconstraint); + } + ++void ++createungroupedkeyboard(struct wlr_keyboard *keyboard) ++{ ++ /* for keyboards that need their own keyboard group */ ++ KeyboardGroup *group = createkeyboardgroup(); ++ ++ /* Set the keymap to match the group keymap */ ++ wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap); ++ LISTEN(&keyboard->base.events.destroy, &group->destroy, destroykeyboardgroup); ++ ++ /* Add the new keyboard to the group */ ++ wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1464,10 +1484,17 @@ inputdevice(struct wl_listener *listener, void *data) + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; ++ const KBInputRule *r; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +- createkeyboard(wlr_keyboard_from_input_device(device)); ++ for (r = kbinputrules; r < END(kbinputrules); r++) { ++ if (!r->name || strstr(device->name, r->name)) { ++ r->kbcreate(wlr_keyboard_from_input_device(device)); ++ break; ++ } ++ } ++ + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/vanitygaps/README.md b/dwl/dwl-patches/patches/vanitygaps/README.md new file mode 100644 index 0000000..cbdf6b4 --- /dev/null +++ b/dwl/dwl-patches/patches/vanitygaps/README.md @@ -0,0 +1,12 @@ +### Description +Adds (inner) gaps between client windows and (outer) gaps between windows and +the screen edge in a flexible manner. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/vanitygaps) +- [main 2025-01-20](/dwl/dwl-patches/raw/branch/main/patches/vanitygaps/vanitygaps.patch) +- [vanitygaps-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/vanitygaps/vanitygaps-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) +- [Bonicgamer](https://github.com/Bonicgamer) diff --git a/dwl/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch b/dwl/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch new file mode 100644 index 0000000..622b444 --- /dev/null +++ b/dwl/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch @@ -0,0 +1,358 @@ +From 3c95b58bc2b87ebd9b8481b3b16e49d99883f0a7 Mon Sep 17 00:00:00 2001 +From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com> +Date: Mon, 17 Aug 2020 14:48:24 -0400 +Subject: [PATCH 1/2] Implement vanitygaps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 21 ++++++++ + dwl.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 161 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..39e528b1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,12 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +138,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index a2711f67..d749728a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -200,6 +200,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -273,6 +277,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +298,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -327,6 +339,7 @@ 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 setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +353,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +427,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -989,6 +1005,11 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1171,6 +1192,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1524,6 +1551,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2352,6 +2456,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2691,7 +2805,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps; + int i, n = 0; + Client *c; + +@@ -2701,22 +2815,31 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (smartgaps == n) { ++ oe = 0; // outer gaps disabled ++ } ++ + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0; + else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie; ++ i = 0; ++ my = ty = m->gappoh*oe; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my, ++ .width = mw - m->gappiv*ie, .height = h}, 0); ++ my += c->geom.height + m->gappih*ie; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ r = n - i; ++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0); ++ ty += c->geom.height + m->gappih*ie; + } + i++; + } +@@ -2739,6 +2862,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.46.0 + + +From 8b9db986eddeade22d92fb15a8c836912869be29 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Jul 2022 00:15:32 -0500 +Subject: [PATCH 2/2] allow gaps in monocle layout if requested +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 39e528b1..f4d4095d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int gappih = 10; /* horiz inner gap between windows */ + static const unsigned int gappiv = 10; /* vert inner gap between windows */ +diff --git a/dwl.c b/dwl.c +index d749728a..dbc66ef8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1879,8 +1879,12 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); + n++; ++ if (!monoclegaps) ++ resize(c, m->w, 0); ++ else ++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov, ++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0); + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +-- +2.46.0 + diff --git a/dwl/dwl-patches/patches/vanitygaps/vanitygaps.patch b/dwl/dwl-patches/patches/vanitygaps/vanitygaps.patch new file mode 100644 index 0000000..db1ddf4 --- /dev/null +++ b/dwl/dwl-patches/patches/vanitygaps/vanitygaps.patch @@ -0,0 +1,357 @@ +From 8d29d5cace06c97917fbc26c673fd37731ac4984 Mon Sep 17 00:00:00 2001 +From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com> +Date: Mon, 17 Aug 2020 14:48:24 -0400 +Subject: [PATCH 1/2] Implement vanitygaps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 21 ++++++++ + dwl.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 160 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..39e528b1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,12 @@ + /* appearance */ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ ++static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +138,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index ad21e1ba..fa823957 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -197,6 +197,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -271,6 +275,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -290,6 +295,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -323,6 +335,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 setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -336,6 +349,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -405,6 +419,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ + + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; +@@ -1048,6 +1063,11 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1230,6 +1250,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1566,6 +1592,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2357,6 +2460,16 @@ setfullscreen(Client *c, int fullscreen) + printstatus(); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2701,7 +2814,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps; + int i, n = 0; + Client *c; + +@@ -2711,22 +2824,31 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (smartgaps == n) { ++ oe = 0; // outer gaps disabled ++ } ++ + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0; + else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie; ++ i = 0; ++ my = ty = m->gappoh*oe; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my, ++ .width = mw - m->gappiv*ie, .height = h}, 0); ++ my += c->geom.height + m->gappih*ie; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ r = n - i; ++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0); ++ ty += c->geom.height + m->gappih*ie; + } + i++; + } +@@ -2749,6 +2871,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.48.0 + + +From e3d10f01df9b7d6735ce9e43ebfdca35b8639f0e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Jul 2022 00:15:32 -0500 +Subject: [PATCH 2/2] allow gaps in monocle layout if requested +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 39e528b1..f4d4095d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int gappih = 10; /* horiz inner gap between windows */ + static const unsigned int gappiv = 10; /* vert inner gap between windows */ +diff --git a/dwl.c b/dwl.c +index fa823957..59eabb54 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1922,8 +1922,12 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); + n++; ++ if (!monoclegaps) ++ resize(c, m->w, 0); ++ else ++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov, ++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0); + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +-- +2.48.0 + diff --git a/dwl/dwl-patches/patches/varcol/README.md b/dwl/dwl-patches/patches/varcol/README.md new file mode 100644 index 0000000..9e5b1b0 --- /dev/null +++ b/dwl/dwl-patches/patches/varcol/README.md @@ -0,0 +1,15 @@ +### Description +A variable column layout. + +This layout behaves much the same as the `tile` layout, but adds key bindings that can be used to: +- Increase/decrease the number of non-master columns +- Increase/decrease the colfact to adjust the column spacing +- Push a client in or out of a special `left` column +- Toggle displaying the special `left` column + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/varcol) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/varcol/varcol.patch) + +### Authors +- [minego](https://codeberg.org/minego) diff --git a/dwl/dwl-patches/patches/varcol/varcol.patch b/dwl/dwl-patches/patches/varcol/varcol.patch new file mode 100644 index 0000000..b94baa2 --- /dev/null +++ b/dwl/dwl-patches/patches/varcol/varcol.patch @@ -0,0 +1,618 @@ +From 24c197ea93388c222b72c2192b968aa80b444a9a Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 12:32:49 -0600 +Subject: [PATCH] varcol layout + +--- + config.def.h | 25 ++- + dwl.c | 29 ++++ + varcol.c | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 480 insertions(+), 3 deletions(-) + create mode 100644 varcol.c + +diff --git a/config.def.h b/config.def.h +index 9009517..9345ff2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -21,16 +21,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + static int log_level = WLR_ERROR; + + static const Rule rules[] = { +- /* app_id title tags mask isfloating monitor */ ++ /* app_id title tags mask isfloating monitor isLeft */ + /* examples: +- { "Gimp", NULL, 0, 1, -1 }, ++ { "Gimp", NULL, 0, 1, -1, 0 }, ++ { "slack", NULL, 0, 1, -1, 1 }, + */ +- { "firefox", NULL, 1 << 8, 0, -1 }, ++ { "firefox", NULL, 1 << 8, 0, -1, 0 }, + }; + + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "=O=", varcol }, /* first entry is default */ + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -152,8 +154,25 @@ static const Key keys[] = { + TAGKEYS( XKB_KEY_7, XKB_KEY_ampersand, 6), + TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7), + TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8), ++ + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} }, + ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab, pushleft, {0}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft, {0}}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I, incncols, {.i = +1 }}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D, incncols, {.i = -1 }}, ++ { MODKEY, XKB_KEY_h, setcolfact, {.f = -0.05 }}, ++ { MODKEY, XKB_KEY_l, setcolfact, {.f = +0.05 }}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Tab, pushleft, {0}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_ISO_Left_Tab,pushleft, {0}}, ++ ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_I, incncols, {.i = +1}}, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_D, incncols, {.i = -1 }}, ++ { MODKEY, XKB_KEY_h, setcolfact, {.f = -0.05 }}, ++ { MODKEY, XKB_KEY_l, setcolfact, {.f = +0.05 }}, ++ + /* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */ + { WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} }, + /* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is +diff --git a/dwl.c b/dwl.c +index 5867b0c..35343e7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -139,6 +139,8 @@ typedef struct { + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ ++ int isLeft; ++ float cfact; + } Client; + + typedef struct { +@@ -205,6 +207,18 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ float colfact[3]; /* Relative sizes of the different column types */ ++ int nmastercols; /* The number of master columns to use */ ++ int nrightcols; /* The number of right "stack" columns to use */ ++ ++ /* ++ NOTE: This patch does not set these values, but leaves these here as a ++ placeholder to make it easier to merge with patches that do set gaps. ++ */ ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + }; + + typedef struct { +@@ -228,6 +242,7 @@ typedef struct { + uint32_t tags; + int isfloating; + int monitor; ++ int isLeft; + } Rule; + + typedef struct { +@@ -284,6 +299,7 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); ++static void incncols(const Arg *arg); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); +@@ -307,6 +323,7 @@ static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); ++static void pushleft(const Arg *arg); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); +@@ -314,6 +331,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); + static void resize(Client *c, struct wlr_box geo, int interact); + static void run(char *startup_cmd); ++static void setcolfact(const Arg *arg); + 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); +@@ -340,6 +358,7 @@ static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); ++static void varcol(Monitor *m); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); +@@ -351,6 +370,7 @@ static void zoom(const Arg *arg); + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; ++static const float colfact[3] = { 0.1f, 0.6f, 0.3f }; /* The relative factors for the size of each column */ + static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; +@@ -457,12 +477,16 @@ applyrules(Client *c) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; ++ c->isLeft = 0; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; ++ if (r->isLeft >= 0) { ++ c->isLeft = r->isLeft; ++ } + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) +@@ -882,6 +906,9 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; ++ m->colfact[0] = colfact[0]; ++ m->colfact[1] = colfact[1]; ++ m->colfact[2] = colfact[2]; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; +@@ -2929,6 +2956,8 @@ zoom(const Arg *arg) + arrange(selmon); + } + ++#include "varcol.c" ++ + #ifdef XWAYLAND + void + activatex11(struct wl_listener *listener, void *data) +diff --git a/varcol.c b/varcol.c +new file mode 100644 +index 0000000..30fdb85 +--- /dev/null ++++ b/varcol.c +@@ -0,0 +1,429 @@ ++#include ++ ++static inline void ++client_get_size_hints(Client *c, struct wlr_box *max, struct wlr_box *min) ++{ ++ struct wlr_xdg_toplevel *toplevel; ++ struct wlr_xdg_toplevel_state *state; ++#ifdef XWAYLAND ++ if (client_is_x11(c)) { ++ xcb_size_hints_t *size_hints = c->surface.xwayland->size_hints; ++ if (size_hints) { ++ max->width = size_hints->max_width; ++ max->height = size_hints->max_height; ++ min->width = size_hints->min_width; ++ min->height = size_hints->min_height; ++ } ++ return; ++ } ++#endif ++ toplevel = c->surface.xdg->toplevel; ++ state = &toplevel->current; ++ max->width = state->max_width; ++ max->height = state->max_height; ++ min->width = state->min_width; ++ min->height = state->min_height; ++} ++ ++static int isleft(Client *c); ++ ++/* ++ Variable Column Layout ++ ++ - Special 'left' column, with helper to move clients in or out of the left ++ column. This is useful for things like chat, or status monitor windows that ++ you want to keep visible. ++ ++ - Variable number of master columns ++ - Variable number of right columns ++ - Variable number of master windows ++ ++ // TODO Calculate remainders ++*/ ++ ++/* ++ Move a client within a column ++ ++ w The width of the column ++ x The left position of the column ++ offset The offset of this client within the column ++ count The number of clients in the column ++*/ ++static void placeClientInColumn(Monitor *m, Client *c, int w, int x, int offset, int count, int totaln) ++{ ++ struct wlr_box geom = {0}; ++ int monheight = m->w.height; ++ ++ if (totaln > 1) { ++ /* The total available space does not include the outer vertical gapps */ ++ monheight -= (2 * m->gappov); ++ ++ /* ++ Each client removes the inner vertical gapp from its own height, ++ but that leaves us off by one so account for that here. ++ */ ++ monheight += m->gappiv; ++ } ++ ++ geom.width = w; ++ geom.height = (int) floor(monheight / count); ++ ++ geom.x = x; ++ geom.y = m->w.y + (offset * geom.height); ++ ++ /* Adjust the height to account for gapps AFTER adjusting the y position */ ++ if (totaln > 1) { ++ geom.height -= m->gappiv; ++ } ++ ++ resize(c, geom, 0); ++} ++ ++/* ++ variable column layout ++ ++ This layout has a variable number of columns, in 3 categories. ++ 0-1 small left columns, containing clients that have been "pushed" left ++ 1-n master columns ++ 0-n right columns ++*/ ++void varcol(Monitor *m) ++{ ++ int i, mastern, masterw, rightn, leftw, rightw, x, monwidth; ++ unsigned int leftn, totaln, coln, offset; ++ float colfacts; ++ Client *c, *tmp; ++ int nmastercols = m->nmastercols; ++ int nrightcols = m->nrightcols; ++ struct wl_list left_clients; ++ ++ /* ++ Remove each of window that belongs in the left column, so they can be ++ reattached at the end of the list below. ++ */ ++ wl_list_init(&left_clients); ++ ++ i = 0; ++ wl_list_for_each_safe(c, tmp, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (i < m->nmaster) { ++ /* Master */ ++ ; ++ } else if (isleft(c)) { ++ /* Left; Detach and put in the left list */ ++ wl_list_remove(&c->link); ++ wl_list_insert(&left_clients, &c->link); ++ } ++ ++ i++; ++ } ++ ++ /* Reattach the left clients to the main list */ ++ wl_list_for_each_reverse_safe(c, tmp, &left_clients, link) { ++ wl_list_remove(&c->link); ++ wl_list_insert(clients.prev, &c->link); ++ } ++ ++ /* Count the windows for each column type */ ++ totaln = leftn = rightn = mastern = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (mastern < m->nmaster) { ++ mastern++; ++ } else if (isleft(c)) { ++ leftn++; ++ } else { ++ rightn++; ++ } ++ ++ totaln++; ++ } ++ ++ nmastercols = MAX(MIN(mastern, nmastercols), 1); ++ nrightcols = MAX(MIN(rightn, nrightcols), 1); ++ ++ if (mastern == 0) { ++ return; ++ } ++ ++ /* Calculate the total colfacts value */ ++ colfacts = 0; ++ ++ /* Left column */ ++ if (leftn > 0) { ++ colfacts += m->colfact[0]; ++ } ++ ++ /* Center column(s) */ ++ for (i = 0; i < nmastercols; i++) { ++ colfacts += m->colfact[1]; ++ } ++ ++ /* Right column(s) */ ++ if (rightn > 0) { ++ for (i = 0; i < nrightcols; i++) { ++ colfacts += m->colfact[2]; ++ } ++ } ++ ++ /* Calculate the usable width, with gapps removed */ ++ monwidth = m->w.width; ++ ++ if (rightn > 0) { ++ /* Gap to the left of reach right column */ ++ monwidth -= m->gappih * nrightcols; ++ } ++ if (leftn > 0) { ++ /* Gap on the right of the left column */ ++ monwidth -= m->gappih; ++ } ++ ++ if (totaln > 1) { ++ /* Outer gaps */ ++ monwidth -= (2 * m->gappoh); ++ } ++ ++ /* Calculate the width for each column type */ ++ leftw = (int) ((monwidth / colfacts) * m->colfact[0]); ++ masterw = (int) ((monwidth / colfacts) * m->colfact[1]); ++ rightw = (int) ((monwidth / colfacts) * m->colfact[2]); ++ ++ /* Adjust right and left column to fit all clients */ ++ wl_list_for_each(c, &clients, link) { ++ struct wlr_box min = {0}, max = {0}; ++ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ /* Get client size hints */ ++ client_get_size_hints(c, &max, &min); ++ ++ if (i < mastern) { ++ /* Master columns */ ++ ++ } else if (!isleft(c)) { ++ /* Right columns */ ++ x = min.width - rightw; ++ if (x > 0) { ++ rightw += x; ++ masterw -= x; ++ } ++ } else if (leftn > 0) { ++ /* left column */ ++ x = min.width - leftw; ++ if (x > 0) { ++ leftw += x; ++ masterw -= x; ++ } ++ } ++ } ++ ++ /* Place each client */ ++ x = m->w.x; ++ if (leftn > 0) { ++ x += leftw; ++ x += m->gappih; ++ } ++ if (totaln > 1) { ++ x += m->gappoh; ++ } ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link) { ++ struct wlr_box min = {0}, max = {0}; ++ ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ /* Get client size hints */ ++ client_get_size_hints(c, &max, &min); ++ ++ if (i < mastern) { ++ /* Master columns */ ++ ++ /* Offset within the section */ ++ offset = i; ++ ++ /* Max number of items in each master column */ ++ coln = (unsigned int) ceil((float) mastern / nmastercols); ++ ++ placeClientInColumn(m, c, masterw, x, offset % coln, coln, totaln); ++ ++ /* Only increment x if this is the last client in this column */ ++ if ((++offset % coln) == 0) { ++ x += masterw; ++ x += m->gappih; ++ } ++ } else if (!isleft(c)) { ++ /* Right columns */ ++ ++ /* Offset within the section */ ++ offset = (i - mastern); ++ ++ /* Max number of items in each right column */ ++ coln = (unsigned int) ceil((float) rightn / nrightcols); ++ ++ placeClientInColumn(m, c, rightw, x, offset % coln, coln, totaln); ++ ++ /* Only increment x if this is the last client in this column */ ++ if ((++offset % coln) == 0) { ++ x += rightw; ++ x += m->gappih; ++ } ++ } else if (leftn > 0) { ++ /* left column */ ++ x = m->w.x; ++ if (totaln > 1) { ++ x += m->gappoh; ++ } ++ ++ /* Offset within the section */ ++ offset = i - (mastern + rightn); ++ ++ /* There is only one left column */ ++ coln = leftn; ++ ++ placeClientInColumn(m, c, leftw, x, offset, leftn, totaln); ++ } ++ ++ i++; ++ } ++} ++ ++static int isleft(Client *c) ++{ ++ if (c == NULL) { ++ return 0; ++ } ++ ++ if (c->mon != NULL && c->mon->m.width <= 2000) { ++ /* The left column is not worth using on a small monitor */ ++ return 0; ++ } ++ ++ return c->isLeft; ++} ++ ++/* Return non-zero if the currently selected client is in a master column */ ++static int ismaster(void) ++{ ++ Client *c, *sel = focustop(selmon); ++ int i; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return 0; ++ } ++ ++ i = 0; ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (sel == c) { ++ /* c is the selected client, and is index i */ ++ if (i < selmon->nmaster) { ++ return 1; ++ } else { ++ return 0; ++ } ++ } ++ i++; ++ } ++ ++ return 0; ++} ++ ++/* A value >= 1.0 sets that colfact to that value - 1.0 */ ++void setcolfact(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ int index = 1; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ if (ismaster()) { ++ index = 0; ++ /* master */ ++ index = 0; ++ } else if (isleft(sel)) { ++ /* left */ ++ index = -1; ++ } else { ++ /* right */ ++ index = 1; ++ } ++ index++; ++ ++ if (arg->f >= 1.0) { ++ selmon->colfact[index] = arg->f - 1.0f; ++ } else { ++ /* Adjust the argument based on the selected column */ ++ selmon->colfact[index] += arg->f; ++ } ++ ++ if (selmon->colfact[index] < 0.1) { ++ selmon->colfact[index] = 0.1f; ++ } else if (selmon->colfact[index] > 0.9) { ++ selmon->colfact[index] = 0.9f; ++ } ++ arrange(selmon); ++} ++ ++static void pushleft(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ sel->isLeft = !sel->isLeft; ++ focusclient(sel, 1); ++ arrange(selmon); ++} ++ ++/* ++ Modify either the right or master column count ++*/ ++void incncols(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) { ++ return; ++ } ++ ++ if (selmon->nmastercols < 1) selmon->nmastercols = 1; ++ if (selmon->nrightcols < 1) selmon->nrightcols = 1; ++ ++ if (ismaster()) { ++ /* master */ ++ selmon->nmastercols += arg->i; ++ ++ /* Auto adjust nmaster as well */ ++ selmon->nmaster = selmon->nmastercols; ++ } else if (isleft(sel)) { ++ /* left */ ++ ; ++ } else { ++ /* right */ ++ selmon->nrightcols += arg->i; ++ } ++ ++ if (selmon->nmastercols < 1) selmon->nmastercols = 1; ++ if (selmon->nrightcols < 1) selmon->nrightcols = 1; ++ ++ arrange(selmon); ++} ++ +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/viewnextocctag/README.md b/dwl/dwl-patches/patches/viewnextocctag/README.md new file mode 100644 index 0000000..d1a43d2 --- /dev/null +++ b/dwl/dwl-patches/patches/viewnextocctag/README.md @@ -0,0 +1,9 @@ +### Description +View the next or previous tag, skipping any tags that do not have any clients. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/viewnextocctag) +- [2023-01-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/viewnextocctag/viewnextocctag.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) diff --git a/dwl/dwl-patches/patches/viewnextocctag/viewnextocctag.patch b/dwl/dwl-patches/patches/viewnextocctag/viewnextocctag.patch new file mode 100644 index 0000000..f797431 --- /dev/null +++ b/dwl/dwl-patches/patches/viewnextocctag/viewnextocctag.patch @@ -0,0 +1,92 @@ +From 330fa634a83e9b332494fade75552e02583bad6c Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Sat, 30 Dec 2023 13:39:31 +1000 +Subject: [PATCH] viewnextocctag + +--- + config.def.h | 2 ++ + dwl.c | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 36 insertions(+) + +diff --git a/config.def.h b/config.def.h +index a784eb4f..e1a6a428 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -130,6 +130,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_H, viewnextocctag, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_L, viewnextocctag, {.i = +1} }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index 6f041a0d..df5461d0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -304,6 +304,7 @@ static void motionnotify(uint32_t time, struct wlr_input_device *device, double + double sy, double sx_unaccel, double sy_unaccel); + static void motionrelative(struct wl_listener *listener, void *data); + static void moveresize(const Arg *arg); ++unsigned int nextocctag(int); + static void outputmgrapply(struct wl_listener *listener, void *data); + static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); + static void outputmgrtest(struct wl_listener *listener, void *data); +@@ -344,6 +345,7 @@ static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); ++static void viewnextocctag(const Arg *argint); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); + static Monitor *xytomon(double x, double y); +@@ -1868,6 +1870,27 @@ moveresize(const Arg *arg) + } + } + ++unsigned int ++nextocctag(int direction) ++{ ++ unsigned int seltag = selmon->tagset[selmon->seltags]; ++ unsigned int occ = 0, i; ++ Client *c; ++ ++ wl_list_for_each(c, &clients, link) ++ occ |= c->tags; ++ ++ for (i=0; i 0) ? ++ (seltag == (1u << (TAGCOUNT - 1)) ? 1u : seltag << 1) : ++ (seltag == 1 ? (1u << (TAGCOUNT - 1)) : seltag >> 1); ++ if (seltag & occ) ++ break; ++ } ++ ++ return seltag & TAGMASK; ++} ++ + void + outputmgrapply(struct wl_listener *listener, void *data) + { +@@ -2852,6 +2875,17 @@ view(const Arg *arg) + printstatus(); + } + ++void ++viewnextocctag(const Arg *arg) ++{ ++ unsigned int tmp; ++ ++ if ((tmp = nextocctag(arg->i)) == selmon->tagset[selmon->seltags]) ++ return; ++ ++ view(&(const Arg){.ui = tmp}); ++} ++ + void + virtualkeyboard(struct wl_listener *listener, void *data) + { +-- +2.45.1 + diff --git a/dwl/dwl-patches/patches/virtual-pointer/README.md b/dwl/dwl-patches/patches/virtual-pointer/README.md new file mode 100644 index 0000000..451d26e --- /dev/null +++ b/dwl/dwl-patches/patches/virtual-pointer/README.md @@ -0,0 +1,12 @@ +### Description +implement wlr_virtual_pointer_v1 for things like wayvnc server to work + +**NOTE:** no longer neccessary if you are using a DWL version after https://codeberg.org/dwl/dwl/commit/ac6074f4fdb8cc263c877f08e16a5805d3bb22d2 + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/v0.5/virtual-pointer) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0096e49402bc59b4050e12cdb9befb79d0011006/virtual-pointer/virtual-pointer.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [youbitchoc](https://github.com/youbitchoc) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/virtual-pointer/virtual-pointer.patch b/dwl/dwl-patches/patches/virtual-pointer/virtual-pointer.patch new file mode 100644 index 0000000..e0a1803 --- /dev/null +++ b/dwl/dwl-patches/patches/virtual-pointer/virtual-pointer.patch @@ -0,0 +1,126 @@ +From 4ab53a41256c8f2eac4c003c43b798b6aa312919 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Mon, 8 Apr 2024 11:39:18 -0500 +Subject: [PATCH] implement virtual-pointer + +--- + dwl.c | 49 +++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 39 insertions(+), 10 deletions(-) + +diff --git a/dwl.c b/dwl.c +index ef27a1d..20840cd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -318,11 +319,13 @@ static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); ++static void updatecapabilities(void); + static void updatemons(struct wl_listener *listener, void *data); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); ++static void virtualpointer(struct wl_listener *listener, void *data); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -357,6 +360,7 @@ static struct wlr_output_manager_v1 *output_mgr; + static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; + static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; + static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; ++static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; + + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; +@@ -1338,7 +1342,6 @@ inputdevice(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; +- uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: +@@ -1351,15 +1354,8 @@ inputdevice(struct wl_listener *listener, void *data) + /* TODO handle other input device types */ + break; + } +- +- /* We need to let the wlr_seat know what our capabilities are, which is +- * communiciated to the client. In dwl we always have a cursor, even if +- * there are no pointer devices, so we always include that capability. */ +- /* TODO do we actually require a cursor? */ +- caps = WL_SEAT_CAPABILITY_POINTER; +- if (!wl_list_empty(&keyboards)) +- caps |= WL_SEAT_CAPABILITY_KEYBOARD; +- wlr_seat_set_capabilities(seat, caps); ++ ++ updatecapabilities(); + } + + int +@@ -2331,6 +2327,8 @@ setup(void) + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); ++ virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); ++ LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); +@@ -2536,6 +2534,21 @@ unmapnotify(struct wl_listener *listener, void *data) + motionnotify(0); + } + ++void ++updatecapabilities(void) ++{ ++ uint32_t caps; ++ ++ /* We need to let the wlr_seat know what our capabilities are, which is ++ * communicated to the client. In dwl we always have a cursor, even if ++ * there are no pointer devices, so we always include that capability. */ ++ /* TODO do we actually require a cursor? */ ++ caps = WL_SEAT_CAPABILITY_POINTER; ++ if (!wl_list_empty(&keyboards)) ++ caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ wlr_seat_set_capabilities(seat, caps); ++} ++ + void + updatemons(struct wl_listener *listener, void *data) + { +@@ -2674,6 +2687,22 @@ virtualkeyboard(struct wl_listener *listener, void *data) + { + struct wlr_virtual_keyboard_v1 *keyboard = data; + createkeyboard(&keyboard->keyboard); ++ updatecapabilities(); ++} ++ ++void ++virtualpointer(struct wl_listener *listener, void *data) ++{ ++ struct wlr_virtual_pointer_v1_new_pointer_event *event = data; ++ struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; ++ struct wlr_input_device *device = &pointer->pointer.base; ++ createpointer(&pointer->pointer); ++ updatecapabilities(); ++ ++ if (event->suggested_output) { ++ wlr_cursor_map_input_to_output(cursor, device, ++ event->suggested_output); ++ } + } + + Monitor * +-- +2.43.2 + diff --git a/dwl/dwl-patches/patches/warpcursor/README.md b/dwl/dwl-patches/patches/warpcursor/README.md new file mode 100644 index 0000000..34dadb9 --- /dev/null +++ b/dwl/dwl-patches/patches/warpcursor/README.md @@ -0,0 +1,15 @@ +### Description +Warp cursor to the centre of newly focused clients. + +Only moves the cursor if the cursor is currently not on the new client. + +This is my version of the orphaned cursorwarp patch except I left out the +config flag as I think it is unnecessary. + +### Download +- [git branch](https://codeberg.org/bencc/dwl/src/branch/warpcursor) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/warpcursor/warpcursor.patch) + +### Authors +- [Ben Collerson](https://codeberg.org/bencc) +- [Faerryn](https://github.com/faerryn) diff --git a/dwl/dwl-patches/patches/warpcursor/warpcursor.patch b/dwl/dwl-patches/patches/warpcursor/warpcursor.patch new file mode 100644 index 0000000..6ac0329 --- /dev/null +++ b/dwl/dwl-patches/patches/warpcursor/warpcursor.patch @@ -0,0 +1,71 @@ +From 4951ccc89dac2b557994b2f6c3aacb2398e2d1b1 Mon Sep 17 00:00:00 2001 +From: Ben Collerson +Date: Thu, 4 Jan 2024 20:30:01 +1000 +Subject: [PATCH] warpcursor + +--- + dwl.c | 27 +++++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 145fd018..f7ad6c13 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -347,6 +347,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void warpcursor(const Client *c); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -514,6 +515,7 @@ arrange(Monitor *m) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); ++ warpcursor(focustop(selmon)); + } + + void +@@ -1323,6 +1325,10 @@ focusclient(Client *c, int lift) + if (locked) + return; + ++ /* Warp cursor to center of client if it is outside */ ++ if (lift) ++ warpcursor(c); ++ + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); +@@ -2927,6 +2933,27 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++void ++warpcursor(const Client *c) { ++ if (cursor_mode != CurNormal) { ++ return; ++ } ++ if (!c && selmon) { ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ selmon->w.x + selmon->w.width / 2.0 , ++ selmon->w.y + selmon->w.height / 2.0); ++ } ++ else if ( c && (cursor->x < c->geom.x || ++ cursor->x > c->geom.x + c->geom.width || ++ cursor->y < c->geom.y || ++ cursor->y > c->geom.y + c->geom.height)) ++ wlr_cursor_warp_closest(cursor, ++ NULL, ++ c->geom.x + c->geom.width / 2.0, ++ c->geom.y + c->geom.height / 2.0); ++} ++ + Monitor * + xytomon(double x, double y) + { +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/wayland-socket-handover/README.md b/dwl/dwl-patches/patches/wayland-socket-handover/README.md new file mode 100644 index 0000000..ffc278a --- /dev/null +++ b/dwl/dwl-patches/patches/wayland-socket-handover/README.md @@ -0,0 +1,21 @@ +### Description + +When your Wayland compositor crashes, your entire session dies with it. This +patch allows your session to survive a restart or crash of dwl by passing in +the Wayland socket from an outside wrapper program such as +[wl-restart](https://github.com/Ferdi265/wl-restart). + +To use, apply this patch and start dwl with `wl-restart --env dwl [args...]`. +This automatically restarts dwl when it crashes and tries to keep applications +alive. + +Currently, only **Qt6** supports surviving a compositor restart. The +environment variable `QT_WAYLAND_RECONNECT=1` needs to be set in order for Qt +apps to stay alive when the compositor crashes. + +## Download +- [git branch](https://codeberg.org/yrlf/dwl/src/branch/feature-socket-handover) +- [2025-03-26](https://codeberg.org/yrlf/dwl-patches/raw/branch/main/patches/wayland-socket-handover/wayland-socket-handover.patch) + +### Authors +- [yrlf](https://codeberg.org/yrlf) diff --git a/dwl/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch b/dwl/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch new file mode 100644 index 0000000..ad026ff --- /dev/null +++ b/dwl/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch @@ -0,0 +1,81 @@ +From 1513c816f834b1620313e69318e2789aa2f091d0 Mon Sep 17 00:00:00 2001 +From: Ferdinand Bachmann +Date: Wed, 26 Mar 2025 17:18:17 +0100 +Subject: [PATCH] startup: implement wayland socket handover (wl-restart --env + mechanism) + +--- + dwl.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 45 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 4816159..2f2ed02 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2232,11 +2233,54 @@ resize(Client *c, struct wlr_box geo, int interact) + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } + ++static const char* ++perform_socket_handover(void) ++{ ++ char *socket_name = NULL; ++ int socket_fd = -1; ++ ++ /* Parse wayland socket handover environment variables */ ++ if (getenv("WAYLAND_SOCKET_NAME")) { ++ socket_name = getenv("WAYLAND_SOCKET_NAME"); ++ unsetenv("WAYLAND_SOCKET_NAME"); ++ } ++ ++ if (getenv("WAYLAND_SOCKET_FD")) { ++ socket_fd = atoi(getenv("WAYLAND_SOCKET_FD")); ++ unsetenv("WAYLAND_SOCKET_FD"); ++ fcntl(socket_fd, F_SETFD, FD_CLOEXEC); ++ } ++ ++ /* Warn if either environment variable is missing */ ++ if (!socket_name && socket_fd == -1) { ++ return NULL; ++ } else if (!socket_name) { ++ fprintf(stderr, "Wayland socket handover failed, missing WAYLAND_SOCKET_FD\n"); ++ return NULL; ++ } else if (socket_fd == -1) { ++ fprintf(stderr, "Wayland socket handover failed, missing WAYLAND_SOCKET_NAME\n"); ++ return NULL; ++ } ++ ++ /* Add socket to the Wayland display */ ++ if (wl_display_add_socket_fd(dpy, socket_fd) < 0) { ++ fprintf(stderr, "Wayland socket handover failed, failed to add socket FD to display\n"); ++ return NULL; ++ } ++ ++ return socket_name; ++} ++ + void + run(char *startup_cmd) + { ++ /* Attempt Wayland socket handover. */ ++ const char *socket = perform_socket_handover(); ++ + /* Add a Unix socket to the Wayland display. */ +- const char *socket = wl_display_add_socket_auto(dpy); ++ if (!socket) ++ socket = wl_display_add_socket_auto(dpy); ++ + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); +-- +2.49.0 + diff --git a/dwl/dwl-patches/patches/winview/README.md b/dwl/dwl-patches/patches/winview/README.md new file mode 100644 index 0000000..4fb8dd5 --- /dev/null +++ b/dwl/dwl-patches/patches/winview/README.md @@ -0,0 +1,35 @@ +### Description +Implements the function `winview` which switches the visible tags to the tags on which the current client is visible. + +This patch is inspired from . Citing the description of the dwm patch: + +> Dwm tags are a powerfull feature that allows organizing windows in workspaces. Sometime it can be difficult to remember the tag to activate to unhide a window. With the winview patch the window to unhide can be selected from the all-window view. The user switches to the all-window view (Mod1-0), selects the window (Mod1-j/k or using the mouse) and press Mod1-o. The key Mod1-o switches the view to the selected window tag. +> +> #### Recommend patches +> +> The grid layout is well adapted to display many windows in a limited space. Using both grid and pertag patches you will be able to select this layout for the all-window view while keeping your preferred layout for the other views. +> Configuration and Installation +> Using the default configuration file +> +> Make sure the directory where you build dwm does not contain a config.h file; +> Apply the patch; +> Run make and make install. +> +> Using an existing customised configuration file +> +> Apply the patch; Add the following element in the keys array: +> +> `{ MODKEY, XK_o, winview, {0} },` +> +> Run make and make install. +> +> An example of how to insert this line can be found in the default config file template, config.def.h. + +### Download +- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview.patch) +- [2024-06-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview-20240606.patch) +- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/winview) + +### Authors +- [Dhruva Sambrani](https://codeberg.org/dhruva_sambrani) + diff --git a/dwl/dwl-patches/patches/winview/winview-20240606.patch b/dwl/dwl-patches/patches/winview/winview-20240606.patch new file mode 100644 index 0000000..d3fc099 --- /dev/null +++ b/dwl/dwl-patches/patches/winview/winview-20240606.patch @@ -0,0 +1,42 @@ +diff --git a/config.def.h b/config.def.h +index a784eb4..d09ee55 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -140,6 +140,7 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY, XKB_KEY_o, winview, {0}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +diff --git a/dwl.c b/dwl.c +index 6f041a0..65df112 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -346,6 +346,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void winview(const Arg *a); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -2877,6 +2878,17 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, &pointer.base, event->suggested_output); + } + ++void ++winview(const Arg *a) { ++ Arg b = {0}; ++ Client *sel = focustop(selmon); ++ if(!sel) ++ return; ++ b.ui = sel -> tags; ++ view(&b); ++ return; ++} ++ + Monitor * + xytomon(double x, double y) + { diff --git a/dwl/dwl-patches/patches/winview/winview.patch b/dwl/dwl-patches/patches/winview/winview.patch new file mode 100644 index 0000000..66e6b20 --- /dev/null +++ b/dwl/dwl-patches/patches/winview/winview.patch @@ -0,0 +1,42 @@ +diff --git a/config.def.h b/config.def.h +index 22d2171..b812525 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -143,6 +143,7 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, ++ { MODKEY, XKB_KEY_o, winview, {0}}, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, +diff --git a/dwl.c b/dwl.c +index dc0c861..5c6862c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -351,6 +351,7 @@ static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); + static void virtualkeyboard(struct wl_listener *listener, void *data); + static void virtualpointer(struct wl_listener *listener, void *data); ++static void winview(const Arg *a); + static Monitor *xytomon(double x, double y); + static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +@@ -2942,6 +2943,17 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); + } + ++void ++winview(const Arg *a) { ++ Arg b = {0}; ++ Client *sel = focustop(selmon); ++ if(!sel) ++ return; ++ b.ui = sel -> tags; ++ view(&b); ++ return; ++} ++ + Monitor * + xytomon(double x, double y) + { diff --git a/dwl/dwl-patches/patches/xwayland-handle-minimize/README.md b/dwl/dwl-patches/patches/xwayland-handle-minimize/README.md new file mode 100644 index 0000000..dde0935 --- /dev/null +++ b/dwl/dwl-patches/patches/xwayland-handle-minimize/README.md @@ -0,0 +1,9 @@ +### Description +Some windows (wine) games go black screen after losing focus and never recover https://github.com/swaywm/sway/issues/4324. This patch fixes this by handling minimize requests that some xwayland clients do. + + ## Download +- [git branch](https://codeberg.org/korei999/dwl/src/branch/xwayland-handle-minimize) +- [2024-04-01](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch) + +### Authors +- [korei999](https://codeberg.org/korei999) \ No newline at end of file diff --git a/dwl/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch b/dwl/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch new file mode 100644 index 0000000..d6d1f8f --- /dev/null +++ b/dwl/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch @@ -0,0 +1,91 @@ +From 7277f668f19f5a7fcfbbc96e80cb2829487848ca Mon Sep 17 00:00:00 2001 +From: korei999 +Date: Mon, 1 Apr 2024 15:13:11 +0300 +Subject: [PATCH] handle minimize request for xwayland clients + +--- + client.h | 9 ++++++--- + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 25 insertions(+), 3 deletions(-) + +diff --git a/client.h b/client.h +index 800b867..c46cfb2 100644 +--- a/client.h ++++ b/client.h +@@ -94,9 +94,12 @@ client_activate_surface(struct wlr_surface *s, int activated) + { + struct wlr_xdg_toplevel *toplevel; + #ifdef XWAYLAND +- struct wlr_xwayland_surface *xsurface; +- if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(s))) { +- wlr_xwayland_surface_activate(xsurface, activated); ++ struct wlr_xwayland_surface *surface; ++ if ((surface = wlr_xwayland_surface_try_from_wlr_surface(s))) { ++ if (activated && surface->minimized) ++ wlr_xwayland_surface_set_minimized(surface, false); ++ ++ wlr_xwayland_surface_activate(surface, activated); + return; + } + #endif +diff --git a/dwl.c b/dwl.c +index 39ce68c..b49f57b 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -131,6 +131,7 @@ typedef struct { + #ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; ++ struct wl_listener minimize; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +@@ -412,6 +413,7 @@ static void configurex11(struct wl_listener *listener, void *data); + static void createnotifyx11(struct wl_listener *listener, void *data); + static void dissociatex11(struct wl_listener *listener, void *data); + static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); ++static void minimizenotify(struct wl_listener *listener, void *data); + static void sethints(struct wl_listener *listener, void *data); + static void xwaylandready(struct wl_listener *listener, void *data); + static struct wlr_xwayland *xwayland; +@@ -1177,6 +1179,7 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); ++ wl_list_remove(&c->minimize.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +@@ -2984,6 +2987,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); ++ LISTEN(&xsurface->events.request_minimize, &c->minimize, minimizenotify); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); +@@ -3011,6 +3015,21 @@ getatom(xcb_connection_t *xc, const char *name) + return atom; + } + ++void ++minimizenotify(struct wl_listener *listener, void *data) ++{ ++ Client *c = wl_container_of(listener, c, minimize); ++ struct wlr_xwayland_surface *xsurface = c->surface.xwayland; ++ struct wlr_xwayland_minimize_event *e = data; ++ int focused; ++ ++ if (xsurface->surface == NULL || !xsurface->surface->mapped) ++ return; ++ ++ focused = seat->keyboard_state.focused_surface == xsurface->surface; ++ wlr_xwayland_surface_set_minimized(xsurface, !focused && e->minimize); ++} ++ + void + sethints(struct wl_listener *listener, void *data) + { +-- +2.44.0 + diff --git a/dwl/dwl-patches/patches/zoomswap/README.md b/dwl/dwl-patches/patches/zoomswap/README.md new file mode 100644 index 0000000..83cbeb1 --- /dev/null +++ b/dwl/dwl-patches/patches/zoomswap/README.md @@ -0,0 +1,54 @@ +### Description +This patch swaps the current window (C) with the previous master (P) when zooming. +``` +Original behaviour : ++-----------------+-------+ +| | | +| | | +| | | +| P +-------| +| | | +| | C | +| | | ++-----------------+-------+ + ++-----------------+-------+ +| | | +| | P | +| | | +| C +-------| +| | | +| | | +| | | ++-----------------+-------+ + +New Behaviour : ++-----------------+-------+ +| | | +| | | +| | | +| C +-------+ +| | | +| | P | +| | | ++-----------------+-------+ + ++-----------------+-------+ +| | | +| | | +| | | +| P +-------+ +| | | +| | C | +| | | ++-----------------+-------+ +``` + +### Download +- [git branch](https://codeberg.org/Palanix/dwl/src/branch/zoomswap) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap-v0.7.patch) +- [v0.6](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap-v0.6.patch) +- [2024-02-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/zoomswap/zoomswap.patch) + +### Authors +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch b/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch new file mode 100644 index 0000000..93de288 --- /dev/null +++ b/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch @@ -0,0 +1,55 @@ +From 2b4e1a8bbb2d17a3da5ca54f2995469dfec5bbbb Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 145fd01..e84202c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -207,6 +207,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Client *prevc; + }; + + typedef struct { +@@ -943,6 +944,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -2968,7 +2970,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -2990,9 +2992,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch b/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch new file mode 100644 index 0000000..d20ff32 --- /dev/null +++ b/dwl/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch @@ -0,0 +1,55 @@ +From cd4ce3f98b4231515c1363070a7fb5f9654a40bc Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5bf995e..4e2b44e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -208,6 +208,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Client *prevc; + }; + + typedef struct { +@@ -984,6 +985,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -3023,7 +3025,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -3045,9 +3047,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.45.2 + diff --git a/dwl/dwl-patches/patches/zoomswap/zoomswap.patch b/dwl/dwl-patches/patches/zoomswap/zoomswap.patch new file mode 100644 index 0000000..76e4466 --- /dev/null +++ b/dwl/dwl-patches/patches/zoomswap/zoomswap.patch @@ -0,0 +1,55 @@ +From c82be3e2069fdb9cbea0da22a4c9ed69f4ab5aea Mon Sep 17 00:00:00 2001 +From: Palanix +Date: Fri, 28 Jul 2023 02:14:32 +0200 +Subject: [PATCH] Implement zoomswap + +--- + dwl.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index fa76db2..1dea8fe 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -202,6 +202,7 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ Client *prevc; + }; + + typedef struct { +@@ -858,6 +859,7 @@ createmon(struct wl_listener *listener, void *data) + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; ++ m->prevc = NULL; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); +@@ -2793,7 +2795,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + void + zoom(const Arg *arg) + { +- Client *c, *sel = focustop(selmon); ++ Client *c, *sel = focustop(selmon), *tmp = sel; + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; +@@ -2815,9 +2817,12 @@ zoom(const Arg *arg) + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) +- sel = c; ++ sel = selmon->prevc ? selmon->prevc : c, c = tmp; ++ wl_list_remove(&c->link); ++ wl_list_insert(&sel->link, &c->link); + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); ++ selmon->prevc = c; + + focusclient(sel, 1); + arrange(selmon); +-- +2.43.1 + diff --git a/dwl/dwl-patches/stale-patches/bar-systray-old/README.md b/dwl/dwl-patches/stale-patches/bar-systray-old/README.md new file mode 100644 index 0000000..9c8b94e --- /dev/null +++ b/dwl/dwl-patches/stale-patches/bar-systray-old/README.md @@ -0,0 +1,33 @@ +### Description +Add a system tray next to the [bar](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar). Heed the warning, this is far from suckless ^^ + +![preview](systray.png) + +**Superseded by [bar-systray](/dwl/dwl-patches/src/branch/main/patches/bar-systray).** + +### Dependencies +- GTK4 +- [bar.patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar) as mentioned. +- [gtk4-layer-shell](https://github.com/wmww/gtk4-layer-shell) +- [statusnotifier-systray-gtk4](https://codeberg.org/janetski/statusnotifier-systray-gtk4) built as a static library. + +### Applying the patch +The patch applies on top of the bar patch. That needs to be applied first. + +The patch creates subdirectories `lib` and `include`. After patching, but before `make`, install +`libstatusnotifier-systray-gtk4.a` and `snsystray.h` from statusnotifier-systray-gtk4 in the +directories. One possible way to do that: + +1. Clone [https://codeberg.org/janetski/statusnotifier-systray-gtk4](https://codeberg.org/janetski/statusnotifier-systray-gtk4). Can clone to any location. +2. From statusnotifier-systray-gtk4 root: + 1. `$ meson setup --default-library=static --prefix=/ -Dgir=false -Dvala=false -Ddocs=false build` + 2. `$ meson compile -C build` + 3. `$ DESTDIR=$DWLDIR meson install -C build`, where $DWLDIR is the path to dwl root. +3. Finally, from dwl root, run `make`. + +### Download +- [git branch](/janetski/dwl/src/branch/0.7-systray) +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/bar-systray/bar-systray-0.7.patch) + +### Authors +- [janetski](https://codeberg.org/janetski) ([.vetu](https://discordapp.com/users/355488216469471242) on discord) diff --git a/dwl/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch b/dwl/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch new file mode 100644 index 0000000..eae4561 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch @@ -0,0 +1,398 @@ +From 71f7b97dca2d781668e826aae7e06544958534f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Janne=20Vetel=C3=A4inen?= +Date: Fri, 23 Aug 2024 18:39:17 +0300 +Subject: [PATCH 1/1] Add a system tray next to the sewn's bar + +--- + Makefile | 8 +- + config.def.h | 2 + + dwl.c | 222 ++++++++++++++++++++++++++++++++++++++++++++- + include/.gitignore | 1 + + lib/.gitignore | 1 + + 5 files changed, 225 insertions(+), 9 deletions(-) + create mode 100644 include/.gitignore + create mode 100644 lib/.gitignore + +diff --git a/Makefile b/Makefile +index 9bc67db..853b04c 100644 +--- a/Makefile ++++ b/Makefile +@@ -7,14 +7,14 @@ include config.mk + DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ + -DVERSION=\"$(VERSION)\" $(XWAYLAND) + DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ +- -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ ++ -Wno-unused-parameter -Wshadow -Wunused-macros \ + -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) +-DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) +-LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) gtk4 gtk4-layer-shell-0 ++DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) -Iinclude ++LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) -Llib -lstatusnotifier-systray-gtk4 + + all: dwl + dwl: dwl.o util.o +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..bb9366f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,6 +11,8 @@ static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; + static const float rootcolor[] = COLOR(0x000000ff); ++static const int trayspacing = 4; /* Spacing between icons in system tray */ ++static const int traymargins = 4; /* System tray inner margins */ + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ + static uint32_t colors[][3] = { +diff --git a/dwl.c b/dwl.c +index ece537a..24f550a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -72,9 +72,22 @@ + #include "util.h" + #include "drwl.h" + ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ + /* macros */ ++#ifndef MAX + #define MAX(A, B) ((A) > (B) ? (A) : (B)) ++#endif /* MAX */ ++#ifndef MIN + #define MIN(A, B) ((A) < (B) ? (A) : (B)) ++#endif /* MIN */ + #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) +@@ -324,6 +337,15 @@ 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 gtkactivate(GtkApplication *app, void *data); ++static void gtkclosewindows(void *data, void *udata); ++static void gtkhandletogglebarmsg(void *data); ++static void gtkhandlewidthnotify(SnSystray *systray, GParamSpec *pspec, void *data); ++static void* gtkinit(void *data); ++static void gtkspawnstray(Monitor *m, GtkApplication *app); ++static void gtkterminate(void *data); ++static void gtktoggletray(void *data, void *udata); ++static GdkMonitor* gtkwlrtogdkmon(Monitor *wlrmon); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -380,7 +402,7 @@ static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); + static void updatemons(struct wl_listener *listener, void *data); +-static void updatebar(Monitor *m); ++static void updatebar(Monitor *m, int traywidth); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -394,6 +416,8 @@ static void zoom(const Arg *arg); + /* variables */ + static const char broken[] = "broken"; + static pid_t child_pid = -1; ++static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ++static pthread_t gtkthread; /* Gtk functions are only allowed to be called from this thread */ + static int locked; + static void *exclusive_focus; + static struct wl_display *dpy; +@@ -1187,7 +1211,7 @@ createmon(struct wl_listener *listener, void *data) + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; +- updatebar(m); ++ updatebar(m, 0); + + wl_list_insert(&mons, &m->link); + drawbars(); +@@ -1518,6 +1542,8 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ pthread_mutex_lock(&mutex); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); +@@ -1566,6 +1592,7 @@ drawbar(Monitor *m) + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); ++ pthread_mutex_unlock(&mutex); + } + + void +@@ -1710,6 +1737,174 @@ fullscreennotify(struct wl_listener *listener, void *data) + setfullscreen(c, client_wants_fullscreen(c)); + } + ++void ++gtkactivate(GtkApplication *app, void *data) ++{ ++ GdkDisplay *display; ++ GtkCssProvider *cssp; ++ char csss[64]; ++ Monitor *m; ++ uint32_t bgcolor; ++ ++ bgcolor = colors[SchemeNorm][1] >> 8; ++ display = gdk_display_get_default(); ++ cssp = gtk_css_provider_new(); ++ sprintf(csss, "window{background-color:#%06x;}", bgcolor); ++ gtk_css_provider_load_from_string(cssp, csss); ++ gtk_style_context_add_provider_for_display(display, ++ GTK_STYLE_PROVIDER(cssp), ++ GTK_STYLE_PROVIDER_PRIORITY_USER); ++ ++ wl_list_for_each(m, &mons, link) ++ gtkspawnstray(m, app); ++ ++ g_object_unref(cssp); ++} ++ ++void ++gtkclosewindows(void *data, void *udata) ++{ ++ GtkWindow *window = GTK_WINDOW(data); ++ ++ gtk_window_close(window); ++} ++ ++void ++gtkhandletogglebarmsg(void *data) ++{ ++ GtkApplication *app; ++ GList *windows; ++ ++ app = GTK_APPLICATION(g_application_get_default()); ++ windows = gtk_application_get_windows(app); ++ g_list_foreach(windows, gtktoggletray, data); ++} ++ ++void ++gtkhandlewidthnotify(SnSystray *systray, GParamSpec *pspec, void *data) ++{ ++ Monitor *m = (Monitor *)data; ++ int traywidth; ++ ++ traywidth = sn_systray_get_width(systray); ++ ++ updatebar(m, traywidth); ++ drawbar(m); ++} ++ ++void* ++gtkinit(void *data) ++{ ++ GtkApplication *app = gtk_application_new("org.dwl.systray", ++ G_APPLICATION_NON_UNIQUE); ++ g_signal_connect(app, "activate", G_CALLBACK(gtkactivate), NULL); ++ g_application_run(G_APPLICATION(app), 0, NULL); ++ ++ g_object_unref(app); ++ ++ return NULL; ++} ++ ++void ++gtkspawnstray(Monitor *m, GtkApplication *app) ++{ ++ GdkMonitor *gdkmon; ++ GtkWindow *window; ++ SnSystray *systray; ++ const char *conn; ++ gboolean anchors[4]; ++ int iconsize, tray_init_width, tray_height; ++ ++ gdkmon = gtkwlrtogdkmon(m); ++ if (gdkmon == NULL) ++ die("Failed to get gdkmon"); ++ ++ conn = gdk_monitor_get_connector(gdkmon); ++ iconsize = m->b.real_height - 2 * traymargins; ++ tray_height = m->b.real_height; ++ tray_init_width = m->b.real_height; ++ ++ if (topbar) { ++ anchors[GTK_LAYER_SHELL_EDGE_LEFT] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_TOP] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = false; ++ } else { ++ anchors[GTK_LAYER_SHELL_EDGE_LEFT] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = true; ++ anchors[GTK_LAYER_SHELL_EDGE_TOP] = false; ++ anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = true; ++ } ++ ++ systray = sn_systray_new(iconsize, ++ traymargins, ++ trayspacing, ++ conn); ++ window = GTK_WINDOW(gtk_window_new()); ++ ++ gtk_window_set_default_size(window, tray_init_width, tray_height); ++ gtk_window_set_child(window, GTK_WIDGET(systray)); ++ gtk_window_set_application(window, app); ++ gtk_layer_init_for_window(window); ++ gtk_layer_set_layer(window, GTK_LAYER_SHELL_LAYER_BOTTOM); ++ gtk_layer_set_exclusive_zone(window, -1); ++ gtk_layer_set_monitor(window, gdkmon); ++ ++ for (int j = 0; j < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; j++) { ++ gtk_layer_set_anchor(window, j, anchors[j]); ++ } ++ ++ updatebar(m, tray_init_width); ++ g_signal_connect(systray, "notify::curwidth", G_CALLBACK(gtkhandlewidthnotify), m); ++ gtk_window_present(window); ++} ++ ++void ++gtkterminate(void *data) ++{ ++ GtkApplication *app; ++ GList *windows; ++ ++ app = GTK_APPLICATION(g_application_get_default()); ++ windows = gtk_application_get_windows(app); ++ g_list_foreach(windows, gtkclosewindows, NULL); ++} ++ ++GdkMonitor* ++gtkwlrtogdkmon(Monitor *wlrmon) ++{ ++ GdkMonitor *gdkmon = NULL; ++ ++ GListModel *gdkmons; ++ GdkDisplay *display; ++ const char *gdkname; ++ const char *wlrname; ++ unsigned int i; ++ ++ wlrname = wlrmon->wlr_output->name; ++ display = gdk_display_get_default(); ++ gdkmons = gdk_display_get_monitors(display); ++ ++ for (i = 0; i < g_list_model_get_n_items(gdkmons); i++) { ++ GdkMonitor *mon = g_list_model_get_item(gdkmons, i); ++ gdkname = gdk_monitor_get_connector(mon); ++ if (strcmp(wlrname, gdkname) == 0) ++ gdkmon = mon; ++ } ++ ++ return gdkmon; ++} ++ ++void ++gtktoggletray(void *data, void *udata) ++{ ++ GtkWidget *widget = GTK_WIDGET(data); ++ int *pbarvisible = (int *)udata; ++ int barvisible = GPOINTER_TO_INT(pbarvisible); ++ ++ gtk_widget_set_visible(widget, barvisible); ++} ++ + void + gpureset(struct wl_listener *listener, void *data) + { +@@ -2293,6 +2488,8 @@ powermgrsetmode(struct wl_listener *listener, void *data) + void + quit(const Arg *arg) + { ++ g_idle_add_once(gtkterminate, NULL); ++ pthread_join(gtkthread, NULL); + wl_display_terminate(dpy); + } + +@@ -2836,6 +3033,9 @@ setup(void) + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } + #endif ++ ++ // Gtk functions are only allowed to be called from this thread. ++ pthread_create(>kthread, NULL, >kinit, NULL); + } + + void +@@ -2943,9 +3143,21 @@ tile(Monitor *m) + void + togglebar(const Arg *arg) + { ++ int barvisible; ++ int *pbarvisible; ++ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); ++ ++ // Notify gtkthread ++ if (selmon->scene_buffer->node.enabled) ++ barvisible = 1; ++ else ++ barvisible = 0; ++ ++ pbarvisible = GINT_TO_POINTER(barvisible); ++ g_idle_add_once(gtkhandletogglebarmsg, pbarvisible); + } + + void +@@ -3140,7 +3352,7 @@ updatemons(struct wl_listener *listener, void *data) + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { +- updatebar(m); ++ updatebar(m, 0); + drawbar(m); + } + +@@ -3155,7 +3367,7 @@ updatemons(struct wl_listener *listener, void *data) + } + + void +-updatebar(Monitor *m) ++updatebar(Monitor *m, int traywidth) + { + size_t i; + int rw, rh; +@@ -3163,7 +3375,7 @@ updatebar(Monitor *m) + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; +- m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale) - traywidth; + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + +diff --git a/include/.gitignore b/include/.gitignore +new file mode 100644 +index 0000000..424c745 +--- /dev/null ++++ b/include/.gitignore +@@ -0,0 +1 @@ ++*.h +diff --git a/lib/.gitignore b/lib/.gitignore +new file mode 100644 +index 0000000..10301e2 +--- /dev/null ++++ b/lib/.gitignore +@@ -0,0 +1 @@ ++*.a +-- +2.46.0 + diff --git a/dwl/dwl-patches/stale-patches/bar-systray-old/systray.png b/dwl/dwl-patches/stale-patches/bar-systray-old/systray.png new file mode 100644 index 0000000..54f9f98 Binary files /dev/null and b/dwl/dwl-patches/stale-patches/bar-systray-old/systray.png differ diff --git a/dwl/dwl-patches/stale-patches/master-right/README.md b/dwl/dwl-patches/stale-patches/master-right/README.md new file mode 100644 index 0000000..0f69bbc --- /dev/null +++ b/dwl/dwl-patches/stale-patches/master-right/README.md @@ -0,0 +1,13 @@ +### Description +Show the master area to the right. + +### Reason for deprecation +I created this patch for a user on Discord and I have never used it. + +### Download +- [git branch](https://codeberg.org/sevz/dwl/src/branch/master-right) +- [main 2024-09-01](/dwl/dwl-patches/raw/branch/main/patches/master-right/master-right.patch) +- [master-right-0.7.patch](/dwl/dwl-patches/raw/branch/main/patches/master-right/master-right-0.7.patch) + +### Authors +- [sevz](https://codeberg.org/sevz) diff --git a/dwl/dwl-patches/stale-patches/master-right/master-right-0.7.patch b/dwl/dwl-patches/stale-patches/master-right/master-right-0.7.patch new file mode 100644 index 0000000..e4845f4 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/master-right/master-right-0.7.patch @@ -0,0 +1,35 @@ +From f72236247e5e7cb23c3cac86b496cdd2c523f7ff Mon Sep 17 00:00:00 2001 +From: Sevz17 +Date: Fri, 25 Jun 2021 19:50:56 -0500 +Subject: [PATCH] show master area to the right +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index a2711f67..50b057a7 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2710,11 +2710,12 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, ++ resize(c, (struct wlr_box){.x = m->w.x + m->w.width - mw, ++ .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ty, + .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); + ty += c->geom.height; + } +-- +2.46.0 + diff --git a/dwl/dwl-patches/stale-patches/master-right/master-right.patch b/dwl/dwl-patches/stale-patches/master-right/master-right.patch new file mode 100644 index 0000000..70c80a1 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/master-right/master-right.patch @@ -0,0 +1,35 @@ +From 0afd0a98998dda20e4fe4f4d2b5fcdec49c448c3 Mon Sep 17 00:00:00 2001 +From: Sevz17 +Date: Fri, 25 Jun 2021 19:50:56 -0500 +Subject: [PATCH] show master area to the right +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + dwl.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 9021e442..2bd354a3 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2670,11 +2670,12 @@ tile(Monitor *m) + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, ++ resize(c, (struct wlr_box){.x = m->w.x + m->w.width - mw, ++ .y = m->w.y + my, .width = mw, + .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); + my += c->geom.height; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, ++ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ty, + .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); + ty += c->geom.height; + } +-- +2.46.0 + diff --git a/dwl/dwl-patches/stale-patches/press_repeat_release/README.md b/dwl/dwl-patches/stale-patches/press_repeat_release/README.md new file mode 100644 index 0000000..b9b4ff3 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/press_repeat_release/README.md @@ -0,0 +1,15 @@ +### Description +This patch adds 3 additional options to the `Key` struct, `on_press`, `on_repeat` and `on_release` which can be used to control which events a key binding should be triggered on. + +NOTE: Due to concerns about patching difficulties this patch does NOT include any changes to `config.def.h`. After applying you will need to add the 3 additional initializers to each key binding that you would like to modify. Any key binding that is not updated will cause a build warning but should function as it does in vanilla. + +2025-01-04 Moved to stale patches. +Outstanding issues with this patch: https://codeberg.org/dwl/dwl-patches/issues/98 +Patch maintainer notes he is no longer maintaining dwl patches: https://codeberg.org/dwl/dwl-patches/pulls/102 + +### Download +- [git branch](https://codeberg.org/USERNAME/dwl/src/branch/press_repeat_release) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/press_repeat_release/press_repeat_release.patch) + +### Authors +- [minego](https://codeberg.org/minego) diff --git a/dwl/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch b/dwl/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch new file mode 100644 index 0000000..b86666d --- /dev/null +++ b/dwl/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch @@ -0,0 +1,108 @@ +From aee1dc3e9ca4d8deec5432d0c64921af6e301ecd Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 15:59:50 -0600 +Subject: [PATCH 1/2] onpress, onrepeat, onrelease + +--- + dwl.c | 18 ++++++++++++------ + 1 file changed, 12 insertions(+), 6 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 5867b0c..43bbf0c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -146,6 +146,10 @@ typedef struct { + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; ++ ++ int on_press; ++ int on_repeat; ++ int on_release; + } Key; + + typedef struct { +@@ -286,7 +290,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +-static int keybinding(uint32_t mods, xkb_keysym_t sym); ++static int keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_release); + static void keypress(struct wl_listener *listener, void *data); + static void keypressmod(struct wl_listener *listener, void *data); + static int keyrepeat(void *data); +@@ -1428,7 +1432,7 @@ inputdevice(struct wl_listener *listener, void *data) + } + + int +-keybinding(uint32_t mods, xkb_keysym_t sym) ++keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_release) + { + /* + * Here we handle compositor keybindings. This is when the compositor is +@@ -1439,8 +1443,10 @@ keybinding(uint32_t mods, xkb_keysym_t sym) + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- k->func(&k->arg); +- return 1; ++ if ((on_press && k->on_press) || (on_repeat && k->on_repeat) || (on_release && k->on_release)) { ++ k->func(&k->arg); ++ return 1; ++ } + } + } + return 0; +@@ -1470,7 +1476,7 @@ keypress(struct wl_listener *listener, void *data) + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) +- handled = keybinding(mods, syms[i]) || handled; ++ handled = keybinding(mods, syms[i], event->state == WL_KEYBOARD_KEY_STATE_PRESSED, 0, event->state == WL_KEYBOARD_KEY_STATE_RELEASED) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { +@@ -1518,7 +1524,7 @@ keyrepeat(void *data) + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) +- keybinding(group->mods, group->keysyms[i]); ++ keybinding(group->mods, group->keysyms[i], 0, 1, 0); + + return 0; + } +-- +2.44.0 + + +From 1875bb171c9b0cd2fb03bb7e6c3fb400e33eeaf1 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 16:26:52 -0600 +Subject: [PATCH 2/2] Modified logic so that an unmodified keybinding with + default values for the new flags will behave as it does in vanilla, while + keybindings with customized flags will function as expected + +--- + dwl.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 43bbf0c..55e7a40 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1443,7 +1443,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym, int on_press, int on_repeat, int on_ + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { +- if ((on_press && k->on_press) || (on_repeat && k->on_repeat) || (on_release && k->on_release)) { ++ if ((k->on_press == 0 && k->on_repeat == 0 && k->on_release == 0) || ++ (on_press && k->on_press) || ++ (on_repeat && k->on_repeat) || ++ (on_release && k->on_release) ++ ) { + k->func(&k->arg); + return 1; + } +-- +2.44.0 + diff --git a/dwl/dwl-patches/stale-patches/remembertags/README.md b/dwl/dwl-patches/stale-patches/remembertags/README.md new file mode 100644 index 0000000..b45e87c --- /dev/null +++ b/dwl/dwl-patches/stale-patches/remembertags/README.md @@ -0,0 +1,16 @@ +### Description +This patch modifies the behavior when selecting tags so that selecting a tag will also enable any other tags that were previously visible. + +For example: +1. Select tag 5, with mod+5 +2. Toggle tag 8, with ctrl+mod+8 +3. Select tag 1, with mod+1. Tags 5 and 8 should no longer be visible. +4. Select tag 5 again, with mod+5. Tag 8 should be visible since it was remembered. +5. Select tag 5 again, with mod_5. Selecting the already selected tag resets any remembered tags, so now tag 5 should be the only one visible. + +### Download +- [git branch](https://codeberg.org/minego/dwl/src/branch/remembertags) +- [2024-03-27](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/remembertags/remembertags.patch) + +### Authors +- [minego](https://codeberg.org/minego) \ No newline at end of file diff --git a/dwl/dwl-patches/stale-patches/remembertags/remembertags.patch b/dwl/dwl-patches/stale-patches/remembertags/remembertags.patch new file mode 100644 index 0000000..fd6135e --- /dev/null +++ b/dwl/dwl-patches/stale-patches/remembertags/remembertags.patch @@ -0,0 +1,105 @@ +From fea6eb3cfc84ede8403c89a3230f5c658a6c7bd1 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell +Date: Wed, 27 Mar 2024 13:05:09 -0600 +Subject: [PATCH] remembertags + +--- + config.def.h | 8 ++++---- + dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 52 insertions(+), 4 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 9009517..2312802 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -105,10 +105,10 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + #define MODKEY WLR_MODIFIER_ALT + + #define TAGKEYS(KEY,SKEY,TAG) \ +- { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ +- { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } ++ { MODKEY, KEY, remembertagsview, {.i = TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL, KEY, toggleview, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ ++ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT, SKEY, toggletag, {.ui = 1 << TAG} } + + /* helper for spawning shell commands in the pre dwm-5.0 fashion */ + #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } +diff --git a/dwl.c b/dwl.c +index 5867b0c..31a81aa 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -205,6 +205,11 @@ struct Monitor { + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; ++ unsigned int createtag[2]; /* Create windows on the last tag directly selected, not all selected */ ++ struct { ++ unsigned int tagset; ++ Client *zoomed; ++ } remembered[31]; + }; + + typedef struct { +@@ -308,6 +313,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); + static void printstatus(void); + static void quit(const Arg *arg); ++static void remembertagsview(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); +@@ -1951,6 +1957,48 @@ quit(const Arg *arg) + wl_display_terminate(dpy); + } + ++void ++remembertagsview(const Arg *arg) { ++ unsigned newtags = (1 << arg->i) & TAGMASK; ++ int oldtag; ++ int active; ++ unsigned int newcreate; ++ ++ if (selmon == NULL) { ++ return; ++ } ++ ++ oldtag = selmon->createtag[selmon->seltags]; ++ active = (oldtag == arg->i); ++ ++ if (oldtag < TAGCOUNT) { ++ selmon->remembered[oldtag].tagset = selmon->tagset[selmon->seltags]; ++ } ++ ++ selmon->seltags ^= 1; /*toggle tagset*/ ++ ++ if (-1 == arg->i) { ++ /* A specific tag was not specified */ ++ active = 0; ++ newcreate = selmon->createtag[selmon->seltags]; ++ } else { ++ newcreate = arg->i; ++ } ++ ++ if (active) { ++ /* Select twice to isolate the tag */ ++ selmon->tagset[selmon->seltags] = newtags; ++ } else if (arg->i < TAGCOUNT) { ++ /* Restore whatever was previously on this tag */ ++ selmon->tagset[selmon->seltags] = newtags | selmon->remembered[newcreate].tagset; ++ } ++ ++ selmon->createtag[selmon->seltags] = newcreate; ++ focusclient(focustop(selmon), 1); ++ arrange(selmon); ++ printstatus(); ++} ++ + void + rendermon(struct wl_listener *listener, void *data) + { +-- +2.44.0 + diff --git a/dwl/dwl-patches/stale-patches/tab-pango/README.md b/dwl/dwl-patches/stale-patches/tab-pango/README.md new file mode 100644 index 0000000..c7e4a1c --- /dev/null +++ b/dwl/dwl-patches/stale-patches/tab-pango/README.md @@ -0,0 +1,10 @@ +### Description +Add a tab bar or window title to the top or bottom of windows. + +**This is the old version of the `tab` patch. Deprecated because the [new version](https://codeberg.org/dwl/dwl-patches/raw/branch-main/patches/tab) is significantly more efficient and well-written than this, and it better adheres to the suckless philosophy.** + +### Download +- [2024-03-15](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tab/tab.patch) + +### Authors +- [dev-gm](https://codeberg.org/dev-gm) diff --git a/dwl/dwl-patches/stale-patches/tab-pango/tab.patch b/dwl/dwl-patches/stale-patches/tab-pango/tab.patch new file mode 100644 index 0000000..5b7b8a0 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/tab-pango/tab.patch @@ -0,0 +1,461 @@ +From b624206781513cdff1b9609fc5ac4b848094e1b4 Mon Sep 17 00:00:00 2001 +From: Gavin M +Date: Fri, 15 Mar 2024 16:37:23 -0500 +Subject: [PATCH] Tabbed patch + +--- + Makefile | 2 +- + config.def.h | 18 +++- + dwl.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 281 insertions(+), 15 deletions(-) + +diff --git a/Makefile b/Makefile +index a67fdd3..182eb87 100644 +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement -Wno-unu + -Werror=strict-prototypes -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wlroots wayland-server xkbcommon libinput cairo pangocairo $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 9009517..1ca270f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,16 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const double title_border_width = 0.75; ++static const unsigned int title_padding = 11; ++static const int title_top = 0; ++static const LayoutType floating_title_type = LAYOUT_TYPE_LABEL; ++static const char title_font[] = "Dejavu Sans Mono 10.5"; ++static const float title_font_color[] = COLOR(0xffffffff); ++static const float title_focus_bg[] = COLOR(0x3b3b3bff); ++static const float title_root_bg[] = COLOR(0x131313ff); ++static const float title_urgent_bg[] = COLOR(0x00ff00ff); ++static const float title_border_color[] = COLOR(0x3b3b3bff); + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -30,10 +40,10 @@ static const Rule rules[] = { + + /* layout(s) */ + static const Layout layouts[] = { +- /* symbol arrange function */ +- { "[]=", tile }, +- { "><>", NULL }, /* no layout function means floating behavior */ +- { "[M]", monocle }, ++ /* symbol type render_only_top arrange function */ ++ { "[]=", LAYOUT_TYPE_NONE, 0, tile }, ++ { "><>", LAYOUT_TYPE_LABEL, 0, NULL }, /* no layout function means floating behavior */ ++ { "[M]", LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS, 1, monocle }, + }; + + /* monitors */ +diff --git a/dwl.c b/dwl.c +index 5867b0c..e613d17 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2,6 +2,11 @@ + * See LICENSE file for copyright and license details. + */ + #include ++#include ++#include ++#include ++#include ++#include + #include + #include + #include +@@ -13,8 +18,10 @@ + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -110,6 +117,7 @@ typedef struct { + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; ++ struct wlr_scene_buffer *titlebar; + struct wl_list link; + struct wl_list flink; + union { +@@ -137,7 +145,7 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, titleisinit, istabbed; + uint32_t resize; /* configure serial of a pending resize */ + } Client; + +@@ -179,8 +187,17 @@ typedef struct { + struct wl_listener surface_commit; + } LayerSurface; + ++typedef enum { ++ LAYOUT_TYPE_NONE, ++ LAYOUT_TYPE_LABEL, ++ LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS, ++ LAYOUT_TYPE_TABS_ALWAYS ++} LayoutType; ++ + typedef struct { + const char *symbol; ++ LayoutType type; ++ int render_top_only; + void (*arrange)(Monitor *); + } Layout; + +@@ -282,6 +299,7 @@ static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); ++static Client *focustop_onlytiled(Monitor *m, int onlytiled); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); +@@ -309,6 +327,7 @@ static void pointerfocus(Client *c, struct wlr_surface *surface, + static void printstatus(void); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); ++static void rendertitlebar(Client *client); + static void requestdecorationmode(struct wl_listener *listener, void *data); + static void requeststartdrag(struct wl_listener *listener, void *data); + static void requestmonstate(struct wl_listener *listener, void *data); +@@ -349,6 +368,7 @@ static void xytonode(double x, double y, struct wlr_surface **psurface, + static void zoom(const Arg *arg); + + /* variables */ ++static int title_height; + static const char broken[] = "broken"; + static pid_t child_pid = -1; + static int locked; +@@ -973,6 +993,7 @@ createnotify(struct wl_listener *listener, void *data) + c = xdg_surface->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = xdg_surface; + c->bw = borderpx; ++ c->titleisinit = c->istabbed = 0; + + wlr_xdg_toplevel_set_wm_capabilities(xdg_surface->toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +@@ -1360,6 +1381,22 @@ focustop(Monitor *m) + return NULL; + } + ++Client * ++focustop_onlytiled(Monitor *m, int onlytiled) ++{ ++ Client *c; ++ if (!m) ++ return NULL; ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m)) { ++ if ((onlytiled == 1 && c->isfloating) || (onlytiled == 2 && (!c->isfloating || !m->lt[m->sellt]->arrange))) ++ continue; ++ return c; ++ } ++ } ++ return NULL; ++} ++ + void + fullscreennotify(struct wl_listener *listener, void *data) + { +@@ -2003,6 +2040,195 @@ skip: + wlr_output_state_finish(&pending); + } + ++struct text_buffer { ++ struct wlr_buffer base; ++ void *data; ++ uint32_t format; ++ size_t stride; ++}; ++ ++static void text_buffer_destroy(struct wlr_buffer *wlr_buffer) { ++ struct text_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); ++ free(buffer->data); ++ free(buffer); ++} ++ ++static bool text_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, ++ uint32_t flags, void **data, uint32_t *format, size_t *stride) { ++ struct text_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); ++ if(data != NULL) { ++ *data = (void *)buffer->data; ++ } ++ if(format != NULL) { ++ *format = buffer->format; ++ } ++ if(stride != NULL) { ++ *stride = buffer->stride; ++ } ++ return true; ++} ++ ++static void text_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { ++ // This space is intentionally left blank ++} ++ ++static const struct wlr_buffer_impl text_buffer_impl = { ++ .destroy = text_buffer_destroy, ++ .begin_data_ptr_access = text_buffer_begin_data_ptr_access, ++ .end_data_ptr_access = text_buffer_end_data_ptr_access, ++}; ++ ++static struct text_buffer *text_buffer_create(uint32_t width, uint32_t height, uint32_t stride) { ++ struct text_buffer *buffer = calloc(1, sizeof(*buffer)); ++ if (buffer == NULL) { ++ return NULL; ++ } ++ ++ wlr_buffer_init(&buffer->base, &text_buffer_impl, width, height); ++ buffer->format = DRM_FORMAT_ARGB8888; ++ buffer->stride = stride; ++ ++ buffer->data = malloc(buffer->stride * height); ++ if (buffer->data == NULL) { ++ free(buffer); ++ return NULL; ++ } ++ ++ return buffer; ++} ++ ++void ++rendertitlebar(Client *c) ++{ ++ struct wl_list *init_destroy, *cursor_destroy; ++ cairo_surface_t *surface; ++ cairo_status_t status; ++ cairo_t *cr; ++ PangoFontDescription *desc; ++ PangoLayout *layout; ++ LayoutType title_type; ++ Client *l, *sel; ++ unsigned int len, tabsize, i; ++ const char *title; ++ const float *color; ++ unsigned char *data; ++ int stride; ++ struct text_buffer *text_buffer; ++ void *data_ptr; ++ ++ if (!c || !c->scene || !c->mon || !selmon || (!VISIBLEON(c, selmon) && c->mon == selmon)) ++ return; ++ ++ if (c->titleisinit) { ++ init_destroy = cursor_destroy = &c->titlebar->node.events.destroy.listener_list; ++ do { ++ cursor_destroy = cursor_destroy->next; ++ } while (cursor_destroy && cursor_destroy != init_destroy); ++ if (!cursor_destroy) { ++ return; ++ } ++ wlr_scene_node_destroy(&c->titlebar->node); ++ } ++ c->titleisinit = c->istabbed = 0; ++ ++ sel = focustop_onlytiled(c->mon, c->isfloating + 1); ++ ++ if (c->isfullscreen) ++ return; ++ title_type = c->isfloating ? floating_title_type : c->mon->lt[c->mon->sellt]->type; ++ ++ if (title_type == LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS || title_type == LAYOUT_TYPE_TABS_ALWAYS) { ++ len = 0; ++ wl_list_for_each(l, &clients, link) { ++ if (VISIBLEON(l, c->mon) && l->isfloating == c->isfloating) ++ len++; ++ } ++ if (title_type == LAYOUT_TYPE_TABS_ONLY_MULTIPLE_CLIENTS && len <= 1) ++ return; ++ } ++ ++ if (c->mon->lt[c->mon->sellt]->render_top_only == 1 && !c->isfloating && c != sel) { ++ c->istabbed = 1; ++ return; ++ } /*else if (c->mon->lt[c->mon->sellt]->render_top_only == 2 && c != sel) { ++ c->istabbed = 1; ++ return; ++ }*/ ++ ++ if (title_type == LAYOUT_TYPE_NONE) ++ return; ++ ++ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, c->geom.width, title_height); ++ if ((status = cairo_surface_status(surface)) != CAIRO_STATUS_SUCCESS) { ++ wlr_log(WLR_ERROR, "cairo_image_surface_create failed: %s", ++ cairo_status_to_string(status)); ++ return; ++ } ++ cr = cairo_create(surface); ++ desc = pango_font_description_from_string(title_font); ++ layout = pango_cairo_create_layout(cr); ++ pango_layout_set_font_description(layout, desc); ++ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); ++ ++ cairo_set_line_width(cr, title_border_width); ++ ++ if (title_type == LAYOUT_TYPE_LABEL) { ++ cairo_rectangle(cr, 0, 0, c->geom.width, title_height); ++ cairo_set_source_rgba(cr, title_focus_bg[0], title_focus_bg[1], title_focus_bg[2], title_focus_bg[3]); ++ cairo_fill_preserve(cr); ++ cairo_set_source_rgba(cr, title_border_color[0], title_border_color[1], title_border_color[2], title_border_color[3]); ++ cairo_stroke(cr); ++ cairo_set_source_rgba(cr, title_font_color[0], title_font_color[1], title_font_color[2], title_font_color[3]); ++ title = client_get_title(c); ++ pango_layout_set_text(layout, title ? title : " ", (c->geom.width - title_padding) * PANGO_SCALE); ++ cairo_move_to(cr, title_padding, 0); ++ pango_cairo_show_layout(cr, layout); ++ } else { ++ tabsize = c->geom.width / len; ++ i = 0; ++ wl_list_for_each(l, &clients, link) { ++ if (VISIBLEON(l, c->mon) && l->isfloating == c->isfloating) { ++ cairo_rectangle(cr, i * tabsize, 0, (i + 1) * tabsize, title_height); ++ color = (l == sel) ? title_focus_bg ++ : (c->isurgent ? title_urgent_bg : title_root_bg); ++ cairo_set_source_rgba(cr, color[0], color[1], color[2], color[3]); ++ cairo_fill_preserve(cr); ++ cairo_set_source_rgba(cr, title_border_color[0], title_border_color[1], title_border_color[2], title_border_color[3]); ++ cairo_stroke(cr); ++ cairo_set_source_rgba(cr, title_font_color[0], title_font_color[1], title_font_color[2], title_font_color[3]); ++ title = client_get_title(l); ++ pango_layout_set_text(layout, title ? title : " ", (tabsize - title_padding) * PANGO_SCALE); ++ cairo_move_to(cr, (i * tabsize) + title_padding, 0); ++ pango_cairo_show_layout(cr, layout); ++ i++; ++ } ++ } ++ } ++ ++ data = cairo_image_surface_get_data(surface); ++ stride = cairo_image_surface_get_stride(surface); ++ text_buffer = text_buffer_create(c->geom.width, title_height, stride); ++ ++ if(!wlr_buffer_begin_data_ptr_access(&text_buffer->base, ++ WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data_ptr, NULL, NULL)) { ++ wlr_log(WLR_ERROR, "%s", "Failed to get pointer access to text buffer"); ++ return; ++ } ++ memcpy(data_ptr, data, stride * title_height); ++ wlr_buffer_end_data_ptr_access(&text_buffer->base); ++ cairo_surface_destroy(surface); ++ ++ c->titlebar = wlr_scene_buffer_create(c->scene, &text_buffer->base); ++ c->titleisinit = c->istabbed = 1; ++ ++ wlr_scene_node_set_position(&c->titlebar->node, 0, !title_top ? c->geom.height - title_height : 0); ++ wlr_scene_node_raise_to_top(&c->titlebar->node); ++ ++ pango_font_description_free(desc); ++ g_object_unref(layout); ++ cairo_destroy(cr); ++} ++ + void + requestdecorationmode(struct wl_listener *listener, void *data) + { +@@ -2036,24 +2262,30 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox = interact ? &sgeom : &c->mon->w; + struct wlr_box clip; ++ unsigned int th; ++ int draw_borders = 1; + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; ++ c->bw = draw_borders ? borderpx : 0; + applybounds(c, bbox); + ++ rendertitlebar(c); ++ th = c->istabbed ? title_height : c->bw; ++ + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); +- wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); +- wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); +- wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); +- wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); +- wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); +- wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); ++ wlr_scene_node_set_position(&c->scene_surface->node, c->bw, title_top ? th : c->bw); ++ wlr_scene_rect_set_size(c->border[0], c->geom.width, (title_top && c->istabbed) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[1], c->geom.width, (!title_top && c->istabbed) ? 0 : c->bw); ++ wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - (c->bw + th)); ++ wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - (c->bw + th)); ++ wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - (title_top ? c->bw : th)); ++ wlr_scene_node_set_position(&c->border[2]->node, 0, title_top ? th : c->bw); ++ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, title_top ? th : c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, +- c->geom.height - 2 * c->bw); ++ c->geom.height - (c->bw + th)); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); + } +@@ -2274,6 +2506,11 @@ setup(void) + + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; ++ cairo_surface_t *surface; ++ cairo_t *cr; ++ PangoFontDescription *desc; ++ PangoLayout *layout; ++ + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) +@@ -2506,6 +2743,24 @@ setup(void) + + wlr_scene_set_presentation(scene, wlr_presentation_create(dpy, backend)); + ++ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); ++ ++ cr = cairo_create(surface); ++ ++ desc = pango_font_description_from_string(title_font); ++ /* Create Pango layout. */ ++ layout = pango_cairo_create_layout(cr); ++ pango_layout_set_font_description(layout, desc); ++ pango_layout_set_text(layout, " ", -1); ++ /* Set width and height to text size */ ++ pango_layout_get_pixel_size(layout, NULL, &title_height); ++ ++ /* Cleanup */ ++ pango_font_description_free (desc); ++ cairo_surface_destroy(surface); ++ g_object_unref (layout); ++ cairo_destroy(cr); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -2978,6 +3233,7 @@ createnotifyx11(struct wl_listener *listener, void *data) + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = borderpx; ++ c->titleisinit = c->istabbed = 0; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); +-- +2.44.0 + diff --git a/dwl/dwl-patches/stale-patches/togglekblayout/README.md b/dwl/dwl-patches/stale-patches/togglekblayout/README.md new file mode 100644 index 0000000..366a6d8 --- /dev/null +++ b/dwl/dwl-patches/stale-patches/togglekblayout/README.md @@ -0,0 +1,14 @@ +### Description + +> This patch is no longer being maintained by me [wochap](https://codeberg.org/wochap), since I'm now using a different patch specific to my use case: https://codeberg.org/wochap/dwl/src/branch/v0.6-b/xkb-rules-switcher/xkb-rules-switcher.patch + +Switch between multiple keyboard layouts at runtime. + +### Download +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/togglekblayout) +- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayout/togglekblayout.patch) + +### Authors +- [wochap](https://codeberg.org/wochap) +- [Stivvo](https://github.com/Stivvo) + diff --git a/dwl/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch b/dwl/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch new file mode 100644 index 0000000..4ef802a --- /dev/null +++ b/dwl/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch @@ -0,0 +1,107 @@ +From 1bb99c78da484ce6036dc997962ed2f4c0d11208 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 19 Oct 2023 23:21:49 -0500 +Subject: [PATCH 1/2] apply main...Stivvo:toggleKbLayout.patch + +--- + config.def.h | 6 ++++++ + dwl.c | 20 ++++++++++++++++++++ + 2 files changed, 26 insertions(+) + +diff --git a/config.def.h b/config.def.h +index db0babc..caa09ea 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -57,6 +57,11 @@ static const struct xkb_rule_names xkb_rules = { + static const int repeat_rate = 25; + static const int repeat_delay = 600; + ++/* gb will be set the first time togglekblayout is called, then us.. it is ++ * recommended to set the same layout in position 0 of kblayouts and in ++ * xkb_rules */ ++static const char *kblayouts[] = {"us", "gb"}; ++ + /* Trackpad */ + static const int tap_to_click = 1; + static const int tap_and_drag = 1; +@@ -141,6 +146,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY, XKB_KEY_w, togglekblayout, {0} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index ef27a1d..25458e6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -312,6 +312,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 togglekblayout(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); +@@ -368,6 +369,7 @@ static struct wl_listener lock_listener = {.notify = locksession}; + + static struct wlr_seat *seat; + static struct wl_list keyboards; ++static unsigned int kblayout = 0; /* index of kblayouts */ + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ +@@ -2454,6 +2456,24 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglekblayout(const Arg *arg) ++{ ++ Keyboard *kb; ++ struct xkb_rule_names newrule = xkb_rules; ++ ++ kblayout = (kblayout + 1) % LENGTH(kblayouts); ++ newrule.layout = kblayouts[kblayout]; ++ wl_list_for_each(kb, &keyboards, link) { ++ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ struct xkb_keymap *keymap = xkb_map_new_from_names(context, &newrule, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ wlr_keyboard_set_keymap(kb->device->keyboard, keymap); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++ } ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.42.0 + + +From 3428168a686e2da8ba8a9dc1473350610afaef19 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Thu, 19 Oct 2023 23:46:06 -0500 +Subject: [PATCH 2/2] fix build + +--- + dwl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/dwl.c b/dwl.c +index 25458e6..090280f 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2468,7 +2468,7 @@ togglekblayout(const Arg *arg) + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &newrule, + XKB_KEYMAP_COMPILE_NO_FLAGS); +- wlr_keyboard_set_keymap(kb->device->keyboard, keymap); ++ wlr_keyboard_set_keymap(kb->wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + } +-- +2.42.0 diff --git a/dwl/dwl.1 b/dwl/dwl.1 new file mode 100644 index 0000000..780c78f --- /dev/null +++ b/dwl/dwl.1 @@ -0,0 +1,158 @@ +.Dd January 8, 2021 +.Dt DWL 1 +.Os +.Sh NAME +.Nm dwl +.Nd dwm for Wayland +.Sh SYNOPSIS +.Nm +.Op Fl v +.Op Fl d +.Op Fl s Ar startup command +.Sh DESCRIPTION +.Nm +is a Wayland compositor based on wlroots. +It is intended to fill the same space in the Wayland world that +.Nm dwm +does for X11. +.Pp +When given the +.Fl v +option, +.Nm +writes its name and version to standard error and exits unsuccessfully. +.Pp +When given the +.Fl d +option, +.Nm +enables full wlroots logging, including debug information. +.Pp +When given the +.Fl s +option, +.Nm +starts a shell process running +.Ar command +when starting. +When stopping, it sends +.Dv SIGTERM +to the child process and waits for it to exit. +.Pp +Users are encouraged to customize +.Nm +by editing the sources, in particular +.Pa config.h . +The default key bindings are as follows: +.Bl -tag -width 20n -offset indent -compact +.It Mod-[1-9] +Show only all windows with a tag. +.It Mod-Ctrl-[1-9] +Show all windows with a tag. +.It Mod-Shift-[1-9] +Move window to a single tag. +.It Mod-Ctrl-Shift-[1-9] +Toggle tag for window. +.It Mod-p +Spawn +.Nm wmenu-run . +.It Mod-Shift-Return +Spawn +.Nm foot . +.It Mod-[jk] +Move focus down/up the stack. +.It Mod-[id] +Increase/decrease number of windows in master area. +.It Mod-[hl] +Decrease/increase master area. +.It Mod-Return +Move window on top of stack or switch top of stack with second window. +.It Mod-Tab +Show only all windows with previous tag. +.It Mod-Shift-c +Close window. +.It Mod-t +Switch to tabbed layout. +.It Mod-f +Switch to floating layout. +.It Mod-m +Switch to monocle layout. +.It Mod-Space +Switch to previous layout. +.It Mod-Shift-Space +Toggle floating state of window. +.It Mod-e +Toggle fullscreen state of window. +.It Mod-0 +Show all windows. +.It Mod-Shift-0 +Set all tags for window. +.It Mod-, +Move focus to previous monitor. +.It Mod-. +Move focus to next monitor. +.It Mod-Shift-, +Move window to previous monitor. +.It Mod-Shift-. +Move window to next monitor. +.It Mod-Shift-q +Quit +.Nm . +.El +These might differ depending on your keyboard layout. +.Sh ENVIRONMENT +These environment variables are used by +.Nm : +.Bl -tag -width XDG_RUNTIME_DIR +.It Ev XDG_RUNTIME_DIR +A directory where temporary user files, such as the Wayland socket, +are stored. +.It Ev XDG_CONFIG_DIR +A directory containing configuration of various programs and +libraries, including libxkbcommon. +.It Ev DISPLAY , WAYLAND_DISPLAY , WAYLAND_SOCKET +Tell how to connect to an underlying X11 or Wayland server. +.It Ev WLR_* +Various variables specific to wlroots. +.It Ev XKB_* , XLOCALEDIR , XCOMPOSEFILE +Various variables specific to libxkbcommon. +.It Ev XCURSOR_PATH +List of directories to search for XCursor themes in. +.It Ev HOME +A directory where there are always dear files there for you. +Waiting for you to clean them up. +.El +.Pp +These are set by +.Nm : +.Bl -tag -width WAYLAND_DISPLAY +.It Ev WAYLAND_DISPLAY +Tell how to connect to +.Nm . +.It Ev DISPLAY +If using +.Nm Xwayland , +tell how to connect to the +.Nm Xwayland +server. +.El +.Sh EXAMPLES +Start +.Nm +with s6 in the background: +.Dl dwl \-s \(aqs6\-svscan <&\-\(aq +.Sh SEE ALSO +.Xr foot 1 , +.Xr wmenu 1 , +.Xr dwm 1 , +.Xr xkeyboard-config 7 +.Sh CAVEATS +The child process's standard input is connected with a pipe to +.Nm . +If the child process neither reads from the pipe nor closes its +standard input, +.Nm +will freeze after a while due to it blocking when writing to the full +pipe buffer. +.Sh BUGS +All of them. diff --git a/dwl/dwl.c b/dwl/dwl.c new file mode 100644 index 0000000..1613d8c --- /dev/null +++ b/dwl/dwl.c @@ -0,0 +1,3575 @@ +/* + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "drwl.h" + +/* macros */ +#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 LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << LENGTH(tags)) - 1) +#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) +#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ +#ifdef XWAYLAND +enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +#endif + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* LayerShell */ + struct wlr_box geom; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void autostartexec(void); +static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +static void cleanupmon(struct wl_listener *listener, void *data); +static void closemon(Monitor *m); +static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +static void commitnotify(struct wl_listener *listener, void *data); +static void commitpopup(struct wl_listener *listener, void *data); +static void createdecoration(struct wl_listener *listener, void *data); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +static void moveresizekb(const Arg *arg); +static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroysessionlock(struct wl_listener *listener, void *data); +static void destroysessionmgr(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); +static Monitor *dirtomon(enum wlr_direction dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +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 handlesig(int signo); +static void incnmaster(const Arg *arg); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void monocle(Monitor *m); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +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 setgamma(struct wl_listener *listener, void *data); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); +static void setup(void); +static void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unlocksession(struct wl_listener *listener, void *data); +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); +static void unmapnotify(struct wl_listener *listener, void *data); +static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; +static struct wl_listener lock_listener = {.notify = locksession}; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(struct wl_listener *listener, void *data); +static void configurex11(struct wl_listener *listener, void *data); +static void createnotifyx11(struct wl_listener *listener, void *data); +static void dissociatex11(struct wl_listener *listener, void *data); +static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wlr_xwayland *xwayland; +static xcb_atom_t netatom[NetLast]; +#endif + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" + +static pid_t *autostart_pids; +static size_t autostart_len; + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i; + const Rule *r; + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + l->geom.x = l->scene->node.x; + l->geom.y = l->scene->node.y; + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exlusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } +} + +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * 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 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, + event->delta_discrete, event->source, event->relative_direction); +} + +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + +void +buttonpress(struct wl_listener *listener, void *data) +{ + unsigned int i = 0, x = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; + uint32_t mods; + Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + return; + } else { + cursor_mode = CurNormal; + } + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ + size_t i; +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + drawbars(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + wlr_fractional_scale_v1_notify_scale(layer_surface->surface, l->mon->wlr_output->scale); + wlr_surface_set_preferred_buffer_scale(layer_surface->surface, (int32_t)ceilf(l->mon->wlr_output->scale)); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title this will likely select + * a wrong monitor. + */ + applyrules(c); + wlr_surface_set_preferred_buffer_scale(client_surface(c), (int)ceilf(c->mon->wlr_output->scale)); + wlr_fractional_scale_v1_notify_scale(client_surface(c), c->mon->wlr_output->scale); + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + return; + } + + if (client_surface(c)->mapped && c->mon) + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) + return; + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->gaps = gaps; + + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + + wl_list_insert(&mons, &m->link); + drawbars(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method (device, scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroysessionmgr(struct wl_listener *listener, void *data) +{ + wl_list_remove(&lock_listener.link); + wl_list_remove(&listener->link); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wlr_keyboard_group_destroy(group->wlr_group); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } + drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +void +focusstack(const Arg *arg) +{ + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } + /* If only one client is visible on selmon, then c == sel */ + focusclient(c, 1); +} + +/* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + LISTEN_STATIC(&drw->events.lost, gpureset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) { + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid +#ifdef XWAYLAND + && (!xwayland || in.si_pid != xwayland->server->pid) +#endif + ) { + pid_t *p, *lim; + waitpid(in.si_pid, NULL, 0); + if (in.si_pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == in.si_pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +} + +void +incnmaster(const Arg *arg) +{ + if (!arg || !selmon) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + */ + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { + k->func(&k->arg); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); + LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); + LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ + Client* focused = focustop(selmon); + if (focused) + wl_list_insert(&focused->link, &c->link); + else + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor than its parent, if not + * try to apply rules for them */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + drawbars(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + +void +monocle(Monitor *m) +{ + Client *c; + int n = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + resize(c, m->w, 0); + n++; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); +} + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->geom.x : w->geom.x); + sy = cursor->y - (l ? l->geom.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } + + /* 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) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change, this to avoid + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + /* https://codeberg.org/dwl/dwl/issues/577 */ + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct wlr_gamma_control_v1 *gamma_control; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite + * wlr_scene_output_commit() in order to add the gamma to the pending + * state before committing, instead try to commit the gamma in one frame, + * and commit the rest of the state in the next one (or in the same frame if + * the gamma can not be committed). + */ + if (m->gamma_lut_changed) { + gamma_control + = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); + m->gamma_lut_changed = 0; + + if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) + goto commit; + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto commit; + } + wlr_output_commit_state(m->wlr_output, &pending); + wlr_output_schedule_frame(m->wlr_output); + } else { +commit: + wlr_scene_output_commit(m->scene_output, NULL); + } + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); + if (startup_cmd) { + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + close(STDIN_FILENO); + setsid(); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + } + + drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully + * initialized, as the image/coordinates are not transformed for the + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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 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); +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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)); +} + +void +setfloating(Client *c, int floating) +{ + Client *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + drawbars(); +} + +void +setgamma(struct wl_listener *listener, void *data) +{ + struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + Monitor *m = event->output->data; + if (!m) + return; + m->gamma_lut_changed = 1; + wlr_output_schedule_frame(m->wlr_output); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +void +setup(void) +{ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: + * wlr_renderer_init_wl_display(drw); + * but we need to create manually the linux_dmabuf interface to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + LISTEN_STATIC(&activation->events.request_activate, urgent); + + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + LISTEN_STATIC(&power_mgr->events.set_mode, powermgrsetmode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + LISTEN_STATIC(&backend->events.new_output, createmon); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); + LISTEN_STATIC(&xdg_shell->events.new_popup, createpopup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + LISTEN_STATIC(&layer_shell->events.new_surface, createlayersurface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + LISTEN_STATIC(&xdg_decoration_mgr->events.new_toplevel_decoration, createdecoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + LISTEN_STATIC(&pointer_constraints->events.new_constraint, createpointerconstraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + LISTEN_STATIC(&cursor->events.motion, motionrelative); + LISTEN_STATIC(&cursor->events.motion_absolute, motionabsolute); + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); + LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + LISTEN_STATIC(&xwayland->events.ready, xwaylandready); + LISTEN_STATIC(&xwayland->events.new_surface, createnotifyx11); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[1024]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + +void +tile(Monitor *m) +{ + unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + if (n == 0) + return; + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; + } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; + } + i++; + } +} + +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +void +moveresizekb(const Arg *arg) +{ + Client *c = focustop(selmon); + Monitor *m = selmon; + + if(!(m && arg && arg->v && c && c->isfloating)) { + return; + } + + resize(c, (struct wlr_box){ + .x = c->geom.x + ((int *)arg->v)[0], + .y = c->geom.y + ((int *)arg->v)[1], + .width = c->geom.width + ((int *)arg->v)[2], + .height = c->geom.height + ((int *)arg->v)[3], + }, 1); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == exclusive_focus) + exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +unmapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + drawbars(); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + +void +zoom(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; + + /* Search for the first tiled window that is not sel, marking sel as + * NULL if we pass it along the way */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating) { + if (c != sel) + break; + sel = NULL; + } + } + + /* Return if no other tiled window was found */ + if (&c->link == &clients) + return; + + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) + sel = c; + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + + focusclient(sel, 1); + arrange(selmon); +} + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + /* TODO: figure out if there is another way to do this */ + if (!c->mon) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, + .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); + else + arrange(c->mon); +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +xcb_atom_t +getatom(xcb_connection_t *xc, const char *name) +{ + xcb_atom_t atom = 0; + xcb_intern_atom_reply_t *reply; + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) + atom = reply->atom; + free(reply); + + return atom; +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + drawbars(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); + int err = xcb_connection_has_error(xc); + if (err) { + fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); + return; + } + + /* Collect atoms we are interested in. If getatom returns 0, we will + * not detect that window type. */ + netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); + netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); + netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of dwl. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) + wlr_xwayland_set_cursor(xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + + xcb_disconnect(xc); +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else + goto usage; + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); +} diff --git a/dwl/dwl.c.orig b/dwl/dwl.c.orig new file mode 100644 index 0000000..c6c419e --- /dev/null +++ b/dwl/dwl.c.orig @@ -0,0 +1,3556 @@ +/* + * See LICENSE file for copyright and license details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XWAYLAND +#include +#include +#include +#endif + +#include "util.h" +#include "drwl.h" + +/* macros */ +#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 LENGTH(X) (sizeof X / sizeof X[0]) +#define END(A) ((A) + LENGTH(A)) +#define TAGMASK ((1u << LENGTH(tags)) - 1) +#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) +#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0) +#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ +enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ +enum { XDGShell, LayerShell, X11 }; /* client types */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ +#ifdef XWAYLAND +enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +#endif + +typedef union { + int i; + uint32_t ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* XDGShell or X11* */ + struct wlr_box geom; /* layout-relative, includes border */ + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ + struct wlr_scene_tree *scene_surface; + struct wl_list link; + struct wl_list flink; + union { + struct wlr_xdg_surface *xdg; + struct wlr_xwayland_surface *xwayland; + } surface; + struct wlr_xdg_toplevel_decoration_v1 *decoration; + struct wl_listener commit; + struct wl_listener map; + struct wl_listener maximize; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener set_title; + struct wl_listener fullscreen; + struct wl_listener set_decoration_mode; + struct wl_listener destroy_decoration; + struct wlr_box prev; /* layout-relative, includes border */ + struct wlr_box bounds; +#ifdef XWAYLAND + struct wl_listener activate; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener configure; + struct wl_listener set_hints; +#endif + unsigned int bw; + uint32_t tags; + int isfloating, isurgent, isfullscreen; + uint32_t resize; /* configure serial of a pending resize */ +} Client; + +typedef struct { + uint32_t mod; + xkb_keysym_t keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + struct wl_list link; + struct wlr_keyboard_group *wlr_group; + + int nsyms; + const xkb_keysym_t *keysyms; /* invalid if nsyms == 0 */ + uint32_t mods; /* invalid if nsyms == 0 */ + struct wl_event_source *key_repeat_source; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} KeyboardGroup; + +typedef struct { + /* Must keep these three elements in this order */ + unsigned int type; /* LayerShell */ + struct wlr_box geom; + Monitor *mon; + struct wlr_scene_tree *scene; + struct wlr_scene_tree *popups; + struct wlr_scene_layer_surface_v1 *scene_layer; + struct wl_list link; + int mapped; + struct wlr_layer_surface_v1 *layer_surface; + + struct wl_listener destroy; + struct wl_listener unmap; + struct wl_listener surface_commit; +} LayerSurface; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +typedef struct { + struct wlr_buffer base; + struct wl_listener release; + bool busy; + Img *image; + uint32_t data[]; +} Buffer; + +struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; + struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; + struct wl_listener request_state; + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ + struct { + int width, height; + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; + int gaps; + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; + float mfact; + int gamma_lut_changed; + int nmaster; + char ltsymbol[16]; + int asleep; + Drwl *drw; + Buffer *pool[2]; + int lrpad; +}; + +typedef struct { + const char *name; + float mfact; + int nmaster; + float scale; + const Layout *lt; + enum wl_output_transform rr; + int x, y; +} MonitorRule; + +typedef struct { + struct wlr_pointer_constraint_v1 *constraint; + struct wl_listener destroy; +} PointerConstraint; + +typedef struct { + const char *id; + const char *title; + uint32_t tags; + int isfloating; + int monitor; +} Rule; + +typedef struct { + struct wlr_scene_tree *scene; + + struct wlr_session_lock_v1 *lock; + struct wl_listener new_surface; + struct wl_listener unlock; + struct wl_listener destroy; +} SessionLock; + +/* function declarations */ +static void applybounds(Client *c, struct wlr_box *bbox); +static void applyrules(Client *c); +static void arrange(Monitor *m); +static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); +static void arrangelayers(Monitor *m); +static void autostartexec(void); +static void axisnotify(struct wl_listener *listener, void *data); +static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); +static void bufdestroy(struct wlr_buffer *buffer); +static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride); +static void bufdataend(struct wlr_buffer *buffer); +static Buffer *bufmon(Monitor *m); +static void bufrelease(struct wl_listener *listener, void *data); +static void buttonpress(struct wl_listener *listener, void *data); +static void chvt(const Arg *arg); +static void checkidleinhibitor(struct wlr_surface *exclude); +static void cleanup(void); +static void cleanupmon(struct wl_listener *listener, void *data); +static void closemon(Monitor *m); +static void commitlayersurfacenotify(struct wl_listener *listener, void *data); +static void commitnotify(struct wl_listener *listener, void *data); +static void commitpopup(struct wl_listener *listener, void *data); +static void createdecoration(struct wl_listener *listener, void *data); +static void createidleinhibitor(struct wl_listener *listener, void *data); +static void createkeyboard(struct wlr_keyboard *keyboard); +static KeyboardGroup *createkeyboardgroup(void); +static void createlayersurface(struct wl_listener *listener, void *data); +static void createlocksurface(struct wl_listener *listener, void *data); +static void createmon(struct wl_listener *listener, void *data); +static void createnotify(struct wl_listener *listener, void *data); +static void createpointer(struct wlr_pointer *pointer); +static void createpointerconstraint(struct wl_listener *listener, void *data); +static void createpopup(struct wl_listener *listener, void *data); +static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); +static void cursorframe(struct wl_listener *listener, void *data); +static void cursorwarptohint(void); +static void destroydecoration(struct wl_listener *listener, void *data); +static void destroydragicon(struct wl_listener *listener, void *data); +static void destroyidleinhibitor(struct wl_listener *listener, void *data); +static void destroylayersurfacenotify(struct wl_listener *listener, void *data); +static void destroylock(SessionLock *lock, int unlocked); +static void destroylocksurface(struct wl_listener *listener, void *data); +static void destroynotify(struct wl_listener *listener, void *data); +static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroysessionlock(struct wl_listener *listener, void *data); +static void destroysessionmgr(struct wl_listener *listener, void *data); +static void destroykeyboardgroup(struct wl_listener *listener, void *data); +static Monitor *dirtomon(enum wlr_direction dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void focusclient(Client *c, int lift); +static void focusmon(const Arg *arg); +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 handlesig(int signo); +static void incnmaster(const Arg *arg); +static void inputdevice(struct wl_listener *listener, void *data); +static int keybinding(uint32_t mods, xkb_keysym_t sym); +static void keypress(struct wl_listener *listener, void *data); +static void keypressmod(struct wl_listener *listener, void *data); +static int keyrepeat(void *data); +static void killclient(const Arg *arg); +static void locksession(struct wl_listener *listener, void *data); +static void mapnotify(struct wl_listener *listener, void *data); +static void maximizenotify(struct wl_listener *listener, void *data); +static void monocle(Monitor *m); +static void motionabsolute(struct wl_listener *listener, void *data); +static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +static void motionrelative(struct wl_listener *listener, void *data); +static void moveresize(const Arg *arg); +static void outputmgrapply(struct wl_listener *listener, void *data); +static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); +static void outputmgrtest(struct wl_listener *listener, void *data); +static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +static void powermgrsetmode(struct wl_listener *listener, void *data); +static void quit(const Arg *arg); +static void rendermon(struct wl_listener *listener, void *data); +static void requestdecorationmode(struct wl_listener *listener, void *data); +static void requeststartdrag(struct wl_listener *listener, void *data); +static void requestmonstate(struct wl_listener *listener, void *data); +static void resize(Client *c, struct wlr_box geo, int interact); +static void run(char *startup_cmd); +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 setgamma(struct wl_listener *listener, void *data); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setmon(Client *c, Monitor *m, uint32_t newtags); +static void setpsel(struct wl_listener *listener, void *data); +static void setsel(struct wl_listener *listener, void *data); +static void setup(void); +static void spawn(const Arg *arg); +static void startdrag(struct wl_listener *listener, void *data); +static int statusin(int fd, unsigned int mask, void *data); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void togglefullscreen(const Arg *arg); +static void togglegaps(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unlocksession(struct wl_listener *listener, void *data); +static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); +static void unmapnotify(struct wl_listener *listener, void *data); +static void updatemons(struct wl_listener *listener, void *data); +static void updatebar(Monitor *m); +static void updatetitle(struct wl_listener *listener, void *data); +static void urgent(struct wl_listener *listener, void *data); +static void view(const Arg *arg); +static void virtualkeyboard(struct wl_listener *listener, void *data); +static void virtualpointer(struct wl_listener *listener, void *data); +static Monitor *xytomon(double x, double y); +static void xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static pid_t child_pid = -1; +static int locked; +static void *exclusive_focus; +static struct wl_display *dpy; +static struct wl_event_loop *event_loop; +static struct wlr_backend *backend; +static struct wlr_scene *scene; +static struct wlr_scene_tree *layers[NUM_LAYERS]; +static struct wlr_scene_tree *drag_icon; +/* Map from ZWLR_LAYER_SHELL_* constants to Lyr* enum */ +static const int layermap[] = { LyrBg, LyrBottom, LyrTop, LyrOverlay }; +static struct wlr_renderer *drw; +static struct wlr_allocator *alloc; +static struct wlr_compositor *compositor; +static struct wlr_session *session; + +static struct wlr_xdg_shell *xdg_shell; +static struct wlr_xdg_activation_v1 *activation; +static struct wlr_xdg_decoration_manager_v1 *xdg_decoration_mgr; +static struct wl_list clients; /* tiling order */ +static struct wl_list fstack; /* focus order */ +static struct wlr_idle_notifier_v1 *idle_notifier; +static struct wlr_idle_inhibit_manager_v1 *idle_inhibit_mgr; +static struct wlr_layer_shell_v1 *layer_shell; +static struct wlr_output_manager_v1 *output_mgr; +static struct wlr_gamma_control_manager_v1 *gamma_control_mgr; +static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr; +static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; +static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; +static struct wlr_output_power_manager_v1 *power_mgr; + +static struct wlr_pointer_constraints_v1 *pointer_constraints; +static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; +static struct wlr_pointer_constraint_v1 *active_constraint; + +static struct wlr_cursor *cursor; +static struct wlr_xcursor_manager *cursor_mgr; + +static struct wlr_scene_rect *root_bg; +static struct wlr_session_lock_manager_v1 *session_lock_mgr; +static struct wlr_scene_rect *locked_bg; +static struct wlr_session_lock_v1 *cur_lock; +static struct wl_listener lock_listener = {.notify = locksession}; + +static struct wlr_seat *seat; +static KeyboardGroup *kb_group; +static unsigned int cursor_mode; +static Client *grabc; +static int grabcx, grabcy; /* client-relative */ + +static struct wlr_output_layout *output_layout; +static struct wlr_box sgeom; +static struct wl_list mons; +static Monitor *selmon; + +static char stext[256]; +static struct wl_event_source *status_event_source; + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, + .end_data_ptr_access = bufdataend, +}; + +#ifdef XWAYLAND +static void activatex11(struct wl_listener *listener, void *data); +static void associatex11(struct wl_listener *listener, void *data); +static void configurex11(struct wl_listener *listener, void *data); +static void createnotifyx11(struct wl_listener *listener, void *data); +static void dissociatex11(struct wl_listener *listener, void *data); +static xcb_atom_t getatom(xcb_connection_t *xc, const char *name); +static void sethints(struct wl_listener *listener, void *data); +static void xwaylandready(struct wl_listener *listener, void *data); +static struct wlr_xwayland *xwayland; +static xcb_atom_t netatom[NetLast]; +#endif + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* attempt to encapsulate suck into one file */ +#include "client.h" + +static pid_t *autostart_pids; +static size_t autostart_len; + +/* function implementations */ +void +applybounds(Client *c, struct wlr_box *bbox) +{ + /* set minimum possible */ + c->geom.width = MAX(1 + 2 * (int)c->bw, c->geom.width); + c->geom.height = MAX(1 + 2 * (int)c->bw, c->geom.height); + + if (c->geom.x >= bbox->x + bbox->width) + c->geom.x = bbox->x + bbox->width - c->geom.width; + if (c->geom.y >= bbox->y + bbox->height) + c->geom.y = bbox->y + bbox->height - c->geom.height; + if (c->geom.x + c->geom.width <= bbox->x) + c->geom.x = bbox->x; + if (c->geom.y + c->geom.height <= bbox->y) + c->geom.y = bbox->y; +} + +void +applyrules(Client *c) +{ + /* rule matching */ + const char *appid, *title; + uint32_t newtags = 0; + int i; + const Rule *r; + Monitor *mon = selmon, *m; + + c->isfloating = client_is_float_type(c); + if (!(appid = client_get_appid(c))) + appid = broken; + if (!(title = client_get_title(c))) + title = broken; + + for (r = rules; r < END(rules); r++) { + if ((!r->title || strstr(title, r->title)) + && (!r->id || strstr(appid, r->id))) { + c->isfloating = r->isfloating; + newtags |= r->tags; + i = 0; + wl_list_for_each(m, &mons, link) { + if (r->monitor == i++) + mon = m; + } + } + } + setmon(c, mon, newtags); +} + +void +arrange(Monitor *m) +{ + Client *c; + + if (!m->wlr_output->enabled) + return; + + wl_list_for_each(c, &clients, link) { + if (c->mon == m) { + wlr_scene_node_set_enabled(&c->scene->node, VISIBLEON(c, m)); + client_set_suspended(c, !VISIBLEON(c, m)); + } + } + + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ + wl_list_for_each(c, &clients, link) { + if (c->mon != m || c->scene->node.parent == layers[LyrFS]) + continue; + + wlr_scene_node_reparent(&c->scene->node, + (!m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrTile] + : (m->lt[m->sellt]->arrange && c->isfloating) + ? layers[LyrFloat] + : c->scene->node.parent); + } + + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); + motionnotify(0, NULL, 0, 0, 0, 0); + checkidleinhibitor(NULL); +} + +void +arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive) +{ + LayerSurface *l; + struct wlr_box full_area = m->m; + + wl_list_for_each(l, list, link) { + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + + if (exclusive != (layer_surface->current.exclusive_zone > 0)) + continue; + + wlr_scene_layer_surface_v1_configure(l->scene_layer, &full_area, usable_area); + wlr_scene_node_set_position(&l->popups->node, l->scene->node.x, l->scene->node.y); + l->geom.x = l->scene->node.x; + l->geom.y = l->scene->node.y; + } +} + +void +arrangelayers(Monitor *m) +{ + int i; + struct wlr_box usable_area = m->m; + LayerSurface *l; + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + if (!m->wlr_output->enabled) + return; + + if (m->scene_buffer->node.enabled) { + usable_area.height -= m->b.real_height; + usable_area.y += topbar ? m->b.real_height : 0; + } + + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); + + if (!wlr_box_equal(&usable_area, &m->w)) { + m->w = usable_area; + arrange(m); + } + + /* Arrange non-exlusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 0); + + /* Find topmost keyboard interactive layer, if such a layer exists */ + for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { + wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { + if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) + continue; + /* Deactivate the focused client. */ + focusclient(NULL, 0); + exclusive_focus = l; + client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } +} + +void +autostartexec(void) { + const char *const *p; + size_t i = 0; + + /* count entries */ + for (p = autostart; *p; autostart_len++, p++) + while (*++p); + + autostart_pids = calloc(autostart_len, sizeof(pid_t)); + for (p = autostart; *p; i++, p++) { + if ((autostart_pids[i] = fork()) == 0) { + setsid(); + execvp(*p, (char *const *)p); + die("dwl: execvp %s:", *p); + } + /* skip arguments */ + while (*++p); + } +} + +void +axisnotify(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * 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 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, + event->delta_discrete, event->source, event->relative_direction); +} + +bool +baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) +{ + return true; +} + +void +bufdestroy(struct wlr_buffer *wlr_buffer) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + if (buf->busy) + wl_list_remove(&buf->release.link); + drwl_image_destroy(buf->image); + free(buf); +} + +bool +bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) +{ + Buffer *buf = wl_container_of(wlr_buffer, buf, base); + + if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; + + *data = buf->data; + *stride = wlr_buffer->width * 4; + *format = DRM_FORMAT_ARGB8888; + + return true; +} + +void +bufdataend(struct wlr_buffer *wlr_buffer) +{ +} + +Buffer * +bufmon(Monitor *m) +{ + size_t i; + Buffer *buf = NULL; + + for (i = 0; i < LENGTH(m->pool); i++) { + if (m->pool[i]) { + if (m->pool[i]->busy) + continue; + buf = m->pool[i]; + break; + } + + buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); + buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); + wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); + m->pool[i] = buf; + break; + } + if (!buf) + return NULL; + + buf->busy = true; + LISTEN(&buf->base.events.release, &buf->release, bufrelease); + wlr_buffer_lock(&buf->base); + drwl_setimage(m->drw, buf->image); + return buf; +} + +void +bufrelease(struct wl_listener *listener, void *data) +{ + Buffer *buf = wl_container_of(listener, buf, release); + buf->busy = false; + wl_list_remove(&buf->release.link); +} + +void +buttonpress(struct wl_listener *listener, void *data) +{ + unsigned int i = 0, x = 0; + double cx; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + struct wlr_scene_node *node; + struct wlr_scene_buffer *buffer; + uint32_t mods; + Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + click = ClkRoot; + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (c) + click = ClkClient; + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; + selmon = xytomon(cursor->x, cursor->y); + if (locked) + break; + + if (!c && !exclusive_focus && + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; + else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { + click = ClkStatus; + } else + click = ClkTitle; + } + + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); + if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { + b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); + return; + } + } + break; + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); + return; + } else { + cursor_mode = CurNormal; + } + break; + } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, + event->time_msec, event->button, event->state); +} + +void +chvt(const Arg *arg) +{ + wlr_session_change_vt(session, arg->ui); +} + +void +checkidleinhibitor(struct wlr_surface *exclude) +{ + int inhibited = 0, unused_lx, unused_ly; + struct wlr_idle_inhibitor_v1 *inhibitor; + wl_list_for_each(inhibitor, &idle_inhibit_mgr->inhibitors, link) { + struct wlr_surface *surface = wlr_surface_get_root_surface(inhibitor->surface); + struct wlr_scene_tree *tree = surface->data; + if (exclude != surface && (bypass_surface_visibility || (!tree + || wlr_scene_node_coords(&tree->node, &unused_lx, &unused_ly)))) { + inhibited = 1; + break; + } + } + + wlr_idle_notifier_v1_set_inhibited(idle_notifier, inhibited); +} + +void +cleanup(void) +{ + size_t i; +#ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; +#endif + wl_display_destroy_clients(dpy); + + /* kill child processes */ + for (i = 0; i < autostart_len; i++) { + if (0 < autostart_pids[i]) { + kill(autostart_pids[i], SIGTERM); + waitpid(autostart_pids[i], NULL, 0); + } + } + + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + } + wlr_xcursor_manager_destroy(cursor_mgr); + + destroykeyboardgroup(&kb_group->destroy, NULL); + + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); + + wl_display_destroy(dpy); + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); + + drwl_fini(); +} + +void +cleanupmon(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy); + LayerSurface *l, *tmp; + size_t i; + + /* m->layers[i] are intentionally not unlinked */ + for (i = 0; i < LENGTH(m->layers); i++) { + wl_list_for_each_safe(l, tmp, &m->layers[i], link) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); + wl_list_remove(&m->request_state.link); + m->wlr_output->data = NULL; + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); +} + +void +closemon(Monitor *m) +{ + /* update selmon if needed and + * move closed monitor's clients to the focused one */ + Client *c; + int i = 0, nmons = wl_list_length(&mons); + if (!nmons) { + selmon = NULL; + } else if (m == selmon) { + do /* don't switch to disabled mons */ + selmon = wl_container_of(mons.next, selmon, link); + while (!selmon->wlr_output->enabled && i++ < nmons); + + if (!selmon->wlr_output->enabled) + selmon = NULL; + } + + wl_list_for_each(c, &clients, link) { + if (c->isfloating && c->geom.x > m->m.width) + resize(c, (struct wlr_box){.x = c->geom.x - m->w.width, .y = c->geom.y, + .width = c->geom.width, .height = c->geom.height}, 0); + if (c->mon == m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + drawbars(); +} + +void +commitlayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = l->layer_surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->current.layer]]; + struct wlr_layer_surface_v1_state old_state; + + if (l->layer_surface->initial_commit) { + wlr_fractional_scale_v1_notify_scale(layer_surface->surface, l->mon->wlr_output->scale); + wlr_surface_set_preferred_buffer_scale(layer_surface->surface, (int32_t)ceilf(l->mon->wlr_output->scale)); + + /* Temporarily set the layer's current state to pending + * so that we can easily arrange it */ + old_state = l->layer_surface->current; + l->layer_surface->current = l->layer_surface->pending; + arrangelayers(l->mon); + l->layer_surface->current = old_state; + return; + } + + if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) + return; + l->mapped = layer_surface->surface->mapped; + + if (scene_layer != l->scene->node.parent) { + wlr_scene_node_reparent(&l->scene->node, scene_layer); + wl_list_remove(&l->link); + wl_list_insert(&l->mon->layers[layer_surface->current.layer], &l->link); + wlr_scene_node_reparent(&l->popups->node, (layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer)); + } + + arrangelayers(l->mon); +} + +void +commitnotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, commit); + + if (c->surface.xdg->initial_commit) { + /* + * Get the monitor this client will be rendered on + * Note that if the user set a rule in which the client is placed on + * a different monitor based on its title this will likely select + * a wrong monitor. + */ + applyrules(c); + wlr_surface_set_preferred_buffer_scale(client_surface(c), (int)ceilf(c->mon->wlr_output->scale)); + wlr_fractional_scale_v1_notify_scale(client_surface(c), c->mon->wlr_output->scale); + setmon(c, NULL, 0); /* Make sure to reapply rules in mapnotify() */ + + wlr_xdg_toplevel_set_wm_capabilities(c->surface.xdg->toplevel, WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + wlr_xdg_toplevel_set_size(c->surface.xdg->toplevel, 0, 0); + if (c->decoration) + requestdecorationmode(&c->set_decoration_mode, c->decoration); + return; + } + + if (client_surface(c)->mapped && c->mon) + resize(c, c->geom, (c->isfloating && !c->isfullscreen)); + + /* mark a pending resize as completed */ + if (c->resize && c->resize <= c->surface.xdg->current.configure_serial) + c->resize = 0; +} + +void +commitpopup(struct wl_listener *listener, void *data) +{ + struct wlr_surface *surface = data; + struct wlr_xdg_popup *popup = wlr_xdg_popup_try_from_wlr_surface(surface); + LayerSurface *l = NULL; + Client *c = NULL; + struct wlr_box box; + int type = -1; + + if (!popup->base->initial_commit) + return; + + type = toplevel_from_wlr_surface(popup->base->surface, &c, &l); + if (!popup->parent || type < 0) + return; + popup->base->surface->data = wlr_scene_xdg_surface_create( + popup->parent->data, popup->base); + if ((l && !l->mon) || (c && !c->mon)) + return; + box = type == LayerShell ? l->mon->m : c->mon->w; + box.x -= (type == LayerShell ? l->geom.x : c->geom.x); + box.y -= (type == LayerShell ? l->geom.y : c->geom.y); + wlr_xdg_popup_unconstrain_from_box(popup, &box); + wl_list_remove(&listener->link); +} + +void +createdecoration(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_decoration_v1 *deco = data; + Client *c = deco->toplevel->base->data; + c->decoration = deco; + + LISTEN(&deco->events.request_mode, &c->set_decoration_mode, requestdecorationmode); + LISTEN(&deco->events.destroy, &c->destroy_decoration, destroydecoration); + + requestdecorationmode(&c->set_decoration_mode, deco); +} + +void +createidleinhibitor(struct wl_listener *listener, void *data) +{ + struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; + LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor); + + checkidleinhibitor(NULL); +} + +void +createkeyboard(struct wlr_keyboard *keyboard) +{ + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); +} + +KeyboardGroup * +createkeyboardgroup(void) +{ + KeyboardGroup *group = ecalloc(1, sizeof(*group)); + struct xkb_context *context; + struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + + /* Prepare an XKB keymap and assign it to the keyboard group. */ + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, + XKB_KEYMAP_COMPILE_NO_FLAGS))) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + + /* Set up listeners for keyboard events */ + LISTEN(&group->wlr_group->keyboard.events.key, &group->key, keypress); + LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, keypressmod); + + group->key_repeat_source = wl_event_loop_add_timer(event_loop, keyrepeat, group); + + /* A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same wlr_keyboard_group, which provides a single wlr_keyboard interface for + * all of them. Set this combined wlr_keyboard as the seat keyboard. + */ + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + return group; +} + +void +createlayersurface(struct wl_listener *listener, void *data) +{ + struct wlr_layer_surface_v1 *layer_surface = data; + LayerSurface *l; + struct wlr_surface *surface = layer_surface->surface; + struct wlr_scene_tree *scene_layer = layers[layermap[layer_surface->pending.layer]]; + + if (!layer_surface->output + && !(layer_surface->output = selmon ? selmon->wlr_output : NULL)) { + wlr_layer_surface_v1_destroy(layer_surface); + return; + } + + l = layer_surface->data = ecalloc(1, sizeof(*l)); + l->type = LayerShell; + LISTEN(&surface->events.commit, &l->surface_commit, commitlayersurfacenotify); + LISTEN(&surface->events.unmap, &l->unmap, unmaplayersurfacenotify); + LISTEN(&layer_surface->events.destroy, &l->destroy, destroylayersurfacenotify); + + l->layer_surface = layer_surface; + l->mon = layer_surface->output->data; + l->scene_layer = wlr_scene_layer_surface_v1_create(scene_layer, layer_surface); + l->scene = l->scene_layer->tree; + l->popups = surface->data = wlr_scene_tree_create(layer_surface->current.layer + < ZWLR_LAYER_SHELL_V1_LAYER_TOP ? layers[LyrTop] : scene_layer); + l->scene->node.data = l->popups->node.data = l; + + wl_list_insert(&l->mon->layers[layer_surface->pending.layer],&l->link); + wlr_surface_send_enter(surface, layer_surface->output); +} + +void +createlocksurface(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, new_surface); + struct wlr_session_lock_surface_v1 *lock_surface = data; + Monitor *m = lock_surface->output->data; + struct wlr_scene_tree *scene_tree = lock_surface->surface->data + = wlr_scene_subsurface_tree_create(lock->scene, lock_surface->surface); + m->lock_surface = lock_surface; + + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(lock_surface, m->m.width, m->m.height); + + LISTEN(&lock_surface->events.destroy, &m->destroy_lock_surface, destroylocksurface); + + if (m == selmon) + client_notify_enter(lock_surface->surface, wlr_seat_get_keyboard(seat)); +} + +void +createmon(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; + const MonitorRule *r; + size_t i; + struct wlr_output_state state; + Monitor *m; + + if (!wlr_output_init_render(wlr_output, alloc, drw)) + return; + + m = wlr_output->data = ecalloc(1, sizeof(*m)); + m->wlr_output = wlr_output; + + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->gaps = gaps; + + m->tagset[0] = m->tagset[1] = 1; + for (r = monrules; r < END(monrules); r++) { + if (!r->name || strstr(wlr_output->name, r->name)) { + m->m.x = r->x; + m->m.y = r->y; + m->mfact = r->mfact; + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; + } + } + + /* The mode is a tuple of (width, height, refresh rate), and each + * monitor supports only a specific set of modes. We just pick the + * monitor's preferred mode; a more sophisticated compositor would let + * the user configure it. */ + wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); + + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); + LISTEN(&wlr_output->events.request_state, &m->request_state, requestmonstate); + + wlr_output_state_set_enabled(&state, 1); + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + + if (!(m->drw = drwl_create())) + die("failed to create drwl context"); + + m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); + m->scene_buffer->point_accepts_input = baracceptsinput; + updatebar(m); + + wl_list_insert(&mons, &m->link); + drawbars(); + + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make + * sure that other screen content not part of the same surface tree (made + * up of subsurfaces, popups or similarly coupled surfaces) are not + * visible below the fullscreened surface. + * + */ + /* updatemons() will resize and set correct position */ + m->fullscreen_bg = wlr_scene_rect_create(layers[LyrFS], 0, 0, fullscreen_bg); + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, 0); + + /* Adds this to the output layout in the order it was configured. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + m->scene_output = wlr_scene_output_create(scene, wlr_output); + if (m->m.x == -1 && m->m.y == -1) + wlr_output_layout_add_auto(output_layout, wlr_output); + else + wlr_output_layout_add(output_layout, wlr_output, m->m.x, m->m.y); +} + +void +createnotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client creates a new toplevel (application window). */ + struct wlr_xdg_toplevel *toplevel = data; + Client *c = NULL; + + /* Allocate a Client for this surface */ + c = toplevel->base->data = ecalloc(1, sizeof(*c)); + c->surface.xdg = toplevel->base; + c->bw = borderpx; + + LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify); + LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify); + LISTEN(&toplevel->base->surface->events.unmap, &c->unmap, unmapnotify); + LISTEN(&toplevel->events.destroy, &c->destroy, destroynotify); + LISTEN(&toplevel->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&toplevel->events.request_maximize, &c->maximize, maximizenotify); + LISTEN(&toplevel->events.set_title, &c->set_title, updatetitle); +} + +void +createpointer(struct wlr_pointer *pointer) +{ + struct libinput_device *device; + if (wlr_input_device_is_libinput(&pointer->base) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); + } + + if (libinput_device_config_scroll_has_natural_scroll(device)) + libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); + + if (libinput_device_config_left_handed_is_available(device)) + libinput_device_config_left_handed_set(device, left_handed); + + if (libinput_device_config_middle_emulation_is_available(device)) + libinput_device_config_middle_emulation_set_enabled(device, middle_button_emulation); + + if (libinput_device_config_scroll_get_methods(device) != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) + libinput_device_config_scroll_set_method (device, scroll_method); + + if (libinput_device_config_click_get_methods(device) != LIBINPUT_CONFIG_CLICK_METHOD_NONE) + libinput_device_config_click_set_method (device, click_method); + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); + + if (libinput_device_config_accel_is_available(device)) { + libinput_device_config_accel_set_profile(device, accel_profile); + libinput_device_config_accel_set_speed(device, accel_speed); + } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +} + +void +createpointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = ecalloc(1, sizeof(*pointer_constraint)); + pointer_constraint->constraint = data; + LISTEN(&pointer_constraint->constraint->events.destroy, + &pointer_constraint->destroy, destroypointerconstraint); +} + +void +createpopup(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client (either xdg-shell or layer-shell) + * creates a new popup. */ + struct wlr_xdg_popup *popup = data; + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); +} + +void +cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) +{ + if (active_constraint == constraint) + return; + + if (active_constraint) + wlr_pointer_constraint_v1_send_deactivated(active_constraint); + + active_constraint = constraint; + wlr_pointer_constraint_v1_send_activated(constraint); +} + +void +cursorframe(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(seat); +} + +void +cursorwarptohint(void) +{ + Client *c = NULL; + double sx = active_constraint->current.cursor_hint.x; + double sy = active_constraint->current.cursor_hint.y; + + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->current.cursor_hint.enabled) { + wlr_cursor_warp(cursor, NULL, sx + c->geom.x + c->bw, sy + c->geom.y + c->bw); + wlr_seat_pointer_warp(active_constraint->seat, sx, sy); + } +} + +void +destroydecoration(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, destroy_decoration); + c->decoration = NULL; + + wl_list_remove(&c->destroy_decoration.link); + wl_list_remove(&c->set_decoration_mode.link); +} + +void +destroydragicon(struct wl_listener *listener, void *data) +{ + /* Focus enter isn't sent during drag, so refocus the focused node. */ + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +destroyidleinhibitor(struct wl_listener *listener, void *data) +{ + /* `data` is the wlr_surface of the idle inhibitor being destroyed, + * at this point the idle inhibitor is still in the list of the manager */ + checkidleinhibitor(wlr_surface_get_root_surface(data)); +} + +void +destroylayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, destroy); + + wl_list_remove(&l->link); + wl_list_remove(&l->destroy.link); + wl_list_remove(&l->unmap.link); + wl_list_remove(&l->surface_commit.link); + wlr_scene_node_destroy(&l->scene->node); + wlr_scene_node_destroy(&l->popups->node); + free(l); +} + +void +destroylock(SessionLock *lock, int unlock) +{ + wlr_seat_keyboard_notify_clear_focus(seat); + if ((locked = !unlock)) + goto destroy; + + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + focusclient(focustop(selmon), 0); + motionnotify(0, NULL, 0, 0, 0, 0); + +destroy: + wl_list_remove(&lock->new_surface.link); + wl_list_remove(&lock->unlock.link); + wl_list_remove(&lock->destroy.link); + + wlr_scene_node_destroy(&lock->scene->node); + cur_lock = NULL; + free(lock); +} + +void +destroylocksurface(struct wl_listener *listener, void *data) +{ + Monitor *m = wl_container_of(listener, m, destroy_lock_surface); + struct wlr_session_lock_surface_v1 *surface, *lock_surface = m->lock_surface; + + m->lock_surface = NULL; + wl_list_remove(&m->destroy_lock_surface.link); + + if (lock_surface->surface != seat->keyboard_state.focused_surface) + return; + + if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { + surface = wl_container_of(cur_lock->surfaces.next, surface, link); + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + } else if (!locked) { + focusclient(focustop(selmon), 1); + } else { + wlr_seat_keyboard_clear_focus(seat); + } +} + +void +destroynotify(struct wl_listener *listener, void *data) +{ + /* Called when the xdg_toplevel is destroyed. */ + Client *c = wl_container_of(listener, c, destroy); + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); +#ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); + wl_list_remove(&c->associate.link); + wl_list_remove(&c->configure.link); + wl_list_remove(&c->dissociate.link); + wl_list_remove(&c->set_hints.link); + } else +#endif + { + wl_list_remove(&c->commit.link); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); + } + free(c); +} + +void +destroypointerconstraint(struct wl_listener *listener, void *data) +{ + PointerConstraint *pointer_constraint = wl_container_of(listener, pointer_constraint, destroy); + + if (active_constraint == pointer_constraint->constraint) { + cursorwarptohint(); + active_constraint = NULL; + } + + wl_list_remove(&pointer_constraint->destroy.link); + free(pointer_constraint); +} + +void +destroysessionlock(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, destroy); + destroylock(lock, 0); +} + +void +destroysessionmgr(struct wl_listener *listener, void *data) +{ + wl_list_remove(&lock_listener.link); + wl_list_remove(&listener->link); +} + +void +destroykeyboardgroup(struct wl_listener *listener, void *data) +{ + KeyboardGroup *group = wl_container_of(listener, group, destroy); + wl_event_source_remove(group->key_repeat_source); + wlr_keyboard_group_destroy(group->wlr_group); + wl_list_remove(&group->key.link); + wl_list_remove(&group->modifiers.link); + wl_list_remove(&group->destroy.link); + free(group); +} + +Monitor * +dirtomon(enum wlr_direction dir) +{ + struct wlr_output *next; + if (!wlr_output_layout_get(output_layout, selmon->wlr_output)) + return selmon; + if ((next = wlr_output_layout_adjacent_output(output_layout, + dir, selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + if ((next = wlr_output_layout_farthest_output(output_layout, + dir ^ (WLR_DIRECTION_LEFT|WLR_DIRECTION_RIGHT), + selmon->wlr_output, selmon->m.x, selmon->m.y))) + return next->data; + return selmon; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; + uint32_t i, occ = 0, urg = 0; + Client *c; + Buffer *buf; + + if (!m->scene_buffer->node.enabled) + return; + if (!(buf = bufmon(m))) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ + drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { + if (c->mon != m) + continue; + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + c = focustop(m); + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(m, tags[i]); + drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, + m == selmon && c && c->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m, m->ltsymbol); + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + + if ((w = m->b.width - tw - x) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); + if (c && c->isfloating) + drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); + } else { + drwl_setscheme(m->drw, colors[SchemeNorm]); + drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); + } + } + + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, + m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); + wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); + wlr_buffer_unlock(&buf->base); +} + +void +drawbars(void) +{ + Monitor *m = NULL; + + wl_list_for_each(m, &mons, link) + drawbar(m); +} + +void +focusclient(Client *c, int lift) +{ + struct wlr_surface *old = seat->keyboard_state.focused_surface; + int unused_lx, unused_ly, old_client_type; + Client *old_c = NULL; + LayerSurface *old_l = NULL; + + if (locked) + return; + + /* Raise client in stacking order if requested */ + if (c && lift) + wlr_scene_node_raise_to_top(&c->scene->node); + + if (c && client_surface(c) == old) + return; + + if ((old_client_type = toplevel_from_wlr_surface(old, &old_c, &old_l)) == XDGShell) { + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &old_c->surface.xdg->popups, link) + wlr_xdg_popup_destroy(popup); + } + + /* Put the new client atop the focus stack and select its monitor */ + if (c && !client_is_unmanaged(c)) { + wl_list_remove(&c->flink); + wl_list_insert(&fstack, &c->flink); + selmon = c->mon; + c->isurgent = 0; + client_restack_surface(c); + + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) + client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, + * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) + && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + return; + } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { + return; + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { + client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } + drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ + wlr_seat_keyboard_notify_clear_focus(seat); + return; + } + + /* Change cursor surface */ + motionnotify(0, NULL, 0, 0, 0, 0); + + /* Have a client, so focus its top-level wlr_surface */ + client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + + /* Activate the new client */ + client_activate_surface(client_surface(c), 1); +} + +void +focusmon(const Arg *arg) +{ + int i = 0, nmons = wl_list_length(&mons); + if (nmons) { + do /* don't switch to disabled mons */ + selmon = dirtomon(arg->i); + while (!selmon->wlr_output->enabled && i++ < nmons); + } + focusclient(focustop(selmon), 1); +} + +void +focusstack(const Arg *arg) +{ + /* Focus the next or previous client (in tiling order) on selmon */ + Client *c, *sel = focustop(selmon); + if (!sel || (sel->isfullscreen && !client_has_children(sel))) + return; + if (arg->i > 0) { + wl_list_for_each(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } else { + wl_list_for_each_reverse(c, &sel->link, link) { + if (&c->link == &clients) + continue; /* wrap past the sentinel node */ + if (VISIBLEON(c, selmon)) + break; /* found it */ + } + } + /* If only one client is visible on selmon, then c == sel */ + focusclient(c, 1); +} + +/* We probably should change the name of this, it sounds like + * will focus the topmost client of this mon, when actually will + * only return that client */ +Client * +focustop(Monitor *m) +{ + Client *c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m)) + return c; + } + return NULL; +} + +void +fullscreennotify(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, fullscreen); + setfullscreen(c, client_wants_fullscreen(c)); +} + +void +gpureset(struct wl_listener *listener, void *data) +{ + struct wlr_renderer *old_drw = drw; + struct wlr_allocator *old_alloc = alloc; + struct Monitor *m; + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't recreate renderer"); + + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't recreate allocator"); + + LISTEN_STATIC(&drw->events.lost, gpureset); + + wlr_compositor_set_renderer(compositor, drw); + + wl_list_for_each(m, &mons, link) { + wlr_output_init_render(m->wlr_output, alloc, drw); + } + + wlr_allocator_destroy(old_alloc); + wlr_renderer_destroy(old_drw); +} + +void +handlesig(int signo) +{ + if (signo == SIGCHLD) { + siginfo_t in; + /* wlroots expects to reap the XWayland process itself, so we + * use WNOWAIT to keep the child waitable until we know it's not + * XWayland. + */ + while (!waitid(P_ALL, 0, &in, WEXITED|WNOHANG|WNOWAIT) && in.si_pid +#ifdef XWAYLAND + && (!xwayland || in.si_pid != xwayland->server->pid) +#endif + ) { + pid_t *p, *lim; + waitpid(in.si_pid, NULL, 0); + if (in.si_pid == child_pid) + child_pid = -1; + if (!(p = autostart_pids)) + continue; + lim = &p[autostart_len]; + + for (; p < lim; p++) { + if (*p == in.si_pid) { + *p = -1; + break; + } + } + } + } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); + } +} + +void +incnmaster(const Arg *arg) +{ + if (!arg || !selmon) + return; + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +inputdevice(struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct wlr_input_device *device = data; + uint32_t caps; + + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + createkeyboard(wlr_keyboard_from_input_device(device)); + break; + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; + default: + /* TODO handle other input device types */ + break; + } + + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In dwl we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + /* TODO do we actually require a cursor? */ + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(seat, caps); +} + +int +keybinding(uint32_t mods, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + */ + const Key *k; + for (k = keys; k < END(keys); k++) { + if (CLEANMASK(mods) == CLEANMASK(k->mod) + && sym == k->keysym && k->func) { + k->func(&k->arg); + return 1; + } + } + return 0; +} + +void +keypress(struct wl_listener *listener, void *data) +{ + int i; + /* This event is raised when a key is pressed or released. */ + KeyboardGroup *group = wl_container_of(listener, group, key); + struct wlr_keyboard_key_event *event = data; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms( + group->wlr_group->keyboard.xkb_state, keycode, &syms); + + int handled = 0; + uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard); + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* On _press_ if there is no active screen locker, + * attempt to process a compositor keybinding. */ + if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for (i = 0; i < nsyms; i++) + handled = keybinding(mods, syms[i]) || handled; + } + + if (handled && group->wlr_group->keyboard.repeat_info.delay > 0) { + group->mods = mods; + group->keysyms = syms; + group->nsyms = nsyms; + wl_event_source_timer_update(group->key_repeat_source, + group->wlr_group->keyboard.repeat_info.delay); + } else { + group->nsyms = 0; + wl_event_source_timer_update(group->key_repeat_source, 0); + } + + if (handled) + return; + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, + event->keycode, event->state); +} + +void +keypressmod(struct wl_listener *listener, void *data) +{ + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + KeyboardGroup *group = wl_container_of(listener, group, modifiers); + + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); +} + +int +keyrepeat(void *data) +{ + KeyboardGroup *group = data; + int i; + if (!group->nsyms || group->wlr_group->keyboard.repeat_info.rate <= 0) + return 0; + + wl_event_source_timer_update(group->key_repeat_source, + 1000 / group->wlr_group->keyboard.repeat_info.rate); + + for (i = 0; i < group->nsyms; i++) + keybinding(group->mods, group->keysyms[i]); + + return 0; +} + +void +killclient(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + client_send_close(sel); +} + +void +locksession(struct wl_listener *listener, void *data) +{ + struct wlr_session_lock_v1 *session_lock = data; + SessionLock *lock; + wlr_scene_node_set_enabled(&locked_bg->node, 1); + if (cur_lock) { + wlr_session_lock_v1_destroy(session_lock); + return; + } + lock = session_lock->data = ecalloc(1, sizeof(*lock)); + focusclient(NULL, 0); + + lock->scene = wlr_scene_tree_create(layers[LyrBlock]); + cur_lock = lock->lock = session_lock; + locked = 1; + + LISTEN(&session_lock->events.new_surface, &lock->new_surface, createlocksurface); + LISTEN(&session_lock->events.destroy, &lock->destroy, destroysessionlock); + LISTEN(&session_lock->events.unlock, &lock->unlock, unlocksession); + + wlr_session_lock_v1_send_locked(session_lock); +} + +void +mapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is mapped, or ready to display on-screen. */ + Client *p = NULL; + Client *w, *c = wl_container_of(listener, c, map); + Monitor *m; + int i; + + /* Create scene tree for this client and its border */ + c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); + wlr_scene_node_set_enabled(&c->scene->node, c->type != XDGShell); + c->scene_surface = c->type == XDGShell + ? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg) + : wlr_scene_subsurface_tree_create(c->scene, client_surface(c)); + c->scene->node.data = c->scene_surface->node.data = c; + + client_get_geometry(c, &c->geom); + + /* Handle unmanaged clients first so we can return prior create borders */ + if (client_is_unmanaged(c)) { + /* Unmanaged clients always are floating */ + wlr_scene_node_reparent(&c->scene->node, layers[LyrFloat]); + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + if (client_wants_focus(c)) { + focusclient(c, 1); + exclusive_focus = c; + } + goto unset_fullscreen; + } + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, + (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + + /* Initialize client geometry with room for border */ + client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + c->geom.width += 2 * c->bw; + c->geom.height += 2 * c->bw; + + /* Insert this client into client lists. */ + Client* focused = focustop(selmon); + if (focused) + wl_list_insert(&focused->link, &c->link); + else + wl_list_insert(&clients, &c->link); + wl_list_insert(&fstack, &c->flink); + + /* Set initial monitor, tags, floating status, and focus: + * we always consider floating, clients that have parent and thus + * we set the same tags and monitor than its parent, if not + * try to apply rules for them */ + if ((p = client_get_parent(c))) { + c->isfloating = 1; + setmon(c, p->mon, p->tags); + } else { + applyrules(c); + } + drawbars(); + +unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); + wl_list_for_each(w, &clients, link) { + if (w != c && w != p && w->isfullscreen && m == w->mon && (w->tags & c->tags)) + setfullscreen(w, 0); + } +} + +void +maximizenotify(struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. dwl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * Since xdg-shell protocol v5 we should ignore request of unsupported + * capabilities, just schedule a empty configure when the client uses <5 + * protocol version + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + Client *c = wl_container_of(listener, c, maximize); + if (c->surface.xdg->initialized + && wl_resource_get_version(c->surface.xdg->toplevel->resource) + < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) + wlr_xdg_surface_schedule_configure(c->surface.xdg); +} + +void +monocle(Monitor *m) +{ + Client *c; + int n = 0; + + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + resize(c, m->w, 0); + n++; + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); + if ((c = focustop(m))) + wlr_scene_node_raise_to_top(&c->scene->node); +} + +void +motionabsolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct wlr_pointer_motion_absolute_event *event = data; + double lx, ly, dx, dy; + + if (!event->time_msec) /* this is 0 with virtual pointers */ + wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y); + + wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly); + dx = lx - cursor->x; + dy = ly - cursor->y; + motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy); +} + +void +motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) +{ + double sx = 0, sy = 0, sx_confined, sy_confined; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; + struct wlr_pointer_constraint_v1 *constraint; + + /* Find the client under the pointer and send the event along. */ + xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); + + if (cursor_mode == CurPressed && !seat->drag + && surface != seat->pointer_state.focused_surface + && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { + c = w; + surface = seat->pointer_state.focused_surface; + sx = cursor->x - (l ? l->geom.x : w->geom.x); + sy = cursor->y - (l ? l->geom.y : w->geom.y); + } + + /* time is 0 in internal calls meant to restore pointer focus. */ + if (time) { + wlr_relative_pointer_manager_v1_send_relative_motion( + relative_pointer_mgr, seat, (uint64_t)time * 1000, + dx, dy, dx_unaccel, dy_unaccel); + + wl_list_for_each(constraint, &pointer_constraints->constraints, link) + cursorconstrain(constraint); + + if (active_constraint && cursor_mode != CurResize && cursor_mode != CurMove) { + toplevel_from_wlr_surface(active_constraint->surface, &c, NULL); + if (c && active_constraint->surface == seat->pointer_state.focused_surface) { + sx = cursor->x - c->geom.x - c->bw; + sy = cursor->y - c->geom.y - c->bw; + if (wlr_region_confine(&active_constraint->region, sx, sy, + sx + dx, sy + dy, &sx_confined, &sy_confined)) { + dx = sx_confined - sx; + dy = sy_confined - sy; + } + + if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + return; + } + } + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) + selmon = xytomon(cursor->x, cursor->y); + } + + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ + resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { + resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, + .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); + return; + } + + /* 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) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +} + +void +motionrelative(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct wlr_pointer_motion_event *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y, + event->unaccel_dx, event->unaccel_dy); +} + +void +moveresize(const Arg *arg) +{ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL); + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + + /* Float the window and tell motionnotify to grab it */ + setfloating(grabc, 1); + switch (cursor_mode = arg->ui) { + case CurMove: + grabcx = (int)round(cursor->x) - grabc->geom.x; + grabcy = (int)round(cursor->y) - grabc->geom.y; + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: + /* Doesn't work for X11 output - the next absolute motion event + * returns the cursor to where it started */ + wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); + break; + } +} + +void +outputmgrapply(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 0); +} + +void +outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) +{ + /* + * Called when a client such as wlr-randr requests a change in output + * configuration. This is only one way that the layout can be changed, + * so any Monitor information should be updated by updatemons() after an + * output_layout.change event, not here. + */ + struct wlr_output_configuration_head_v1 *config_head; + int ok = 1; + + wl_list_for_each(config_head, &config->heads, link) { + struct wlr_output *wlr_output = config_head->state.output; + Monitor *m = wlr_output->data; + struct wlr_output_state state; + + /* Ensure displays previously disabled by wlr-output-power-management-v1 + * are properly handled*/ + m->asleep = 0; + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, config_head->state.enabled); + if (!config_head->state.enabled) + goto apply_or_test; + + if (config_head->state.mode) + wlr_output_state_set_mode(&state, config_head->state.mode); + else + wlr_output_state_set_custom_mode(&state, + config_head->state.custom_mode.width, + config_head->state.custom_mode.height, + config_head->state.custom_mode.refresh); + + wlr_output_state_set_transform(&state, config_head->state.transform); + wlr_output_state_set_scale(&state, config_head->state.scale); + wlr_output_state_set_adaptive_sync_enabled(&state, + config_head->state.adaptive_sync_enabled); + +apply_or_test: + ok &= test ? wlr_output_test_state(wlr_output, &state) + : wlr_output_commit_state(wlr_output, &state); + + /* Don't move monitors if position wouldn't change, this to avoid + * wlroots marking the output as manually configured. + * wlr_output_layout_add does not like disabled outputs */ + if (!test && wlr_output->enabled && (m->m.x != config_head->state.x || m->m.y != config_head->state.y)) + wlr_output_layout_add(output_layout, wlr_output, + config_head->state.x, config_head->state.y); + + wlr_output_state_finish(&state); + } + + if (ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); + + /* https://codeberg.org/dwl/dwl/issues/577 */ + updatemons(NULL, NULL); +} + +void +outputmgrtest(struct wl_listener *listener, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + outputmgrapplyortest(config, 1); +} + +void +pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + uint32_t time) +{ + struct timespec now; + + if (surface != seat->pointer_state.focused_surface && + sloppyfocus && time && c && !client_is_unmanaged(c)) + focusclient(c, 0); + + /* If surface is NULL, clear pointer focus */ + if (!surface) { + wlr_seat_pointer_notify_clear_focus(seat); + return; + } + + if (!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + /* Let the client know that the mouse cursor has entered one + * of its surfaces, and make keyboard focus follow if desired. + * wlroots makes this a no-op if surface is already focused */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(seat, time, sx, sy); +} + + +void +powermgrsetmode(struct wl_listener *listener, void *data) +{ + struct wlr_output_power_v1_set_mode_event *event = data; + struct wlr_output_state state = {0}; + Monitor *m = event->output->data; + + if (!m) + return; + + m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the ouput */ + wlr_output_state_set_enabled(&state, event->mode); + wlr_output_commit_state(m->wlr_output, &state); + + m->asleep = !event->mode; +} + +void +quit(const Arg *arg) +{ + wl_display_terminate(dpy); +} + +void +rendermon(struct wl_listener *listener, void *data) +{ + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + Monitor *m = wl_container_of(listener, m, frame); + Client *c; + struct wlr_output_state pending = {0}; + struct wlr_gamma_control_v1 *gamma_control; + struct timespec now; + + /* Render if no XDG clients have an outstanding resize and are visible on + * this monitor. */ + wl_list_for_each(c, &clients, link) { + if (c->resize && !c->isfloating && client_is_rendered_on_mon(c, m) && !client_is_stopped(c)) + goto skip; + } + + /* + * HACK: The "correct" way to set the gamma is to commit it together with + * the rest of the state in one go, but to do that we would need to rewrite + * wlr_scene_output_commit() in order to add the gamma to the pending + * state before committing, instead try to commit the gamma in one frame, + * and commit the rest of the state in the next one (or in the same frame if + * the gamma can not be committed). + */ + if (m->gamma_lut_changed) { + gamma_control + = wlr_gamma_control_manager_v1_get_control(gamma_control_mgr, m->wlr_output); + m->gamma_lut_changed = 0; + + if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) + goto commit; + + if (!wlr_output_test_state(m->wlr_output, &pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); + goto commit; + } + wlr_output_commit_state(m->wlr_output, &pending); + wlr_output_schedule_frame(m->wlr_output); + } else { +commit: + wlr_scene_output_commit(m->scene_output, NULL); + } + +skip: + /* Let clients know a frame has been rendered */ + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(m->scene_output, &now); + wlr_output_state_finish(&pending); +} + +void +requestdecorationmode(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_decoration_mode); + if (c->surface.xdg->initialized) + wlr_xdg_toplevel_decoration_v1_set_mode(c->decoration, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); +} + +void +requeststartdrag(struct wl_listener *listener, void *data) +{ + struct wlr_seat_request_start_drag_event *event = data; + + if (wlr_seat_validate_pointer_grab_serial(seat, event->origin, + event->serial)) + wlr_seat_start_pointer_drag(seat, event->drag, event->serial); + else + wlr_data_source_destroy(event->drag->source); +} + +void +requestmonstate(struct wl_listener *listener, void *data) +{ + struct wlr_output_event_request_state *event = data; + wlr_output_commit_state(event->output, event->state); + updatemons(NULL, NULL); +} + +void +resize(Client *c, struct wlr_box geo, int interact) +{ + struct wlr_box *bbox; + struct wlr_box clip; + + if (!c->mon || !client_surface(c)->mapped) + return; + + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); + c->geom = geo; + applybounds(c, bbox); + + /* Update scene-graph, including borders */ + wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y); + wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw); + wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[1], c->geom.width, c->bw); + wlr_scene_rect_set_size(c->border[2], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_rect_set_size(c->border[3], c->bw, c->geom.height - 2 * c->bw); + wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw); + wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw); + wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); + + /* this is a no-op if size hasn't changed */ + c->resize = client_set_size(c, c->geom.width - 2 * c->bw, + c->geom.height - 2 * c->bw); + client_get_clip(c, &clip); + wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip); +} + +void +run(char *startup_cmd) +{ + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto(dpy); + if (!socket) + die("startup: display_add_socket_auto"); + setenv("WAYLAND_DISPLAY", socket, 1); + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start(backend)) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ + autostartexec(); + if (startup_cmd) { + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { + close(STDIN_FILENO); + setsid(); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } + } + + drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ + selmon = xytomon(cursor->x, cursor->y); + + /* TODO hack to get cursor to display in its initial location (100, 100) + * instead of (0, 0) and then jumping. still may not be fully + * initialized, as the image/coordinates are not transformed for the + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + wl_display_run(dpy); +} + +void +setcursor(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + /* If we're "grabbing" the cursor, don't use the client's image, we will + * restore it after "grabbing" sending a leave event, followed by a enter + * event, which will result in the client requesting set the cursor surface */ + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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 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); +} + +void +setcursorshape(struct wl_listener *listener, void *data) +{ + struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; + if (cursor_mode != CurNormal && cursor_mode != CurPressed) + return; + /* 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)); +} + +void +setfloating(Client *c, int floating) +{ + Client *p = client_get_parent(c); + c->isfloating = floating; + /* If in floating layout do not change the client's layer */ + if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange) + return; + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); + drawbars(); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + c->isfullscreen = fullscreen; + if (!c->mon || !client_surface(c)->mapped) + return; + c->bw = fullscreen ? 0 : borderpx; + client_set_fullscreen(c, fullscreen); + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen + ? LyrFS : c->isfloating ? LyrFloat : LyrTile]); + + if (fullscreen) { + c->prev = c->geom; + resize(c, c->mon->m, 0); + } else { + /* restore previous size instead of arrange for floating windows since + * client positions are set by the user and cannot be recalculated */ + resize(c, c->prev, 0); + } + arrange(c->mon); + drawbars(); +} + +void +setgamma(struct wl_listener *listener, void *data) +{ + struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + Monitor *m = event->output->data; + if (!m) + return; + m->gamma_lut_changed = 1; + wlr_output_schedule_frame(m->wlr_output); +} + +void +setlayout(const Arg *arg) +{ + if (!selmon) + return; + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); + arrange(selmon); + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0f ? arg->f + selmon->mfact : arg->f - 1.0f; + if (f < 0.1 || f > 0.9) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setmon(Client *c, Monitor *m, uint32_t newtags) +{ + Monitor *oldmon = c->mon; + + if (oldmon == m) + return; + c->mon = m; + c->prev = c->geom; + + /* Scene graph sends surface leave/enter events on move and resize */ + if (oldmon) + arrange(oldmon); + if (m) { + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } + focusclient(focustop(selmon), 1); +} + +void +setpsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_primary_selection_event *event = data; + wlr_seat_set_primary_selection(seat, event->source, event->serial); +} + +void +setsel(struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in dwl we always honor + */ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(seat, event->source, event->serial); +} + +void +setup(void) +{ + int i, sig[] = {SIGCHLD, SIGINT, SIGTERM, SIGPIPE}; + struct sigaction sa = {.sa_flags = SA_RESTART, .sa_handler = handlesig}; + sigemptyset(&sa.sa_mask); + + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + + + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + dpy = wl_display_create(); + event_loop = wl_display_get_event_loop(dpy); + + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + if (!(backend = wlr_backend_autocreate(event_loop, &session))) + die("couldn't create backend"); + + /* Initialize the scene graph used to lay out windows */ + scene = wlr_scene_create(); + root_bg = wlr_scene_rect_create(&scene->tree, 0, 0, rootcolor); + for (i = 0; i < NUM_LAYERS; i++) + layers[i] = wlr_scene_tree_create(&scene->tree); + drag_icon = wlr_scene_tree_create(&scene->tree); + wlr_scene_node_place_below(&drag_icon->node, &layers[LyrBlock]->node); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + if (!(drw = wlr_renderer_autocreate(backend))) + die("couldn't create renderer"); + LISTEN_STATIC(&drw->events.lost, gpureset); + + /* Create shm, drm and linux_dmabuf interfaces by ourselves. + * The simplest way is call: + * wlr_renderer_init_wl_display(drw); + * but we need to create manually the linux_dmabuf interface to integrate it + * with wlr_scene. */ + wlr_renderer_init_wl_shm(drw, dpy); + + if (wlr_renderer_get_texture_formats(drw, WLR_BUFFER_CAP_DMABUF)) { + wlr_drm_create(dpy, drw); + wlr_scene_set_linux_dmabuf_v1(scene, + wlr_linux_dmabuf_v1_create_with_renderer(dpy, 5, drw)); + } + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + if (!(alloc = wlr_allocator_autocreate(backend, drw))) + die("couldn't create allocator"); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the setsel() function. */ + compositor = wlr_compositor_create(dpy, 6, drw); + wlr_subcompositor_create(dpy); + wlr_data_device_manager_create(dpy); + wlr_export_dmabuf_manager_v1_create(dpy); + wlr_screencopy_manager_v1_create(dpy); + wlr_data_control_manager_v1_create(dpy); + wlr_primary_selection_v1_device_manager_create(dpy); + wlr_viewporter_create(dpy); + wlr_single_pixel_buffer_manager_v1_create(dpy); + wlr_fractional_scale_manager_v1_create(dpy, 1); + wlr_presentation_create(dpy, backend); + wlr_alpha_modifier_v1_create(dpy); + + /* Initializes the interface used to implement urgency hints */ + activation = wlr_xdg_activation_v1_create(dpy); + LISTEN_STATIC(&activation->events.request_activate, urgent); + + gamma_control_mgr = wlr_gamma_control_manager_v1_create(dpy); + LISTEN_STATIC(&gamma_control_mgr->events.set_gamma, setgamma); + + power_mgr = wlr_output_power_manager_v1_create(dpy); + LISTEN_STATIC(&power_mgr->events.set_mode, powermgrsetmode); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + output_layout = wlr_output_layout_create(dpy); + LISTEN_STATIC(&output_layout->events.change, updatemons); + wlr_xdg_output_manager_v1_create(dpy, output_layout); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init(&mons); + LISTEN_STATIC(&backend->events.new_output, createmon); + + /* Set up our client lists, the xdg-shell and the layer-shell. The xdg-shell is a + * Wayland protocol which is used for application windows. For more + * detail on shells, refer to the article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init(&clients); + wl_list_init(&fstack); + + xdg_shell = wlr_xdg_shell_create(dpy, 6); + LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify); + LISTEN_STATIC(&xdg_shell->events.new_popup, createpopup); + + layer_shell = wlr_layer_shell_v1_create(dpy, 3); + LISTEN_STATIC(&layer_shell->events.new_surface, createlayersurface); + + idle_notifier = wlr_idle_notifier_v1_create(dpy); + + idle_inhibit_mgr = wlr_idle_inhibit_v1_create(dpy); + LISTEN_STATIC(&idle_inhibit_mgr->events.new_inhibitor, createidleinhibitor); + + session_lock_mgr = wlr_session_lock_manager_v1_create(dpy); + wl_signal_add(&session_lock_mgr->events.new_lock, &lock_listener); + LISTEN_STATIC(&session_lock_mgr->events.destroy, destroysessionmgr); + locked_bg = wlr_scene_rect_create(layers[LyrBlock], sgeom.width, sgeom.height, + (float [4]){0.1f, 0.1f, 0.1f, 1.0f}); + wlr_scene_node_set_enabled(&locked_bg->node, 0); + + /* Use decoration protocols to negotiate server-side decorations */ + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(dpy), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); + xdg_decoration_mgr = wlr_xdg_decoration_manager_v1_create(dpy); + LISTEN_STATIC(&xdg_decoration_mgr->events.new_toplevel_decoration, createdecoration); + + pointer_constraints = wlr_pointer_constraints_v1_create(dpy); + LISTEN_STATIC(&pointer_constraints->events.new_constraint, createpointerconstraint); + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + cursor = wlr_cursor_create(); + wlr_cursor_attach_output_layout(cursor, output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). Scaled cursors will be loaded with each output. */ + cursor_mgr = wlr_xcursor_manager_create(NULL, 24); + setenv("XCURSOR_SIZE", "24", 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + LISTEN_STATIC(&cursor->events.motion, motionrelative); + LISTEN_STATIC(&cursor->events.motion_absolute, motionabsolute); + LISTEN_STATIC(&cursor->events.button, buttonpress); + LISTEN_STATIC(&cursor->events.axis, axisnotify); + LISTEN_STATIC(&cursor->events.frame, cursorframe); + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + LISTEN_STATIC(&backend->events.new_input, inputdevice); + virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_keyboard_mgr->events.new_virtual_keyboard, virtualkeyboard); + virtual_pointer_mgr = wlr_virtual_pointer_manager_v1_create(dpy); + LISTEN_STATIC(&virtual_pointer_mgr->events.new_virtual_pointer, virtualpointer); + + seat = wlr_seat_create(dpy, "seat0"); + LISTEN_STATIC(&seat->events.request_set_cursor, setcursor); + LISTEN_STATIC(&seat->events.request_set_selection, setsel); + LISTEN_STATIC(&seat->events.request_set_primary_selection, setpsel); + LISTEN_STATIC(&seat->events.request_start_drag, requeststartdrag); + LISTEN_STATIC(&seat->events.start_drag, startdrag); + + kb_group = createkeyboardgroup(); + wl_list_init(&kb_group->destroy.link); + + output_mgr = wlr_output_manager_v1_create(dpy); + LISTEN_STATIC(&output_mgr->events.apply, outputmgrapply); + LISTEN_STATIC(&output_mgr->events.test, outputmgrtest); + + drwl_init(); + + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ + unsetenv("DISPLAY"); +#ifdef XWAYLAND + /* + * Initialise the XWayland X server. + * It will be started when the first X client is started. + */ + if ((xwayland = wlr_xwayland_create(dpy, compositor, 1))) { + LISTEN_STATIC(&xwayland->events.ready, xwaylandready); + LISTEN_STATIC(&xwayland->events.new_surface, createnotifyx11); + + setenv("DISPLAY", xwayland->display_name, 1); + } else { + fprintf(stderr, "failed to setup XWayland X server, continuing without it\n"); + } +#endif +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwl: execvp %s failed:", ((char **)arg->v)[0]); + } +} + +void +startdrag(struct wl_listener *listener, void *data) +{ + struct wlr_drag *drag = data; + if (!drag->icon) + return; + + drag->icon->data = &wlr_scene_drag_icon_create(drag_icon, drag->icon)->node; + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); +} + +int +statusin(int fd, unsigned int mask, void *data) +{ + char status[1024]; + ssize_t n; + + if (mask & WL_EVENT_ERROR) + die("status in event error"); + if (mask & WL_EVENT_HANGUP) + wl_event_source_remove(status_event_source); + + n = read(fd, status, sizeof(status) - 1); + if (n < 0 && errno != EWOULDBLOCK) + die("read:"); + + status[n] = '\0'; + status[strcspn(status, "\n")] = '\0'; + + strncpy(stext, status, sizeof(stext)); + drawbars(); + + return 0; +} + +void +tag(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (!sel || (arg->ui & TAGMASK) == 0) + return; + + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +tagmon(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setmon(sel, dirtomon(arg->i), 0); +} + +void +tile(Monitor *m) +{ + unsigned int h, r, e = m->gaps, mw, my, ty; + int i, n = 0; + Client *c; + + wl_list_for_each(c, &clients, link) + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) + n++; + if (n == 0) + return; + if (smartgaps == n) + e = 0; + + if (n > m->nmaster) + mw = m->nmaster ? (int)roundf((m->w.width + gappx*e) * m->mfact) : 0; + else + mw = m->w.width; + i = 0; + my = ty = gappx*e; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->w.height - my - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + gappx*e, .y = m->w.y + my, + .width = mw - 2*gappx*e, .height = h}, 0); + my += c->geom.height + gappx*e; + } else { + r = n - i; + h = (m->w.height - ty - gappx*e - gappx*e * (r - 1)) / r; + resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, + .width = m->w.width - mw - gappx*e, .height = h}, 0); + ty += c->geom.height + gappx*e; + } + i++; + } +} + +void +togglebar(const Arg *arg) +{ + wlr_scene_node_set_enabled(&selmon->scene_buffer->node, + !selmon->scene_buffer->node.enabled); + arrangelayers(selmon); +} + +void +togglefloating(const Arg *arg) +{ + Client *sel = focustop(selmon); + /* return if fullscreen */ + if (sel && !sel->isfullscreen) + setfloating(sel, !sel->isfloating); +} + +void +togglefullscreen(const Arg *arg) +{ + Client *sel = focustop(selmon); + if (sel) + setfullscreen(sel, !sel->isfullscreen); +} + +void +togglegaps(const Arg *arg) +{ + selmon->gaps = !selmon->gaps; + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + uint32_t newtags; + Client *sel = focustop(selmon); + if (!sel || !(newtags = sel->tags ^ (arg->ui & TAGMASK))) + return; + + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +toggleview(const Arg *arg) +{ + uint32_t newtagset; + if (!(newtagset = selmon ? selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK) : 0)) + return; + + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +unlocksession(struct wl_listener *listener, void *data) +{ + SessionLock *lock = wl_container_of(listener, lock, unlock); + destroylock(lock, 1); +} + +void +unmaplayersurfacenotify(struct wl_listener *listener, void *data) +{ + LayerSurface *l = wl_container_of(listener, l, unmap); + + l->mapped = 0; + wlr_scene_node_set_enabled(&l->scene->node, 0); + if (l == exclusive_focus) + exclusive_focus = NULL; + if (l->layer_surface->output && (l->mon = l->layer_surface->output->data)) + arrangelayers(l->mon); + if (l->layer_surface->surface == seat->keyboard_state.focused_surface) + focusclient(focustop(selmon), 1); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +unmapnotify(struct wl_listener *listener, void *data) +{ + /* Called when the surface is unmapped, and should no longer be shown. */ + Client *c = wl_container_of(listener, c, unmap); + if (c == grabc) { + cursor_mode = CurNormal; + grabc = NULL; + } + + if (client_is_unmanaged(c)) { + if (c == exclusive_focus) { + exclusive_focus = NULL; + focusclient(focustop(selmon), 1); + } + } else { + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); + } + + wlr_scene_node_destroy(&c->scene->node); + drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); +} + +void +updatemons(struct wl_listener *listener, void *data) +{ + /* + * Called whenever the output layout changes: adding or removing a + * monitor, changing an output's mode or position, etc. This is where + * the change officially happens and we update geometry, window + * positions, focus, and the stored configuration in wlroots' + * output-manager implementation. + */ + struct wlr_output_configuration_v1 *config + = wlr_output_configuration_v1_create(); + Client *c; + struct wlr_output_configuration_head_v1 *config_head; + Monitor *m; + + /* First remove from the layout the disabled monitors */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled || m->asleep) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + config_head->state.enabled = 0; + /* Remove this output from the layout to avoid cursor enter inside it */ + wlr_output_layout_remove(output_layout, m->wlr_output); + closemon(m); + m->m = m->w = (struct wlr_box){0}; + } + /* Insert outputs that need to */ + wl_list_for_each(m, &mons, link) { + if (m->wlr_output->enabled + && !wlr_output_layout_get(output_layout, m->wlr_output)) + wlr_output_layout_add_auto(output_layout, m->wlr_output); + } + + /* Now that we update the output layout we can get its box */ + wlr_output_layout_get_box(output_layout, NULL, &sgeom); + + wlr_scene_node_set_position(&root_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(root_bg, sgeom.width, sgeom.height); + + /* Make sure the clients are hidden when dwl is locked */ + wlr_scene_node_set_position(&locked_bg->node, sgeom.x, sgeom.y); + wlr_scene_rect_set_size(locked_bg, sgeom.width, sgeom.height); + + wl_list_for_each(m, &mons, link) { + if (!m->wlr_output->enabled) + continue; + config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output); + + /* Get the effective monitor geometry to use for surfaces */ + wlr_output_layout_get_box(output_layout, m->wlr_output, &m->m); + m->w = m->m; + wlr_scene_output_set_position(m->scene_output, m->m.x, m->m.y); + + wlr_scene_node_set_position(&m->fullscreen_bg->node, m->m.x, m->m.y); + wlr_scene_rect_set_size(m->fullscreen_bg, m->m.width, m->m.height); + + if (m->lock_surface) { + struct wlr_scene_tree *scene_tree = m->lock_surface->surface->data; + wlr_scene_node_set_position(&scene_tree->node, m->m.x, m->m.y); + wlr_session_lock_surface_v1_configure(m->lock_surface, m->m.width, m->m.height); + } + + /* Calculate the effective monitor geometry to use for clients */ + arrangelayers(m); + /* Don't move clients to the left output when plugging monitors */ + arrange(m); + /* make sure fullscreen clients have the right size */ + if ((c = focustop(m)) && c->isfullscreen) + resize(c, m->m, 0); + + /* Try to re-set the gamma LUT when updating monitors, + * it's only really needed when enabling a disabled output, but meh. */ + m->gamma_lut_changed = 1; + + config_head->state.x = m->m.x; + config_head->state.y = m->m.y; + + if (!selmon) { + selmon = m; + } + } + + if (selmon && selmon->wlr_output->enabled) { + wl_list_for_each(c, &clients, link) { + if (!c->mon && client_surface(c)->mapped) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); + if (selmon->lock_surface) { + client_notify_enter(selmon->lock_surface->surface, + wlr_seat_get_keyboard(seat)); + client_activate_surface(selmon->lock_surface->surface, 1); + } + } + + if (stext[0] == '\0') + strncpy(stext, "dwl-"VERSION, sizeof(stext)); + wl_list_for_each(m, &mons, link) { + updatebar(m); + drawbar(m); + } + + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a + * wl_pointer.motion event for the clients, it's only the image what it's + * at the wrong position after all. */ + wlr_cursor_move(cursor, NULL, 0, 0); + + wlr_output_manager_v1_set_configuration(output_mgr, config); +} + +void +updatebar(Monitor *m) +{ + size_t i; + int rw, rh; + char fontattrs[12]; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; + m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); + + wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); + + for (i = 0; i < LENGTH(m->pool); i++) + if (m->pool[i]) { + wlr_buffer_drop(&m->pool[i]->base); + m->pool[i] = NULL; + } + + if (m->b.scale == m->wlr_output->scale && m->drw) + return; + + drwl_font_destroy(m->drw->font); + snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); + if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) + die("Could not load font"); + + m->b.scale = m->wlr_output->scale; + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); +} + +void +updatetitle(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) + drawbars(); +} + +void +urgent(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *c = NULL; + toplevel_from_wlr_surface(event->surface, &c, NULL); + if (!c || c == focustop(selmon)) + return; + + c->isurgent = 1; + drawbars(); + + if (client_surface(c)->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +view(const Arg *arg) +{ + if (!selmon || (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); + drawbars(); +} + +void +virtualkeyboard(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_keyboard_v1 *kb = data; + /* virtual keyboards shouldn't share keyboard group */ + KeyboardGroup *group = createkeyboardgroup(); + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(&kb->keyboard, group->wlr_group->keyboard.keymap); + LISTEN(&kb->keyboard.base.events.destroy, &group->destroy, destroykeyboardgroup); + + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(group->wlr_group, &kb->keyboard); +} + +void +virtualpointer(struct wl_listener *listener, void *data) +{ + struct wlr_virtual_pointer_v1_new_pointer_event *event = data; + struct wlr_input_device *device = &event->new_pointer->pointer.base; + + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); +} + +Monitor * +xytomon(double x, double y) +{ + struct wlr_output *o = wlr_output_layout_output_at(output_layout, x, y); + return o ? o->data : NULL; +} + +void +xytonode(double x, double y, struct wlr_surface **psurface, + Client **pc, LayerSurface **pl, double *nx, double *ny) +{ + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; + struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; + + for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + + if (node->type == WLR_SCENE_NODE_BUFFER) { + scene_surface = wlr_scene_surface_try_from_buffer( + wlr_scene_buffer_from_node(node)); + if (!scene_surface) continue; + surface = scene_surface->surface; + } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; + if (c && c->type == LayerShell) { + c = NULL; + l = pnode->data; + } + } + + if (psurface) *psurface = surface; + if (pc) *pc = c; + if (pl) *pl = l; +} + +void +zoom(const Arg *arg) +{ + Client *c, *sel = focustop(selmon); + + if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange || sel->isfloating) + return; + + /* Search for the first tiled window that is not sel, marking sel as + * NULL if we pass it along the way */ + wl_list_for_each(c, &clients, link) { + if (VISIBLEON(c, selmon) && !c->isfloating) { + if (c != sel) + break; + sel = NULL; + } + } + + /* Return if no other tiled window was found */ + if (&c->link == &clients) + return; + + /* If we passed sel, move c to the front; otherwise, move sel to the + * front */ + if (!sel) + sel = c; + wl_list_remove(&sel->link); + wl_list_insert(&clients, &sel->link); + + focusclient(sel, 1); + arrange(selmon); +} + +#ifdef XWAYLAND +void +activatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, activate); + + /* Only "managed" windows can be activated */ + if (!client_is_unmanaged(c)) + wlr_xwayland_surface_activate(c->surface.xwayland, 1); +} + +void +associatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, associate); + + LISTEN(&client_surface(c)->events.map, &c->map, mapnotify); + LISTEN(&client_surface(c)->events.unmap, &c->unmap, unmapnotify); +} + +void +configurex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, configure); + struct wlr_xwayland_surface_configure_event *event = data; + /* TODO: figure out if there is another way to do this */ + if (!c->mon) { + wlr_xwayland_surface_configure(c->surface.xwayland, + event->x, event->y, event->width, event->height); + return; + } + if (c->isfloating || client_is_unmanaged(c)) + resize(c, (struct wlr_box){.x = event->x, .y = event->y, + .width = event->width + c->bw * 2, .height = event->height + c->bw * 2}, 0); + else + arrange(c->mon); +} + +void +createnotifyx11(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xsurface = data; + Client *c; + + /* Allocate a Client for this surface */ + c = xsurface->data = ecalloc(1, sizeof(*c)); + c->surface.xwayland = xsurface; + c->type = X11; + c->bw = client_is_unmanaged(c) ? 0 : borderpx; + + /* Listen to the various events it can emit */ + LISTEN(&xsurface->events.associate, &c->associate, associatex11); + LISTEN(&xsurface->events.destroy, &c->destroy, destroynotify); + LISTEN(&xsurface->events.dissociate, &c->dissociate, dissociatex11); + LISTEN(&xsurface->events.request_activate, &c->activate, activatex11); + LISTEN(&xsurface->events.request_configure, &c->configure, configurex11); + LISTEN(&xsurface->events.request_fullscreen, &c->fullscreen, fullscreennotify); + LISTEN(&xsurface->events.set_hints, &c->set_hints, sethints); + LISTEN(&xsurface->events.set_title, &c->set_title, updatetitle); +} + +void +dissociatex11(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, dissociate); + wl_list_remove(&c->map.link); + wl_list_remove(&c->unmap.link); +} + +xcb_atom_t +getatom(xcb_connection_t *xc, const char *name) +{ + xcb_atom_t atom = 0; + xcb_intern_atom_reply_t *reply; + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xc, 0, strlen(name), name); + if ((reply = xcb_intern_atom_reply(xc, cookie, NULL))) + atom = reply->atom; + free(reply); + + return atom; +} + +void +sethints(struct wl_listener *listener, void *data) +{ + Client *c = wl_container_of(listener, c, set_hints); + struct wlr_surface *surface = client_surface(c); + if (c == focustop(selmon)) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); + drawbars(); + + if (c->isurgent && surface && surface->mapped) + client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); +} + +void +xwaylandready(struct wl_listener *listener, void *data) +{ + struct wlr_xcursor *xcursor; + xcb_connection_t *xc = xcb_connect(xwayland->display_name, NULL); + int err = xcb_connection_has_error(xc); + if (err) { + fprintf(stderr, "xcb_connect to X server failed with code %d\n. Continuing with degraded functionality.\n", err); + return; + } + + /* Collect atoms we are interested in. If getatom returns 0, we will + * not detect that window type. */ + netatom[NetWMWindowTypeDialog] = getatom(xc, "_NET_WM_WINDOW_TYPE_DIALOG"); + netatom[NetWMWindowTypeSplash] = getatom(xc, "_NET_WM_WINDOW_TYPE_SPLASH"); + netatom[NetWMWindowTypeToolbar] = getatom(xc, "_NET_WM_WINDOW_TYPE_TOOLBAR"); + netatom[NetWMWindowTypeUtility] = getatom(xc, "_NET_WM_WINDOW_TYPE_UTILITY"); + + /* assign the one and only seat */ + wlr_xwayland_set_seat(xwayland, seat); + + /* Set the default XWayland cursor to match the rest of dwl. */ + if ((xcursor = wlr_xcursor_manager_get_xcursor(cursor_mgr, "default", 1))) + wlr_xwayland_set_cursor(xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + + xcb_disconnect(xc); +} +#endif + +int +main(int argc, char *argv[]) +{ + char *startup_cmd = NULL; + int c; + + while ((c = getopt(argc, argv, "s:hdv")) != -1) { + if (c == 's') + startup_cmd = optarg; + else if (c == 'd') + log_level = WLR_DEBUG; + else if (c == 'v') + die("dwl " VERSION); + else + goto usage; + } + if (optind < argc) + goto usage; + + /* Wayland requires XDG_RUNTIME_DIR for creating its communications socket */ + if (!getenv("XDG_RUNTIME_DIR")) + die("XDG_RUNTIME_DIR must be set"); + setup(); + run(startup_cmd); + cleanup(); + return EXIT_SUCCESS; + +usage: + die("Usage: %s [-v] [-d] [-s startup command]", argv[0]); +} diff --git a/dwl/dwl.desktop b/dwl/dwl.desktop new file mode 100644 index 0000000..e1380f7 --- /dev/null +++ b/dwl/dwl.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=dwl +Comment=dwm for Wayland +Exec=dwl +Type=Application diff --git a/dwl/protocols/wlr-layer-shell-unstable-v1.xml b/dwl/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/dwl/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/dwl/protocols/wlr-output-power-management-unstable-v1.xml b/dwl/protocols/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 0000000..a977839 --- /dev/null +++ b/dwl/protocols/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create a output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + + diff --git a/dwl/util.c b/dwl/util.c new file mode 100644 index 0000000..51130af --- /dev/null +++ b/dwl/util.c @@ -0,0 +1,51 @@ +/* See LICENSE.dwm file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} + +int +fd_set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags < 0) { + perror("fcntl(F_GETFL):"); + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("fcntl(F_SETFL):"); + return -1; + } + + return 0; +} diff --git a/dwl/util.h b/dwl/util.h new file mode 100644 index 0000000..226980d --- /dev/null +++ b/dwl/util.h @@ -0,0 +1,5 @@ +/* See LICENSE.dwm file for copyright and license details. */ + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); +int fd_set_nonblock(int fd); diff --git a/local/bin/brwmenu b/local/bin/brwmenu index 858f51b..1d27bb0 100755 --- a/local/bin/brwmenu +++ b/local/bin/brwmenu @@ -6,7 +6,7 @@ import urllib.parse import os DMENU_OPTS = ["-i", "-fn", "monospace:size=12", "-nb", "#151515", "-nf", "#bbbbbb", "-sb", "#005577", "-sf", "#eeeeee"] -BROWSER = "qutebrowser" +BROWSER = "firefox" NOTIFIER = "notify-send" CHOICES = [ diff --git a/local/bin/emacsd b/local/bin/emacsd new file mode 100755 index 0000000..34944c4 --- /dev/null +++ b/local/bin/emacsd @@ -0,0 +1,2 @@ +#!/bin/sh +kitty -e emacsclient -c $1 diff --git a/local/bin/emacsd~ b/local/bin/emacsd~ new file mode 100755 index 0000000..e29cb1d --- /dev/null +++ b/local/bin/emacsd~ @@ -0,0 +1,2 @@ +#!/bin/sh +emacsclient -c $1 \ No newline at end of file diff --git a/local/bin/spot b/local/bin/spot new file mode 100755 index 0000000..4197d88 --- /dev/null +++ b/local/bin/spot @@ -0,0 +1,2 @@ +#!/bin/zsh +dbus-run-session spotify --ozone-platform=wayland & diff --git a/local/bin/startw b/local/bin/startw index 3669c56..aca1410 100755 --- a/local/bin/startw +++ b/local/bin/startw @@ -1,2 +1,2 @@ #!/bin/sh -dbus-run-session sway +prime-run dbus-run-session slstatus -s | prime-run dwl -s 'foot -s' diff --git a/slstatus/config.h b/slstatus/config.h index b83a027..ace2f9b 100644 --- a/slstatus/config.h +++ b/slstatus/config.h @@ -66,10 +66,9 @@ static const char unknown_str[] = "n/a"; */ static const struct arg args[] = { /* function format argument */ - {run_command, "[vol %s%] ", "pamixer --get-volume"}, - {temp, "[tmp %s°C] ", "/sys/class/thermal/thermal_zone3/temp"}, - {run_command, "[mem %s]", "sb-memory"}, - {run_command, " [swp %s]", "sb-swap"}, - {run_command, " [bat %s] ", "sb-battery"}, + {run_command, " [%s%] ", "pamixer --get-volume"}, + {temp, "[%s°C] ", "/sys/class/thermal/thermal_zone3/temp"}, + {run_command, "[%s]", "sb-memory"}, + {run_command, " %s ", "bat-symbol"}, {run_command, "[%s]", "date '+%I:%M %P'"}, }; diff --git a/slstatus/config.h~ b/slstatus/config.h~ deleted file mode 100644 index 81616f0..0000000 --- a/slstatus/config.h~ +++ /dev/null @@ -1,77 +0,0 @@ -/* See LICENSE file for copyright and license details. */ - -/* interval between updates (in ms) */ -const unsigned int interval = 1000; - -/* text to show if no value can be retrieved */ -static const char unknown_str[] = "n/a"; - -/* maximum output string length */ -#define MAXLEN 2048 - -/* - * function description argument (example) - * - * battery_perc battery percentage battery name (BAT0) - * NULL on OpenBSD/FreeBSD - * battery_remaining battery remaining HH:MM battery name (BAT0) - * NULL on OpenBSD/FreeBSD - * battery_state battery charging state battery name (BAT0) - * NULL on OpenBSD/FreeBSD - * cat read arbitrary file path - * cpu_freq cpu frequency in MHz NULL - * cpu_perc cpu usage in percent NULL - * datetime date and time format string (%F %T) - * disk_free free disk space in GB mountpoint path (/) - * disk_perc disk usage in percent mountpoint path (/) - * disk_total total disk space in GB mountpoint path (/) - * disk_used used disk space in GB mountpoint path (/) - * entropy available entropy NULL - * gid GID of current user NULL - * hostname hostname NULL - * ipv4 IPv4 address interface name (eth0) - * ipv6 IPv6 address interface name (eth0) - * kernel_release `uname -r` NULL - * keyboard_indicators caps/num lock indicators format string (c?n?) - * see keyboard_indicators.c - * keymap layout (variant) of current NULL - * keymap - * load_avg load average NULL - * netspeed_rx receive network speed interface name (wlan0) - * netspeed_tx transfer network speed interface name (wlan0) - * num_files number of files in a directory path - * (/home/foo/Inbox/cur) - * ram_free free memory in GB NULL - * ram_perc memory usage in percent NULL - * ram_total total memory size in GB NULL - * ram_used used memory in GB NULL - * run_command custom shell command command (echo foo) - * swap_free free swap in GB NULL - * swap_perc swap usage in percent NULL - * swap_total total swap size in GB NULL - * swap_used used swap in GB NULL - * temp temperature in degree celsius sensor file - * (/sys/class/thermal/...) - * NULL on OpenBSD - * thermal zone on FreeBSD - * (tz0, tz1, etc.) - * uid UID of current user NULL - * uptime system uptime NULL - * username username of current user NULL - * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer) - * NULL on OpenBSD/FreeBSD - * wifi_essid WiFi ESSID interface name (wlan0) - * wifi_perc WiFi signal in percent interface name (wlan0) - */ -static const struct arg args[] = { - /* function format argument */ - {battery_state, " [%s", "BAT1"}, - {battery_perc, " %s% ]", "BAT1"}, - {run_command, " [ %s%] ", "pamixer --get-volume"}, - {cpu_perc, " [ %s%] ", NULL}, - {temp, " [ %s󰔄] ", "/sys/class/thermal/thermal_zone3/temp"}, - {ram_used, " [ %s] ", NULL}, - {run_command, " [󰸗 %s] ", "date +'%b %d'"}, - {run_command, " [󱑃 %s] ", "date +%H:%M"}, - -}; diff --git a/slstatus/slstatus b/slstatus/slstatus index ad65dd3..6ed18d3 100755 Binary files a/slstatus/slstatus and b/slstatus/slstatus differ diff --git a/slstatus/slstatus.o b/slstatus/slstatus.o index 452beb0..648cf82 100644 Binary files a/slstatus/slstatus.o and b/slstatus/slstatus.o differ diff --git a/st/config.h b/st/config.h index 9be97e0..d33713a 100644 --- a/st/config.h +++ b/st/config.h @@ -1,4 +1,4 @@ -static char *font = "monospace:size=20:antialias=true:autohint=true"; +static char *font = "monospace:size=13:antialias=true:autohint=true"; static int borderpx = 2; static char *shell = "/bin/sh"; diff --git a/st/st b/st/st index e713a18..8a9a8c8 100755 Binary files a/st/st and b/st/st differ diff --git a/st/x.o b/st/x.o index d1f856d..d20de4d 100644 Binary files a/st/x.o and b/st/x.o differ diff --git a/wmenu/.gitignore b/wmenu/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/wmenu/.gitignore @@ -0,0 +1 @@ +build diff --git a/wmenu/LICENSE b/wmenu/LICENSE new file mode 100644 index 0000000..27b06f0 --- /dev/null +++ b/wmenu/LICENSE @@ -0,0 +1,32 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2020 Hiltjo Posthuma +© 2015-2019 Quentin Rameau +© 2018-2019 Henrik Nyman +© 2022 adnano + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/wmenu/README.md b/wmenu/README.md new file mode 100644 index 0000000..62a34a7 --- /dev/null +++ b/wmenu/README.md @@ -0,0 +1,32 @@ +# wmenu + +wmenu is an efficient dynamic menu for Sway and wlroots based Wayland +compositors. It provides a Wayland-native dmenu replacement which maintains the +look and feel of dmenu. + +## Installation + +Dependencies: + +- cairo +- pango +- wayland +- xkbcommon +- scdoc (optional) + +``` +$ meson setup build +$ ninja -C build +# ninja -C build install +``` + +## Usage + +See wmenu(1) + +To use wmenu with Sway, you can add the following to your configuration file: + +``` +set $menu wmenu-run +bindsym $mod+d exec $menu +``` diff --git a/wmenu/docs/meson.build b/wmenu/docs/meson.build new file mode 100644 index 0000000..39498d8 --- /dev/null +++ b/wmenu/docs/meson.build @@ -0,0 +1,26 @@ +scdoc_dep = dependency('scdoc', version: '>=1.9.2', native: true, required: false) + +if scdoc_dep.found() + scdoc = find_program( + scdoc_dep.get_pkgconfig_variable('scdoc'), + native: true, + ) + mandir = get_option('mandir') + + docs = [ + 'wmenu.1', + ] + + foreach path : docs + custom_target( + path, + output: path, + input: '@0@.scd'.format(path), + capture: true, + feed: true, + command: [scdoc], + install: true, + install_dir: '@0@/man1'.format(mandir) + ) + endforeach +endif diff --git a/wmenu/docs/wmenu.1.scd b/wmenu/docs/wmenu.1.scd new file mode 100644 index 0000000..4519e8b --- /dev/null +++ b/wmenu/docs/wmenu.1.scd @@ -0,0 +1,186 @@ +WMENU(1) + +# NAME + +wmenu - dynamic menu for Wayland + +# SYNOPSIS + +*wmenu* [-biPv] \ + [-f _font_] \ + [-l _lines_] \ + [-o _output_] \ + [-p _prompt_] \ + [-N _color_] [-n _color_] \ + [-M _color_] [-m _color_] \ + [-S _color_] [-s _color_] + +*wmenu-run* ... + +# DESCRIPTION + +*wmenu* is a dynamic menu for Wayland, which reads a list of newline-separated +items from stdin. When the user selects an item and presses Return, their choice +is printed to stdout and wmenu terminates. Entering text will narrow the items +to those matching the tokens in the input. + +*wmenu-run* is a special invocation of wmenu which lists programs in the user's +$PATH and runs the result. + +# OPTIONS + +*-b* + wmenu appears at the bottom of the screen. + +*-i* + wmenu matches menu items case insensitively. + +*-P* + wmenu will not directly display the keyboard input, but instead replace it + with asterisks. + +*-v* + prints version information to stdout, then exits. + +*-f* _font_ + defines the font used. For more information, see + https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html + +*-l* _lines_ + wmenu lists items vertically, with the given number of lines. + +*-o* _output_ + wmenu is displayed on the output with the given name. + +*-p* _prompt_ + defines the prompt to be displayed to the left of the input field. + +*-N* _RRGGBB[AA]_ + defines the normal background color. + +*-n* _RRGGBB[AA]_ + defines the normal foreground color. + +*-M* _RRGGBB[AA]_ + defines the prompt background color. + +*-m* _RRGGBB[AA]_ + defines the prompt foreground color. + +*-S* _RRGGBB[AA]_ + defines the selection background color. + +*-s* _RRGGBB[AA]_ + defines the selection foreground color. + +# USAGE + +wmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. + +*Tab* + Copy the selected item to the input field. + +*Return* + Confirm selection. Prints the selected item to stdout and exits, returning + success. + +*Ctrl-Return* + Confirm selection. Prints the selected item to stdout and continues. + +*Shift-Return* + Confirm input. Prints the input text to stdout and exits, returning success. + +*Escape* + Exit without selecting an item, returning failure. + +*Ctrl-Left* + Move cursor to the start of the current word. + +*Ctrl-Right* + Move cursor to the end of the current word. + +|[ *C-a* +:< Home + +|[ *C-b* +:< Left + +|[ *C-c* +:< Escape + +|[ *C-d* +:< Delete + +|[ *C-e* +:< End + +|[ *C-f* +:< Right + +|[ *C-g* +:< Escape + +|[ *C-[* +:< Escape + +|[ *C-h* +:< Backspace + +|[ *C-i* +:< Tab + +|[ *C-j* +:< Return + +|[ *C-J* +:< Shift-Return + +|[ *C-k* +:< Delete line right + +|[ *C-m* +:< Return + +|[ *C-M* +:< Shift-Return + +|[ *C-n* +:< Down + +|[ *C-p* +:< Up + +|[ *C-u* +:< Delete line left + +|[ *C-w* +:< Delete word left + +|[ *C-Y* +:< Paste from Wayland clipboard + +|[ *M-b* +:< Move cursor to the start of the current word + +|[ *M-f* +:< Move cursor to the end of the current word + +|[ *M-g* +:< Home + +|[ *M-G* +:< End + +|[ *M-h* +:< Up + +|[ *M-j* +:< Page down + +|[ *M-k* +:< Page up + +|[ *M-l* +:< Down + diff --git a/wmenu/menu.c b/wmenu/menu.c new file mode 100644 index 0000000..1e95ba7 --- /dev/null +++ b/wmenu/menu.c @@ -0,0 +1,692 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "menu.h" + +#include "pango.h" +#include "render.h" +#include "wayland.h" + +// Creates and returns a new menu. +struct menu *menu_create(menu_callback callback) { + struct menu *menu = calloc(1, sizeof(struct menu)); + menu->strncmp = strncmp; + menu->font = "Departure Mono 13"; +menu->normalbg = 0x000000ff; // black background +menu->normalfg = 0xa8dceeff; // cyan text +menu->promptbg = 0x000000ff; // black background +menu->promptfg = 0xa8dceeff; // cyan text +menu->selectionbg = 0xa8dceeff; // cyan background +menu->selectionfg = 0x000000ff; // black text + + menu->callback = callback; + menu->test_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + menu->test_cairo = cairo_create(menu->test_surface); + return menu; +} + +static void free_pages(struct menu *menu) { + struct page *next = menu->pages; + while (next) { + struct page *page = next; + next = page->next; + free(page); + } +} + +static void free_items(struct menu *menu) { + for (size_t i = 0; i < menu->item_count; i++) { + struct item *item = &menu->items[i]; + free(item->text); + } + free(menu->items); +} + +// Destroys the menu, freeing memory associated with it. +void menu_destroy(struct menu *menu) { + free_pages(menu); + free_items(menu); + cairo_destroy(menu->test_cairo); + cairo_surface_destroy(menu->test_surface); + free(menu); +} + +static bool parse_color(const char *color, uint32_t *result) { + if (color[0] == '#') { + ++color; + } + size_t len = strlen(color); + if ((len != 6 && len != 8) || !isxdigit(color[0]) || !isxdigit(color[1])) { + return false; + } + char *ptr; + uint32_t parsed = (uint32_t)strtoul(color, &ptr, 16); + if (*ptr != '\0') { + return false; + } + *result = len == 6 ? ((parsed << 8) | 0xFF) : parsed; + return true; +} + +// Parse menu options from command line arguments. +void menu_getopts(struct menu *menu, int argc, char *argv[]) { + const char *usage = + "Usage: wmenu [-biPv] [-f font] [-l lines] [-o output] [-p prompt]\n" + "\t[-N color] [-n color] [-M color] [-m color] [-S color] [-s color]\n"; + + int opt; + while ((opt = getopt(argc, argv, "bhiPvf:l:o:p:N:n:M:m:S:s:")) != -1) { + switch (opt) { + case 'b': + menu->bottom = true; + break; + case 'i': + menu->strncmp = strncasecmp; + break; + case 'P': + menu->passwd = true; + break; + case 'v': + puts("wmenu " VERSION); + exit(EXIT_SUCCESS); + case 'f': + menu->font = optarg; + break; + case 'l': + menu->lines = atoi(optarg); + break; + case 'o': + menu->output_name = optarg; + break; + case 'p': + menu->prompt = optarg; + break; + case 'N': + if (!parse_color(optarg, &menu->normalbg)) { + fprintf(stderr, "Invalid background color: %s", optarg); + } + break; + case 'n': + if (!parse_color(optarg, &menu->normalfg)) { + fprintf(stderr, "Invalid foreground color: %s", optarg); + } + break; + case 'M': + if (!parse_color(optarg, &menu->promptbg)) { + fprintf(stderr, "Invalid prompt background color: %s", optarg); + } + break; + case 'm': + if (!parse_color(optarg, &menu->promptfg)) { + fprintf(stderr, "Invalid prompt foreground color: %s", optarg); + } + break; + case 'S': + if (!parse_color(optarg, &menu->selectionbg)) { + fprintf(stderr, "Invalid selection background color: %s", optarg); + } + break; + case 's': + if (!parse_color(optarg, &menu->selectionfg)) { + fprintf(stderr, "Invalid selection foreground color: %s", optarg); + } + break; + default: + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + } + + if (optind < argc) { + fprintf(stderr, "%s", usage); + exit(EXIT_FAILURE); + } + + int height = get_font_height(menu->font); + menu->line_height = height + 2; + menu->height = menu->line_height; + if (menu->lines > 0) { + menu->height += menu->height * menu->lines; + } + menu->padding = height / 2; +} + +// Add an item to the menu. +void menu_add_item(struct menu *menu, char *text) { + if ((menu->item_count & (menu->item_count - 1)) == 0) { + size_t alloc_size = menu->item_count ? 2 * menu->item_count : 1; + void *new_array = realloc(menu->items, sizeof(struct item) * alloc_size); + if (!new_array) { + fprintf(stderr, "could not realloc %zu bytes", sizeof(struct item) * alloc_size); + exit(EXIT_FAILURE); + } + menu->items = new_array; + } + + struct item *new = &menu->items[menu->item_count]; + new->text = text; + + menu->item_count++; +} + +static int compare_items(const void *a, const void *b) { + const struct item *item_a = a; + const struct item *item_b = b; + return strcmp(item_a->text, item_b->text); +} + +void menu_sort_and_deduplicate(struct menu *menu) { + size_t j = 1; + size_t i; + + qsort(menu->items, menu->item_count, sizeof(*menu->items), compare_items); + + for (i = 1; i < menu->item_count; i++) { + if (strcmp(menu->items[i].text, menu->items[j - 1].text) == 0) { + free(menu->items[i].text); + } else { + menu->items[j] = menu->items[i]; + j++; + } + } + menu->item_count = j; +} + +static void append_page(struct page *page, struct page **first, struct page **last) { + if (*last) { + (*last)->next = page; + } else { + *first = page; + } + page->prev = *last; + page->next = NULL; + *last = page; +} + +static void page_items(struct menu *menu) { + // Free existing pages + while (menu->pages != NULL) { + struct page *page = menu->pages; + menu->pages = menu->pages->next; + free(page); + } + + if (!menu->matches) { + return; + } + + // Make new pages + if (menu->lines > 0) { + struct page *pages_end = NULL; + struct item *item = menu->matches; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + for (int i = 1; item && i <= menu->lines; i++) { + item->page = page; + page->last = item; + item = item->next_match; + } + append_page(page, &menu->pages, &pages_end); + } + } else { + // Calculate available space + int max_width = menu->width - menu->inputw - menu->promptw + - menu->left_arrow - menu->right_arrow; + + struct page *pages_end = NULL; + struct item *item = menu->matches; + while (item) { + struct page *page = calloc(1, sizeof(struct page)); + page->first = item; + + int total_width = 0; + int items = 0; + while (item) { + total_width += item->width + 2 * menu->padding; + if (total_width > max_width && items > 0) { + break; + } + items++; + + item->page = page; + page->last = item; + item = item->next_match; + } + append_page(page, &menu->pages, &pages_end); + } + } +} + +static const char *fstrstr(struct menu *menu, const char *s, const char *sub) { + for (size_t len = strlen(sub); *s; s++) { + if (!menu->strncmp(s, sub, len)) { + return s; + } + } + return NULL; +} + +static void append_match(struct item *item, struct item **first, struct item **last) { + if (*last) { + (*last)->next_match = item; + } else { + *first = item; + } + item->prev_match = *last; + item->next_match = NULL; + *last = item; +} + +static void match_items(struct menu *menu) { + struct item *lexact = NULL, *exactend = NULL; + struct item *lprefix = NULL, *prefixend = NULL; + struct item *lsubstr = NULL, *substrend = NULL; + char buf[sizeof menu->input], *tok; + char **tokv = NULL; + int i, tokc = 0; + size_t k; + size_t tok_len; + menu->matches = NULL; + menu->matches_end = NULL; + menu->sel = NULL; + + size_t input_len = strlen(menu->input); + + /* tokenize input by space for matching the tokens individually */ + strcpy(buf, menu->input); + tok = strtok(buf, " "); + while (tok) { + tokv = realloc(tokv, (tokc + 1) * sizeof *tokv); + if (!tokv) { + fprintf(stderr, "could not realloc %zu bytes", + (tokc + 1) * sizeof *tokv); + exit(EXIT_FAILURE); + } + tokv[tokc] = tok; + tokc++; + tok = strtok(NULL, " "); + } + tok_len = tokc ? strlen(tokv[0]) : 0; + + for (k = 0; k < menu->item_count; k++) { + struct item *item = &menu->items[k]; + for (i = 0; i < tokc; i++) { + if (!fstrstr(menu, item->text, tokv[i])) { + /* token does not match */ + break; + } + } + if (i != tokc) { + /* not all tokens match */ + continue; + } + if (!tokc || !menu->strncmp(menu->input, item->text, input_len + 1)) { + append_match(item, &lexact, &exactend); + } else if (!menu->strncmp(tokv[0], item->text, tok_len)) { + append_match(item, &lprefix, &prefixend); + } else { + append_match(item, &lsubstr, &substrend); + } + } + + free(tokv); + + if (lexact) { + menu->matches = lexact; + menu->matches_end = exactend; + } + if (lprefix) { + if (menu->matches_end) { + menu->matches_end->next_match = lprefix; + lprefix->prev_match = menu->matches_end; + } else { + menu->matches = lprefix; + } + menu->matches_end = prefixend; + } + if (lsubstr) { + if (menu->matches_end) { + menu->matches_end->next_match = lsubstr; + lsubstr->prev_match = menu->matches_end; + } else { + menu->matches = lsubstr; + } + menu->matches_end = substrend; + } + + page_items(menu); + if (menu->pages) { + menu->sel = menu->pages->first; + } +} + +// Marks the menu as needing to be rendered again. +void menu_invalidate(struct menu *menu) { + menu->rendered = false; +} + +// Render menu items. +void menu_render_items(struct menu *menu) { + calc_widths(menu); + match_items(menu); + render_menu(menu); +} + +static void insert(struct menu *menu, const char *text, ssize_t len) { + if (strlen(menu->input) + len > sizeof menu->input - 1) { + return; + } + memmove(menu->input + menu->cursor + len, menu->input + menu->cursor, + sizeof menu->input - menu->cursor - MAX(len, 0)); + if (len > 0 && text != NULL) { + memcpy(menu->input + menu->cursor, text, len); + } + menu->cursor += len; +} + +// Add pasted text to the menu input. +void menu_paste(struct menu *menu, const char *text, ssize_t len) { + insert(menu, text, len); +} + +static size_t nextrune(struct menu *menu, int incr) { + size_t n, len; + + len = strlen(menu->input); + for(n = menu->cursor + incr; n < len && (menu->input[n] & 0xc0) == 0x80; n += incr); + return n; +} + +// Move the cursor to the beginning or end of the word, skipping over any preceding whitespace. +static void movewordedge(struct menu *menu, int dir) { + if (dir < 0) { + // Move to beginning of word + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { + menu->cursor = nextrune(menu, -1); + } + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { + menu->cursor = nextrune(menu, -1); + } + } else { + // Move to end of word + size_t len = strlen(menu->input); + while (menu->cursor < len && menu->input[menu->cursor] == ' ') { + menu->cursor = nextrune(menu, +1); + } + while (menu->cursor < len && menu->input[menu->cursor] != ' ') { + menu->cursor = nextrune(menu, +1); + } + } +} + +// Handle a keypress. +void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, + xkb_keysym_t sym) { + if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { + return; + } + + struct xkb_state *state = context_get_xkb_state(menu->context); + bool ctrl = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + bool meta = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + bool shift = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); + + size_t len = strlen(menu->input); + + if (ctrl) { + // Emacs-style line editing bindings + switch (sym) { + case XKB_KEY_a: + sym = XKB_KEY_Home; + break; + case XKB_KEY_b: + sym = XKB_KEY_Left; + break; + case XKB_KEY_c: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_d: + sym = XKB_KEY_Delete; + break; + case XKB_KEY_e: + sym = XKB_KEY_End; + break; + case XKB_KEY_f: + sym = XKB_KEY_Right; + break; + case XKB_KEY_g: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_bracketleft: + sym = XKB_KEY_Escape; + break; + case XKB_KEY_h: + sym = XKB_KEY_BackSpace; + break; + case XKB_KEY_i: + sym = XKB_KEY_Tab; + break; + case XKB_KEY_j: + case XKB_KEY_J: + case XKB_KEY_m: + case XKB_KEY_M: + sym = XKB_KEY_Return; + ctrl = false; + break; + case XKB_KEY_n: + sym = XKB_KEY_Down; + break; + case XKB_KEY_p: + sym = XKB_KEY_Up; + break; + + case XKB_KEY_k: + // Delete right + menu->input[menu->cursor] = '\0'; + match_items(menu); + menu_invalidate(menu); + return; + case XKB_KEY_u: + // Delete left + insert(menu, NULL, 0 - menu->cursor); + match_items(menu); + menu_invalidate(menu); + return; + case XKB_KEY_w: + // Delete word + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] == ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + } + while (menu->cursor > 0 && menu->input[nextrune(menu, -1)] != ' ') { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + } + match_items(menu); + menu_invalidate(menu); + return; + case XKB_KEY_Y: + // Paste clipboard + if (!context_paste(menu->context)) { + return; + } + match_items(menu); + menu_invalidate(menu); + return; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + movewordedge(menu, -1); + menu_invalidate(menu); + return; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + movewordedge(menu, +1); + menu_invalidate(menu); + return; + + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + break; + default: + return; + } + } else if (meta) { + // Emacs-style line editing bindings + switch (sym) { + case XKB_KEY_b: + movewordedge(menu, -1); + menu_invalidate(menu); + return; + case XKB_KEY_f: + movewordedge(menu, +1); + menu_invalidate(menu); + return; + case XKB_KEY_g: + sym = XKB_KEY_Home; + break; + case XKB_KEY_G: + sym = XKB_KEY_End; + break; + case XKB_KEY_h: + sym = XKB_KEY_Up; + break; + case XKB_KEY_j: + sym = XKB_KEY_Next; + break; + case XKB_KEY_k: + sym = XKB_KEY_Prior; + break; + case XKB_KEY_l: + sym = XKB_KEY_Down; + break; + default: + return; + } + } + + char buf[8]; + switch (sym) { + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + if (shift) { + menu->callback(menu, menu->input, true); + } else { + char *text = menu->sel ? menu->sel->text : menu->input; + menu->callback(menu, text, !ctrl); + } + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + case XKB_KEY_Up: + case XKB_KEY_KP_Up: + if (menu->sel && menu->sel->prev_match) { + menu->sel = menu->sel->prev_match; + menu_invalidate(menu); + } else if (menu->cursor > 0) { + menu->cursor = nextrune(menu, -1); + menu_invalidate(menu); + } + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + case XKB_KEY_Down: + case XKB_KEY_KP_Down: + if (menu->cursor < len) { + menu->cursor = nextrune(menu, +1); + menu_invalidate(menu); + } else if (menu->sel && menu->sel->next_match) { + menu->sel = menu->sel->next_match; + menu_invalidate(menu); + } + break; + case XKB_KEY_Prior: + case XKB_KEY_KP_Prior: + if (menu->sel && menu->sel->page->prev) { + menu->sel = menu->sel->page->prev->first; + menu_invalidate(menu); + } + break; + case XKB_KEY_Next: + case XKB_KEY_KP_Next: + if (menu->sel && menu->sel->page->next) { + menu->sel = menu->sel->page->next->first; + menu_invalidate(menu); + } + break; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + if (menu->sel == menu->matches) { + menu->cursor = 0; + menu_invalidate(menu); + } else { + menu->sel = menu->matches; + menu_invalidate(menu); + } + break; + case XKB_KEY_End: + case XKB_KEY_KP_End: + if (menu->cursor < len) { + menu->cursor = len; + menu_invalidate(menu); + } else { + menu->sel = menu->matches_end; + menu_invalidate(menu); + } + break; + case XKB_KEY_BackSpace: + if (menu->cursor > 0) { + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + match_items(menu); + menu_invalidate(menu); + } + break; + case XKB_KEY_Delete: + case XKB_KEY_KP_Delete: + if (menu->cursor == len) { + return; + } + menu->cursor = nextrune(menu, +1); + insert(menu, NULL, nextrune(menu, -1) - menu->cursor); + match_items(menu); + menu_invalidate(menu); + break; + case XKB_KEY_Tab: + if (!menu->sel) { + return; + } + menu->cursor = strnlen(menu->sel->text, sizeof menu->input - 1); + memcpy(menu->input, menu->sel->text, menu->cursor); + menu->input[menu->cursor] = '\0'; + match_items(menu); + menu_invalidate(menu); + break; + case XKB_KEY_Escape: + menu->exit = true; + menu->failure = true; + break; + default: + if (xkb_keysym_to_utf8(sym, buf, 8)) { + insert(menu, buf, strnlen(buf, 8)); + match_items(menu); + menu_invalidate(menu); + } + } +} diff --git a/wmenu/menu.h b/wmenu/menu.h new file mode 100644 index 0000000..fdd9ad2 --- /dev/null +++ b/wmenu/menu.h @@ -0,0 +1,95 @@ +#ifndef WMENU_MENU_H +#define WMENU_MENU_H + +#include +#include +#include +#include +#include + +struct menu; +typedef void (*menu_callback)(struct menu *menu, char *text, bool exit); + +// A menu item. +struct item { + char *text; + int width; + struct item *prev_match; // previous matching item + struct item *next_match; // next matching item + struct page *page; // the page holding this item +}; + +// A page of menu items. +struct page { + struct item *first; // first item in the page + struct item *last; // last item in the page + struct page *prev; // previous page + struct page *next; // next page +}; + +// Menu state. +struct menu { + // Whether the menu appears at the bottom of the screen + bool bottom; + // The function used to match menu items + int (*strncmp)(const char *, const char *, size_t); + // Whether the input is a password + bool passwd; + // The font used to display the menu + char *font; + // The number of lines to list items vertically + int lines; + // The name of the output to display on + char *output_name; + // The prompt displayed to the left of the input field + char *prompt; + // Normal colors + uint32_t normalbg, normalfg; + // Prompt colors + uint32_t promptbg, promptfg; + // Selection colors + uint32_t selectionbg, selectionfg; + + struct wl_context *context; + + // 1x1 surface used estimate text sizes with pango + cairo_surface_t *test_surface; + cairo_t *test_cairo; + + int width; + int height; + int line_height; + int padding; + int inputw; + int promptw; + int left_arrow; + int right_arrow; + bool rendered; + + char input[BUFSIZ]; + size_t cursor; + + struct item *items; // array of all items + size_t item_count; + struct item *matches; // list of matching items + struct item *matches_end; // last matching item + struct item *sel; // selected item + struct page *pages; // list of pages + + menu_callback callback; + bool exit; + bool failure; +}; + +struct menu *menu_create(menu_callback callback); +void menu_destroy(struct menu *menu); +void menu_getopts(struct menu *menu, int argc, char *argv[]); +void menu_add_item(struct menu *menu, char *text); +void menu_sort_and_deduplicate(struct menu *menu); +void menu_invalidate(struct menu *menu); +void menu_render_items(struct menu *menu); +void menu_paste(struct menu *menu, const char *text, ssize_t len); +void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state, + xkb_keysym_t sym); + +#endif diff --git a/wmenu/meson.build b/wmenu/meson.build new file mode 100644 index 0000000..3a5cb18 --- /dev/null +++ b/wmenu/meson.build @@ -0,0 +1,80 @@ +project( + 'wmenu', + 'c', + version: '0.2.0', + license: 'MIT', + default_options: [ + 'c_std=c11', + 'warning_level=2', + 'werror=true', + ] +) + +cc = meson.get_compiler('c') + +add_project_arguments(cc.get_supported_arguments([ + '-DVERSION="@0@"'.format(meson.project_version()), + + '-Wno-missing-field-initializers', + '-Wno-unused-parameter', + '-Wundef', + '-Wvla', +]), language : 'c') + +cairo = dependency('cairo') +pango = dependency('pango') +pangocairo = dependency('pangocairo') +wayland_client = dependency('wayland-client') +wayland_protos = dependency('wayland-protocols') +xkbcommon = dependency('xkbcommon') + +rt = cc.find_library('rt') + +subdir('protocols') +subdir('docs') + +executable( + 'wmenu', + files( + 'menu.c', + 'pango.c', + 'pool-buffer.c', + 'render.c', + 'wayland.c', + 'wmenu.c', + ), + dependencies: [ + cairo, + client_protos, + pango, + pangocairo, + rt, + wayland_client, + wayland_protos, + xkbcommon, + ], + install: true, +) + +executable( + 'wmenu-run', + files( + 'menu.c', + 'pango.c', + 'pool-buffer.c', + 'render.c', + 'wayland.c', + 'wmenu-run.c', + ), + dependencies: [ + cairo, + client_protos, + pango, + pangocairo, + rt, + wayland_client, + wayland_protos, + xkbcommon, + ], + install: true, +) diff --git a/wmenu/pango.c b/wmenu/pango.c new file mode 100644 index 0000000..07d4c5b --- /dev/null +++ b/wmenu/pango.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include + +#include "pango.h" + +int get_font_height(const char *fontstr) { + PangoFontMap *fontmap = pango_cairo_font_map_get_default(); + PangoContext *context = pango_font_map_create_context(fontmap); + PangoFontDescription *desc = pango_font_description_from_string(fontstr); + PangoFont *font = pango_font_map_load_font(fontmap, context, desc); + if (font == NULL) { + pango_font_description_free(desc); + g_object_unref(context); + return -1; + } + PangoFontMetrics *metrics = pango_font_get_metrics(font, NULL); + int height = pango_font_metrics_get_height(metrics) / PANGO_SCALE; + pango_font_metrics_unref(metrics); + g_object_unref(font); + pango_font_description_free(desc); + g_object_unref(context); + return height; +} + +PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, + const char *text, double scale) { + PangoLayout *layout = pango_cairo_create_layout(cairo); + PangoAttrList *attrs = pango_attr_list_new(); + pango_layout_set_text(layout, text, -1); + pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); + PangoFontDescription *desc = pango_font_description_from_string(font); + pango_layout_set_font_description(layout, desc); + pango_layout_set_single_paragraph_mode(layout, 1); + pango_layout_set_attributes(layout, attrs); + pango_font_description_free(desc); + pango_attr_list_unref(attrs); + return layout; +} + +void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, + int *baseline, double scale, const char *text) { + PangoLayout *layout = get_pango_layout(cairo, font, text, scale); + pango_cairo_update_layout(cairo, layout); + pango_layout_get_pixel_size(layout, width, height); + if (baseline) { + *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; + } + g_object_unref(layout); +} + +int text_width(cairo_t *cairo, const char *font, const char *text) { + int text_width; + get_text_size(cairo, font, &text_width, NULL, NULL, 1, text); + return text_width; +} + +void pango_printf(cairo_t *cairo, const char *font, double scale, + const char *text) { + PangoLayout *layout = get_pango_layout(cairo, font, text, scale); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_get_font_options(cairo, fo); + pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); + cairo_font_options_destroy(fo); + pango_cairo_update_layout(cairo, layout); + pango_cairo_show_layout(cairo, layout); + g_object_unref(layout); +} diff --git a/wmenu/pango.h b/wmenu/pango.h new file mode 100644 index 0000000..482d49a --- /dev/null +++ b/wmenu/pango.h @@ -0,0 +1,16 @@ +#ifndef WMENU_PANGO_H +#define WMENU_PANGO_H +#include +#include +#include + +int get_font_height(const char *font); +PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, + const char *text, double scale); +void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, + int *baseline, double scale, const char *text); +int text_width(cairo_t *cairo, const char *font, const char *text); +void pango_printf(cairo_t *cairo, const char *font, double scale, + const char *text); + +#endif diff --git a/wmenu/pool-buffer.c b/wmenu/pool-buffer.c new file mode 100644 index 0000000..daa84fd --- /dev/null +++ b/wmenu/pool-buffer.c @@ -0,0 +1,146 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pool-buffer.h" + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int anonymous_shm_open(void) { + char name[] = "/wmenu-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +static int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void buffer_release(void *data, struct wl_buffer *wl_buffer) { + struct pool_buffer *buffer = data; + buffer->busy = false; +} + +static const struct wl_buffer_listener buffer_listener = { + .release = buffer_release +}; + +static struct pool_buffer *create_buffer(struct wl_shm *shm, + struct pool_buffer *buf, int32_t width, int32_t height, + int32_t scale, uint32_t format) { + int32_t stride = width * scale * 4; + int32_t size = stride * height * scale; + + int fd = create_shm_file(size); + assert(fd != -1); + void *data = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + buf->buffer = wl_shm_pool_create_buffer(pool, 0, + width * scale, height * scale, stride, format); + wl_shm_pool_destroy(pool); + close(fd); + + buf->size = (size_t)size; + buf->width = width; + buf->height = height; + buf->scale = scale; + buf->data = data; + buf->surface = cairo_image_surface_create_for_data(data, + CAIRO_FORMAT_ARGB32, width * scale, height * scale, stride); + cairo_surface_set_device_scale(buf->surface, scale, scale); + buf->cairo = cairo_create(buf->surface); + buf->pango = pango_cairo_create_context(buf->cairo); + + wl_buffer_add_listener(buf->buffer, &buffer_listener, buf); + return buf; +} + +void destroy_buffer(struct pool_buffer *buffer) { + if (buffer->buffer) { + wl_buffer_destroy(buffer->buffer); + } + if (buffer->cairo) { + cairo_destroy(buffer->cairo); + } + if (buffer->surface) { + cairo_surface_destroy(buffer->surface); + } + if (buffer->pango) { + g_object_unref(buffer->pango); + } + if (buffer->data) { + munmap(buffer->data, buffer->size); + } + memset(buffer, 0, sizeof(struct pool_buffer)); +} + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale) { + struct pool_buffer *buffer = NULL; + + for (size_t i = 0; i < 2; ++i) { + if (pool[i].busy) { + continue; + } + buffer = &pool[i]; + } + + if (!buffer) { + return NULL; + } + + if (buffer->width != width || buffer->height != height + || buffer->scale != scale) { + destroy_buffer(buffer); + } + + if (!buffer->buffer) { + if (!create_buffer(shm, buffer, width, height, scale, + WL_SHM_FORMAT_ARGB8888)) { + return NULL; + } + } + buffer->busy = true; + return buffer; +} diff --git a/wmenu/pool-buffer.h b/wmenu/pool-buffer.h new file mode 100644 index 0000000..e07802d --- /dev/null +++ b/wmenu/pool-buffer.h @@ -0,0 +1,26 @@ +/* Taken from sway. MIT licensed */ +#ifndef WMENU_POOL_BUFFER_H +#define WMENU_POOL_BUFFER_H + +#include +#include +#include +#include +#include + +struct pool_buffer { + struct wl_buffer *buffer; + cairo_surface_t *surface; + cairo_t *cairo; + PangoContext *pango; + size_t size; + int32_t width, height, scale; + bool busy; + void *data; +}; + +struct pool_buffer *get_next_buffer(struct wl_shm *shm, + struct pool_buffer pool[static 2], int32_t width, int32_t height, int32_t scale); +void destroy_buffer(struct pool_buffer *buffer); + +#endif diff --git a/wmenu/protocols/meson.build b/wmenu/protocols/meson.build new file mode 100644 index 0000000..5c9973d --- /dev/null +++ b/wmenu/protocols/meson.build @@ -0,0 +1,47 @@ +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') + +wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) +if wayland_scanner_dep.found() + wayland_scanner = find_program( + wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'), + native: true, + ) +else + wayland_scanner = find_program('wayland-scanner', native: true) +endif + +protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'], + ['wlr-layer-shell-unstable-v1.xml'], +] + +wl_protos_src = [] +wl_protos_headers = [] + +foreach p : protocols + xml = join_paths(p) + wl_protos_src += custom_target( + xml.underscorify() + '_protocol_c', + input: xml, + output: '@BASENAME@-protocol.c', + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], + ) + wl_protos_headers += custom_target( + xml.underscorify() + '_client_h', + input: xml, + output: '@BASENAME@-client-protocol.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], + ) +endforeach + +lib_client_protos = static_library( + 'client_protos', + wl_protos_src + wl_protos_headers, + dependencies: wayland_client.partial_dependency(compile_args: true), +) + +client_protos = declare_dependency( + link_with: lib_client_protos, + sources: wl_protos_headers, +) diff --git a/wmenu/protocols/wlr-layer-shell-unstable-v1.xml b/wmenu/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..216e0d9 --- /dev/null +++ b/wmenu/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + diff --git a/wmenu/render.c b/wmenu/render.c new file mode 100644 index 0000000..e86d93b --- /dev/null +++ b/wmenu/render.c @@ -0,0 +1,212 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include + +#include "render.h" + +#include "menu.h" +#include "pango.h" +#include "pool-buffer.h" +#include "wayland.h" + +// Calculate text widths. +void calc_widths(struct menu *menu) { + struct wl_context *context = menu->context; + int scale = context_get_scale(context); + cairo_surface_set_device_scale(menu->test_surface, scale, scale); + cairo_set_antialias(menu->test_cairo, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_set_font_options(menu->test_cairo, fo); + cairo_font_options_destroy(fo); + cairo_t *cairo = menu->test_cairo; + + // Calculate prompt width + if (menu->prompt) { + menu->promptw = text_width(cairo, menu->font, menu->prompt) + menu->padding + menu->padding/2; + } else { + menu->promptw = 0; + } + + // Calculate scroll indicator widths + menu->left_arrow = text_width(cairo, menu->font, "<") + 2 * menu->padding; + menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding; + + // Calculate item widths and input area width + for (size_t i = 0; i < menu->item_count; i++) { + struct item *item = &menu->items[i]; + item->width = text_width(cairo, menu->font, item->text); + if (item->width > menu->inputw) { + menu->inputw = item->width; + } + } +} + +static void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { + cairo_set_source_rgba(cairo, + (color >> (3*8) & 0xFF) / 255.0, + (color >> (2*8) & 0xFF) / 255.0, + (color >> (1*8) & 0xFF) / 255.0, + (color >> (0*8) & 0xFF) / 255.0); +} + +// Renders text to cairo. +static int render_text(struct menu *menu, cairo_t *cairo, const char *str, + int x, int y, int width, uint32_t bg_color, uint32_t fg_color, + int left_padding, int right_padding) { + + int text_width, text_height; + get_text_size(cairo, menu->font, &text_width, &text_height, NULL, 1, str); + int text_y = (menu->line_height / 2.0) - (text_height / 2.0); + + if (width == 0) { + width = text_width + left_padding + right_padding; + } + if (bg_color) { + cairo_set_source_u32(cairo, bg_color); + cairo_rectangle(cairo, x, y, width, menu->line_height); + cairo_fill(cairo); + } + cairo_move_to(cairo, x + left_padding, y + text_y); + cairo_set_source_u32(cairo, fg_color); + pango_printf(cairo, menu->font, 1, str); + + return width; +} + +// Renders the prompt message. +static void render_prompt(struct menu *menu, cairo_t *cairo) { + if (!menu->prompt) { + return; + } + render_text(menu, cairo, menu->prompt, 0, 0, 0, + menu->promptbg, menu->promptfg, menu->padding, menu->padding/2); +} + +// Renders the input text. +static void render_input(struct menu *menu, cairo_t *cairo) { + char *censort = NULL; + + if (menu->passwd) { + censort = calloc(1, sizeof(menu->input)); + if (!censort) { + return; + } + memset(censort, '*', strlen(menu->input)); + } + + render_text(menu, cairo, menu->passwd ? censort : menu->input, + menu->promptw, 0, 0, 0, menu->normalfg, menu->padding, menu->padding); + + if (censort) { + free(censort); + } +} + +// Renders a cursor for the input field. +static void render_cursor(struct menu *menu, cairo_t *cairo) { + const int cursor_width = 2; + const int cursor_margin = 2; + int cursor_pos = menu->promptw + menu->padding + + text_width(cairo, menu->font, menu->input) + - text_width(cairo, menu->font, &menu->input[menu->cursor]) + - cursor_width / 2; + cairo_rectangle(cairo, cursor_pos, cursor_margin, cursor_width, + menu->line_height - 2 * cursor_margin); + cairo_fill(cairo); +} + +// Renders a single menu item horizontally. +static int render_horizontal_item(struct menu *menu, cairo_t *cairo, struct item *item, int x) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->normalbg; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->normalfg; + + return render_text(menu, cairo, item->text, x, 0, 0, + bg_color, fg_color, menu->padding, menu->padding); +} + +// Renders a single menu item vertically. +static int render_vertical_item(struct menu *menu, cairo_t *cairo, struct item *item, int x, int y) { + uint32_t bg_color = menu->sel == item ? menu->selectionbg : menu->normalbg; + uint32_t fg_color = menu->sel == item ? menu->selectionfg : menu->normalfg; + + render_text(menu, cairo, item->text, x, y, menu->width - x, + bg_color, fg_color, menu->padding, 0); + return menu->line_height; +} + +// Renders a page of menu items horizontally. +static void render_horizontal_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw + menu->inputw + menu->left_arrow; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + x += render_horizontal_item(menu, cairo, item, x); + } + + // Draw left and right scroll indicators if necessary + if (page->prev) { + cairo_move_to(cairo, menu->promptw + menu->inputw + menu->padding, 0); + pango_printf(cairo, menu->font, 1, "<"); + } + if (page->next) { + cairo_move_to(cairo, menu->width - menu->right_arrow + menu->padding, 0); + pango_printf(cairo, menu->font, 1, ">"); + } +} + +// Renders a page of menu items vertically. +static void render_vertical_page(struct menu *menu, cairo_t *cairo, struct page *page) { + int x = menu->promptw; + int y = menu->line_height; + for (struct item *item = page->first; item != page->last->next_match; item = item->next_match) { + y += render_vertical_item(menu, cairo, item, x, y); + } +} + +// Renders the menu to cairo. +static void render_to_cairo(struct menu *menu, cairo_t *cairo) { + // Render background + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, menu->normalbg); + cairo_paint(cairo); + + // Render prompt and input + render_prompt(menu, cairo); + render_input(menu, cairo); + render_cursor(menu, cairo); + + // Render selected page + if (!menu->sel) { + return; + } + if (menu->lines > 0) { + render_vertical_page(menu, cairo, menu->sel->page); + } else { + render_horizontal_page(menu, cairo, menu->sel->page); + } +} + +// Renders a single frame of the menu. +void render_menu(struct menu *menu) { + struct wl_context *context = menu->context; + + int scale = context_get_scale(context); + struct pool_buffer *buffer = context_get_next_buffer(context, scale); + if (!buffer) { + return; + } + + cairo_t *shm = buffer->cairo; + cairo_set_antialias(shm, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_set_font_options(shm, fo); + cairo_font_options_destroy(fo); + render_to_cairo(menu, shm); + + struct wl_surface *surface = context_get_surface(context); + wl_surface_set_buffer_scale(surface, scale); + wl_surface_attach(surface, buffer->buffer, 0, 0); + wl_surface_damage(surface, 0, 0, menu->width, menu->height); + wl_surface_commit(surface); + + menu->rendered = true; +} diff --git a/wmenu/render.h b/wmenu/render.h new file mode 100644 index 0000000..af5fb67 --- /dev/null +++ b/wmenu/render.h @@ -0,0 +1,9 @@ +#ifndef WMENU_RENDER_H +#define WMENU_RENDER_H + +#include "menu.h" + +void calc_widths(struct menu *menu); +void render_menu(struct menu *menu); + +#endif diff --git a/wmenu/wayland.c b/wmenu/wayland.c new file mode 100644 index 0000000..823ced1 --- /dev/null +++ b/wmenu/wayland.c @@ -0,0 +1,510 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "menu.h" +#include "pool-buffer.h" +#include "render.h" +#include "wayland.h" +#include "xdg-activation-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +// A Wayland output. +struct output { + struct wl_context *context; + struct wl_output *output; + const char *name; // output name + int32_t scale; // output scale + struct output *next; // next output +}; + +// Creates and returns a new output. +static struct output *output_create(struct wl_context *context, struct wl_output *wl_output) { + struct output *output = calloc(1, sizeof(struct output)); + output->context = context; + output->output = wl_output; + output->scale = 1; + return output; +} + +// Keyboard state. +struct keyboard { + struct menu *menu; + struct wl_keyboard *keyboard; + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; + + int repeat_timer; + int repeat_delay; + int repeat_period; + enum wl_keyboard_key_state repeat_key_state; + xkb_keysym_t repeat_sym; +}; + +// Creates and returns a new keyboard. +static struct keyboard *keyboard_create(struct menu *menu, struct wl_keyboard *wl_keyboard) { + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard->menu = menu; + keyboard->keyboard = wl_keyboard; + keyboard->context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + assert(keyboard->context != NULL); + keyboard->repeat_timer = timerfd_create(CLOCK_MONOTONIC, 0); + assert(keyboard->repeat_timer != -1); + return keyboard; +} + +// Frees the keyboard. +static void free_keyboard(struct keyboard *keyboard) { + wl_keyboard_release(keyboard->keyboard); + xkb_state_unref(keyboard->state); + xkb_keymap_unref(keyboard->keymap); + xkb_context_unref(keyboard->context); + free(keyboard); +} + +// Wayland context. +struct wl_context { + struct menu *menu; + + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_data_device_manager *data_device_manager; + struct zwlr_layer_shell_v1 *layer_shell; + struct output *output_list; + struct xdg_activation_v1 *activation; + + struct keyboard *keyboard; + struct wl_data_device *data_device; + struct wl_surface *surface; + struct zwlr_layer_surface_v1 *layer_surface; + struct wl_data_offer *data_offer; + struct output *output; + + struct pool_buffer buffers[2]; + struct pool_buffer *current; +}; + +// Returns the current output_scale. +int context_get_scale(struct wl_context *context) { + return context->output ? context->output->scale : 1; +} + +// Returns the current buffer from the pool. +struct pool_buffer *context_get_current_buffer(struct wl_context *context) { + return context->current; +} + +// Returns the next buffer from the pool. +struct pool_buffer *context_get_next_buffer(struct wl_context *context, int scale) { + struct menu *menu = context->menu; + context->current = get_next_buffer(context->shm, context->buffers, menu->width, menu->height, scale); + return context->current; +} + +// Returns the Wayland surface for the context. +struct wl_surface *context_get_surface(struct wl_context *context) { + return context->surface; +} + +// Returns the XKB state for the context. +struct xkb_state *context_get_xkb_state(struct wl_context *context) { + return context->keyboard->state; +} + +// Returns the XDG activation object for the context. +struct xdg_activation_v1 *context_get_xdg_activation(struct wl_context *context) { + return context->activation; +} + +// Retrieves pasted text from a Wayland data offer. +bool context_paste(struct wl_context *context) { + if (!context->data_offer) { + return false; + } + + int fds[2]; + if (pipe(fds) == -1) { + // Pipe failed + return false; + } + wl_data_offer_receive(context->data_offer, "text/plain", fds[1]); + close(fds[1]); + + wl_display_roundtrip(context->display); + + while (true) { + char buf[1024]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) { + break; + } + menu_paste(context->menu, buf, n); + } + close(fds[0]); + + wl_data_offer_destroy(context->data_offer); + context->data_offer = NULL; + return true; +} + +// Adds an output to the output list. +static void context_add_output(struct wl_context *context, struct output *output) { + output->next = context->output_list; + context->output_list = output; +} + +// Frees the outputs. +static void free_outputs(struct wl_context *context) { + struct output *next = context->output_list; + while (next) { + struct output *output = next; + next = output->next; + wl_output_destroy(output->output); + free(output); + } +} + +// Destroys the Wayland context, freeing memory associated with it. +static void context_destroy(struct wl_context *context) { + wl_registry_destroy(context->registry); + wl_compositor_destroy(context->compositor); + wl_shm_destroy(context->shm); + wl_seat_destroy(context->seat); + wl_data_device_manager_destroy(context->data_device_manager); + zwlr_layer_shell_v1_destroy(context->layer_shell); + free_outputs(context); + + free_keyboard(context->keyboard); + wl_data_device_destroy(context->data_device); + wl_surface_destroy(context->surface); + zwlr_layer_surface_v1_destroy(context->layer_surface); + xdg_activation_v1_destroy(context->activation); + + wl_display_disconnect(context->display); + free(context); +} + +static void noop() { + // Do nothing +} + +static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) { + struct wl_context *context = data; + context->output = wl_output_get_user_data(wl_output); + menu_invalidate(context->menu); +} + +static const struct wl_surface_listener surface_listener = { + .enter = surface_enter, + .leave = noop, +}; + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + struct wl_context *context = data; + context->menu->width = width; + context->menu->height = height; + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + +static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { + struct wl_context *context = data; + context->menu->exit = true; +} + +static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { + struct output *output = data; + output->scale = factor; +} + +static void output_name(void *data, struct wl_output *wl_output, const char *name) { + struct output *output = data; + output->name = name; + + struct wl_context *context = output->context; + if (context->menu->output_name && strcmp(context->menu->output_name, name) == 0) { + context->output = output; + } +} + +static const struct wl_output_listener output_listener = { + .geometry = noop, + .mode = noop, + .done = noop, + .scale = output_scale, + .name = output_name, + .description = noop, +}; + +static void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + struct keyboard *keyboard = data; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + assert(map_shm != MAP_FAILED); + + keyboard->keymap = xkb_keymap_new_from_string(keyboard->context, + map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0); + munmap(map_shm, size); + close(fd); + + keyboard->state = xkb_state_new(keyboard->keymap); +} + +static void keyboard_repeat(struct keyboard *keyboard) { + menu_keypress(keyboard->menu, keyboard->repeat_key_state, keyboard->repeat_sym); + struct itimerspec spec = { 0 }; + spec.it_value.tv_sec = keyboard->repeat_period / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_period % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); +} + +static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) { + struct keyboard *keyboard = data; + + enum wl_keyboard_key_state key_state = _key_state; + xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->state, key + 8); + menu_keypress(keyboard->menu, key_state, sym); + + if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && keyboard->repeat_period >= 0) { + keyboard->repeat_key_state = key_state; + keyboard->repeat_sym = sym; + + struct itimerspec spec = { 0 }; + spec.it_value.tv_sec = keyboard->repeat_delay / 1000; + spec.it_value.tv_nsec = (keyboard->repeat_delay % 1000) * 1000000l; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); + } else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) { + struct itimerspec spec = { 0 }; + timerfd_settime(keyboard->repeat_timer, 0, &spec, NULL); + } +} + +static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) { + struct keyboard *keyboard = data; + keyboard->repeat_delay = delay; + if (rate > 0) { + keyboard->repeat_period = 1000 / rate; + } else { + keyboard->repeat_period = -1; + } +} + +static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + struct keyboard *keyboard = data; + xkb_state_update_mask(keyboard->state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_keymap, + .enter = noop, + .leave = noop, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = keyboard_repeat_info, +}; + +static void seat_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) { + struct wl_context *context = data; + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(seat); + struct keyboard *keyboard = keyboard_create(context->menu, wl_keyboard); + wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, keyboard); + context->keyboard = keyboard; + } +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_capabilities, + .name = noop, +}; + +static void data_device_selection(void *data, struct wl_data_device *data_device, + struct wl_data_offer *data_offer) { + struct wl_context *context = data; + context->data_offer = data_offer; +} + +static const struct wl_data_device_listener data_device_listener = { + .data_offer = noop, + .enter = noop, + .leave = noop, + .motion = noop, + .drop = noop, + .selection = data_device_selection, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + struct wl_context *context = data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + context->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + context->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + context->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4); + wl_seat_add_listener(context->seat, &seat_listener, data); + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + context->data_device_manager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + context->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, 4); + struct output *output = output_create(context, wl_output); + wl_output_set_user_data(wl_output, output); + wl_output_add_listener(wl_output, &output_listener, output); + context_add_output(context, output); + } else if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + context->activation = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = noop, +}; + +// Connect to the Wayland display and run the menu. +int menu_run(struct menu *menu) { + struct wl_context *context = calloc(1, sizeof(struct wl_context)); + context->menu = menu; + menu->context = context; + + context->display = wl_display_connect(NULL); + if (!context->display) { + fprintf(stderr, "Failed to connect to display.\n"); + exit(EXIT_FAILURE); + } + + struct wl_registry *registry = wl_display_get_registry(context->display); + wl_registry_add_listener(registry, ®istry_listener, context); + wl_display_roundtrip(context->display); + assert(context->compositor != NULL); + assert(context->shm != NULL); + assert(context->seat != NULL); + assert(context->data_device_manager != NULL); + assert(context->layer_shell != NULL); + assert(context->activation != NULL); + context->registry = registry; + + // Get data device for seat + struct wl_data_device *data_device = wl_data_device_manager_get_data_device( + context->data_device_manager, context->seat); + wl_data_device_add_listener(data_device, &data_device_listener, context); + context->data_device = data_device; + + // Second roundtrip for seat and output listeners + wl_display_roundtrip(context->display); + assert(context->keyboard != NULL); + + if (menu->output_name && !context->output) { + fprintf(stderr, "Output %s not found\n", menu->output_name); + exit(EXIT_FAILURE); + } + + context->surface = wl_compositor_create_surface(context->compositor); + wl_surface_add_listener(context->surface, &surface_listener, context); + + struct zwlr_layer_surface_v1 *layer_surface = zwlr_layer_shell_v1_get_layer_surface( + context->layer_shell, + context->surface, + context->output ? context->output->output : NULL, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + "menu" + ); + assert(layer_surface != NULL); + context->layer_surface = layer_surface; + + uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (menu->bottom) { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + } else { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + } + + zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); + zwlr_layer_surface_v1_set_size(layer_surface, 0, menu->height); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, true); + zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, context); + + wl_surface_commit(context->surface); + wl_display_roundtrip(context->display); + menu_render_items(menu); + + struct pollfd fds[] = { + { wl_display_get_fd(context->display), POLLIN }, + { context->keyboard->repeat_timer, POLLIN }, + }; + const size_t nfds = sizeof(fds) / sizeof(*fds); + + while (!menu->exit) { + errno = 0; + do { + if (wl_display_flush(context->display) == -1 && errno != EAGAIN) { + fprintf(stderr, "wl_display_flush: %s\n", strerror(errno)); + break; + } + } while (errno == EAGAIN); + + if (poll(fds, nfds, -1) < 0) { + fprintf(stderr, "poll: %s\n", strerror(errno)); + break; + } + + if (fds[0].revents & POLLIN) { + if (wl_display_dispatch(context->display) < 0) { + menu->exit = true; + } + } + + if (fds[1].revents & POLLIN) { + keyboard_repeat(context->keyboard); + } + + // Render the menu if necessary + if (!menu->rendered) { + render_menu(menu); + } + } + + context_destroy(context); + menu->context = NULL; + + if (menu->failure) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/wmenu/wayland.h b/wmenu/wayland.h new file mode 100644 index 0000000..c5d7ce5 --- /dev/null +++ b/wmenu/wayland.h @@ -0,0 +1,19 @@ +#ifndef WMENU_WAYLAND_H +#define WMENU_WAYLAND_H + +#include "menu.h" +#include + +struct wl_context; + +int menu_run(struct menu *menu); + +int context_get_scale(struct wl_context *context); +struct pool_buffer *context_get_current_buffer(struct wl_context *context); +struct pool_buffer *context_get_next_buffer(struct wl_context *context, int scale); +struct wl_surface *context_get_surface(struct wl_context *context); +struct xkb_state *context_get_xkb_state(struct wl_context *context); +struct xdg_activation_v1 *context_get_xdg_activation(struct wl_context *context); +bool context_paste(struct wl_context *context); + +#endif diff --git a/wmenu/wmenu-run.c b/wmenu/wmenu-run.c new file mode 100644 index 0000000..1b7b8c1 --- /dev/null +++ b/wmenu/wmenu-run.c @@ -0,0 +1,78 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include + +#include "menu.h" +#include "wayland.h" +#include "xdg-activation-v1-client-protocol.h" + +static void read_items(struct menu *menu) { + char *path = strdup(getenv("PATH")); + for (char *p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) { + DIR *dir = opendir(p); + if (dir == NULL) { + continue; + } + for (struct dirent *ent = readdir(dir); ent != NULL; ent = readdir(dir)) { + if (ent->d_name[0] == '.') { + continue; + } + menu_add_item(menu, strdup(ent->d_name)); + } + closedir(dir); + } + menu_sort_and_deduplicate(menu); + free(path); +} + +struct command { + struct menu *menu; + char *text; + bool exit; +}; + +static void activation_token_done(void *data, struct xdg_activation_token_v1 *activation_token, + const char *token) { + struct command *cmd = data; + xdg_activation_token_v1_destroy(activation_token); + + int pid = fork(); + if (pid == 0) { + setenv("XDG_ACTIVATION_TOKEN", token, true); + char *argv[] = {"/bin/sh", "-c", cmd->text, NULL}; + execvp(argv[0], (char**)argv); + } else { + if (cmd->exit) { + cmd->menu->exit = true; + } + } +} + +static const struct xdg_activation_token_v1_listener activation_token_listener = { + .done = activation_token_done, +}; + +static void exec_item(struct menu *menu, char *text, bool exit) { + struct command *cmd = calloc(1, sizeof(struct command)); + cmd->menu = menu; + cmd->text = strdup(text); + cmd->exit = exit; + + struct xdg_activation_v1 *activation = context_get_xdg_activation(menu->context); + struct xdg_activation_token_v1 *activation_token = xdg_activation_v1_get_activation_token(activation); + xdg_activation_token_v1_set_surface(activation_token, context_get_surface(menu->context)); + xdg_activation_token_v1_add_listener(activation_token, &activation_token_listener, cmd); + xdg_activation_token_v1_commit(activation_token); +} + +int main(int argc, char *argv[]) { + struct menu *menu = menu_create(exec_item); + menu_getopts(menu, argc, argv); + read_items(menu); + int status = menu_run(menu); + menu_destroy(menu); + return status; +} diff --git a/wmenu/wmenu.c b/wmenu/wmenu.c new file mode 100644 index 0000000..38e78b9 --- /dev/null +++ b/wmenu/wmenu.c @@ -0,0 +1,35 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include + +#include "menu.h" +#include "wayland.h" + +static void read_items(struct menu *menu) { + char buf[sizeof menu->input]; + while (fgets(buf, sizeof buf, stdin)) { + char *p = strchr(buf, '\n'); + if (p) { + *p = '\0'; + } + menu_add_item(menu, strdup(buf)); + } +} + +static void print_item(struct menu *menu, char *text, bool exit) { + puts(text); + fflush(stdout); + if (exit) { + menu->exit = true; + } +} + +int main(int argc, char *argv[]) { + struct menu *menu = menu_create(print_item); + menu_getopts(menu, argc, argv); + read_items(menu); + int status = menu_run(menu); + menu_destroy(menu); + return status; +}