diff options
author | Leonard Kugis <leonard@kug.is> | 2025-05-23 11:41:09 +0000 |
---|---|---|
committer | Leonard Kugis <leonard@kug.is> | 2025-05-23 11:41:09 +0000 |
commit | c70505d7c7b7b48600f273357694b56ccf5d2a15 (patch) | |
tree | 21c27ac6ffced8d6d904e35bdb39baa5d685d829 | |
download | dotfiles-c70505d7c7b7b48600f273357694b56ccf5d2a15.tar.gz dotfiles-c70505d7c7b7b48600f273357694b56ccf5d2a15.tar.bz2 dotfiles-c70505d7c7b7b48600f273357694b56ccf5d2a15.zip |
338 files changed, 41506 insertions, 0 deletions
@@ -0,0 +1,25 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +alias ls='ls --color=auto' +alias grep='grep --color=auto' + +export PS1="\w > " +export PATH=${PATH}:~/.local/bin:~/.cargo/bin +#export QT_QPA_PLATFORMTHEME=qt6ct + +if [ -z "${XDG_RUNTIME_DIR}" ]; then + export XDG_RUNTIME_DIR=/tmp/$(id -u)-runtime-dir + if ! [ -d "${XDG_RUNTIME_DIR}" ]; then + mkdir "${XDG_RUNTIME_DIR}" + chmod 0700 "${XDG_RUNTIME_DIR}" + fi +fi + +if [ -z "${WAYLAND_DISPLAY}" ] && [ "$(tty)" = "/dev/tty1" ]; then + dwl -s .local/bin/dwl-startup.sh +fi diff --git a/.config/fontconfig/fonts.conf b/.config/fontconfig/fonts.conf new file mode 100644 index 0000000..1d71995 --- /dev/null +++ b/.config/fontconfig/fonts.conf @@ -0,0 +1,22 @@ +<?xml version='1.0'?> +<!DOCTYPE fontconfig SYSTEM 'fonts.dtd'> +<fontconfig> + <alias> + <family>sans-serif</family> + <prefer> + <family>IBM Plex Sans</family> + </prefer> + </alias> + <alias> + <family>serif</family> + <prefer> + <family>IBM Plex Serif</family> + </prefer> + </alias> + <alias> + <family>monospace</family> + <prefer> + <family>IBM Plex Mono</family> + </prefer> + </alias> +</fontconfig> diff --git a/.config/fontconfig/fonts.conf.back b/.config/fontconfig/fonts.conf.back new file mode 100644 index 0000000..48974f8 --- /dev/null +++ b/.config/fontconfig/fonts.conf.back @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> +<fontconfig> + <match target="font"> + <edit name="antialias" mode="assign"> + <bool>true</bool> + </edit> + <edit name="hinting" mode="assign"> + <bool>false</bool> + </edit> + <edit name="hintstyle" mode="assign"> + <const>hintnone</const> + </edit> + <edit name="rgba" mode="assign"> + <const>none</const> + </edit> + <edit name="autohint" mode="assign"> + <bool>false</bool> + </edit> + <edit name="lcdfilter" mode="assign"> + <const>lcdnone</const> + </edit> + <edit name="dpi" mode="assign"> + <double>102</double> + </edit> + </match> +</fontconfig> diff --git a/.config/foot/foot.ini b/.config/foot/foot.ini new file mode 100644 index 0000000..90cc346 --- /dev/null +++ b/.config/foot/foot.ini @@ -0,0 +1,275 @@ +# -*- conf -*- + +# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd) +# term=foot (or xterm-256color if built with -Dterminfo=disabled) +# login-shell=no + +# app-id=foot # globally set wayland app-id. Default values are "foot" and "footclient" for desktop and server mode +# title=foot +# locked-title=no + +font=Terminus:size=14 +# font-bold=<bold variant of regular font> +# font-italic=<italic variant of regular font> +# font-bold-italic=<bold+italic variant of regular font> +# font-size-adjustment=0.5 +# line-height=<font metrics> +# letter-spacing=0 +# horizontal-letter-offset=0 +# vertical-letter-offset=0 +# underline-offset=<font metrics> +# underline-thickness=<font underline thickness> +# strikeout-thickness=<font strikeout thickness> +# box-drawings-uses-font-glyphs=no +# dpi-aware=no +# gamma-correct-blending=no + +# initial-window-size-pixels=700x500 # Or, +# initial-window-size-chars=<COLSxROWS> +# initial-window-mode=windowed +# pad=0x0 # optionally append 'center' +# resize-by-cells=yes +# resize-keep-grid=yes +# resize-delay-ms=100 + +# bold-text-in-bright=no +# word-delimiters=,│`|:"'()[]{}<> +# selection-target=primary +# workers=<number of logical CPUs> +# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux) +# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD) + +[environment] +# name=value + +[security] +# osc52=enabled # disabled|copy-enabled|paste-enabled|enabled + +[bell] +# system=yes +# urgent=no +# notify=no +# visual=no +# command= +# command-focused=no + +[desktop-notifications] +# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body} +# command-action-argument=--action ${action-name}=${action-label} +# close="" +# inhibit-when-focused=yes + + +[scrollback] +# lines=1000 +# multiplier=3.0 +# indicator-position=relative +# indicator-format="" + +[url] +# launch=xdg-open ${url} +# label-letters=sadfjklewcmpgh +# osc8-underline=url-mode +# regex=(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')+([0-9a-zA-Z/#@$&*+=~_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')) + +# You can define your own regex's, by adding a section called +# 'regex:<ID>' with a 'regex' and 'launch' key. These can then be tied +# to a key-binding. See foot.ini(5) for details + +# [regex:your-fancy-name] +# regex=<a POSIX-Extended Regular Expression> +# launch=<path to script or application> ${match} +# +# [key-bindings] +# regex-launch=[your-fancy-name] Control+Shift+q +# regex-copy=[your-fancy-name] Control+Alt+Shift+q + +[cursor] +# style=block +# color=<inverse foreground/background> +# blink=no +# blink-rate=500 +# beam-thickness=1.5 +# underline-thickness=<font underline thickness> + +[mouse] +# hide-when-typing=no +# alternate-scroll-mode=yes + +[touch] +# long-press-delay=400 + +[colors] +# alpha=1.0 +# alpha-mode=default # Can be `default`, `matching` or `all` +# background=242424 +# foreground=ffffff +# flash=7f7f00 +# flash-alpha=0.5 + +## Normal/regular colors (color palette 0-7) +# regular0=242424 # black +# regular1=f62b5a # red +# regular2=47b413 # green +# regular3=e3c401 # yellow +# regular4=24acd4 # blue +# regular5=f2affd # magenta +# regular6=13c299 # cyan +# regular7=e6e6e6 # white + +## Bright colors (color palette 8-15) +# bright0=616161 # bright black +# bright1=ff4d51 # bright red +# bright2=35d450 # bright green +# bright3=e9e836 # bright yellow +# bright4=5dc5f8 # bright blue +# bright5=feabf2 # bright magenta +# bright6=24dfc4 # bright cyan +# bright7=ffffff # bright white + +## dimmed colors (see foot.ini(5) man page) +# dim0=<not set> +# ... +# dim7=<not-set> + +## The remaining 256-color palette +# 16 = <256-color palette #16> +# ... +# 255 = <256-color palette #255> + +## Sixel colors +# sixel0 = 000000 +# sixel1 = 3333cc +# sixel2 = cc2121 +# sixel3 = 33cc33 +# sixel4 = cc33cc +# sixel5 = 33cccc +# sixel6 = cccc33 +# sixel7 = 878787 +# sixel8 = 424242 +# sixel9 = 545499 +# sixel10 = 994242 +# sixel11 = 549954 +# sixel12 = 995499 +# sixel13 = 549999 +# sixel14 = 999954 +# sixel15 = cccccc + +## Misc colors +# selection-foreground=<inverse foreground/background> +# selection-background=<inverse foreground/background> +# jump-labels=<regular0> <regular3> # black-on-yellow +# scrollback-indicator=<regular0> <bright4> # black-on-bright-blue +# search-box-no-match=<regular0> <regular1> # black-on-red +# search-box-match=<regular0> <regular3> # black-on-yellow +# urls=<regular3> + +[csd] +# preferred=server +# size=26 +# font=<primary font> +# color=<foreground color> +# hide-when-maximized=no +# double-click-to-maximize=yes +# border-width=0 +# border-color=<csd.color> +# button-width=26 +# button-color=<background color> +# button-minimize-color=<regular4> +# button-maximize-color=<regular2> +# button-close-color=<regular1> + +[key-bindings] +# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up +# scrollback-up-half-page=none +# scrollback-up-line=none +# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down +# scrollback-down-half-page=none +# scrollback-down-line=none +# scrollback-home=none +# scrollback-end=none +# clipboard-copy=Control+Shift+c XF86Copy +# clipboard-paste=Control+Shift+v XF86Paste +# primary-paste=Shift+Insert +# search-start=Control+Shift+r +# font-increase=Control+plus Control+equal Control+KP_Add +# font-decrease=Control+minus Control+KP_Subtract +# font-reset=Control+0 Control+KP_0 +# spawn-terminal=Control+Shift+n +# minimize=none +# maximize=none +# fullscreen=none +# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none +# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none +# pipe-selected=[xargs -r firefox] none +# pipe-command-output=[wl-copy] none # Copy last command's output to the clipboard +# show-urls-launch=Control+Shift+o +# show-urls-copy=none +# show-urls-persistent=none +# prompt-prev=Control+Shift+z +# prompt-next=Control+Shift+x +# unicode-input=Control+Shift+u +# noop=none +# quit=none + +[search-bindings] +# cancel=Control+g Control+c Escape +# commit=Return KP_Enter +# find-prev=Control+r +# find-next=Control+s +# cursor-left=Left Control+b +# cursor-left-word=Control+Left Mod1+b +# cursor-right=Right Control+f +# cursor-right-word=Control+Right Mod1+f +# cursor-home=Home Control+a +# cursor-end=End Control+e +# delete-prev=BackSpace +# delete-prev-word=Mod1+BackSpace Control+BackSpace +# delete-next=Delete +# delete-next-word=Mod1+d Control+Delete +# delete-to-start=Control+u +# delete-to-end=Control+k +# extend-char=Shift+Right +# extend-to-word-boundary=Control+w Control+Shift+Right +# extend-to-next-whitespace=Control+Shift+w +# extend-line-down=Shift+Down +# extend-backward-char=Shift+Left +# extend-backward-to-word-boundary=Control+Shift+Left +# extend-backward-to-next-whitespace=none +# extend-line-up=Shift+Up +# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste +# primary-paste=Shift+Insert +# unicode-input=none +# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up +# scrollback-up-half-page=none +# scrollback-up-line=none +# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down +# scrollback-down-half-page=none +# scrollback-down-line=none +# scrollback-home=none +# scrollback-end=none + +[url-bindings] +# cancel=Control+g Control+c Control+d Escape +# toggle-url-visible=t + +[text-bindings] +# \x03=Mod4+c # Map Super+c -> Ctrl+c + +[mouse-bindings] +# scrollback-up-mouse=BTN_WHEEL_BACK +# scrollback-down-mouse=BTN_WHEEL_FORWARD +# font-increase=Control+BTN_WHEEL_BACK +# font-decrease=Control+BTN_WHEEL_FORWARD +# selection-override-modifiers=Shift +# primary-paste=BTN_MIDDLE +# select-begin=BTN_LEFT +# select-begin-block=Control+BTN_LEFT +# select-extend=BTN_RIGHT +# select-extend-character-wise=Control+BTN_RIGHT +# select-word=BTN_LEFT-2 +# select-word-whitespace=Control+BTN_LEFT-2 +# select-quote = BTN_LEFT-3 +# select-row=BTN_LEFT-4 + +# vim: ft=dosini diff --git a/.config/gtk-3.0/settings.ini b/.config/gtk-3.0/settings.ini new file mode 100644 index 0000000..9942d70 --- /dev/null +++ b/.config/gtk-3.0/settings.ini @@ -0,0 +1,5 @@ +[Settings] +gtk-icon-theme-name=Breeze:dark +gtk-theme-name=Breeze:dark +gtk-font-name=Terminus 11 +gtk-application-prefer-dark-theme=true diff --git a/.config/qt6ct/qt6ct.conf b/.config/qt6ct/qt6ct.conf new file mode 100644 index 0000000..65cfb16 --- /dev/null +++ b/.config/qt6ct/qt6ct.conf @@ -0,0 +1,32 @@ +[Appearance] +color_scheme_path=/usr/share/qt6ct/colors/darker.conf +custom_palette=true +icon_theme=breeze-dark +standard_dialogs=default +style=Fusion + +[Fonts] +fixed="Terminus,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular" +general="Terminus,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular" + +[Interface] +activate_item_on_single_click=1 +buttonbox_layout=0 +cursor_flash_time=1200 +dialog_buttons_have_icons=1 +double_click_interval=400 +gui_effects=@Invalid() +keyboard_scheme=2 +menus_have_icons=true +show_shortcuts_in_context_menus=true +stylesheets=@Invalid() +toolbutton_style=4 +underline_shortcut=1 +wheel_scroll_lines=3 + +[SettingsWindow] +geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\0\0\0\x4\x1b\0\0\x4\x15\0\0\0\0\0\0\0\0\0\0\x4\x1b\0\0\x4\x15\0\0\0\0\0\0\0\0\a\x80\0\0\0\0\0\0\0\0\0\0\x4\x1b\0\0\x4\x15) + +[Troubleshooting] +force_raster_widgets=1 +ignored_applications=@Invalid() diff --git a/.config/systemd/user/default.target.wants/pipewire-pulse.service b/.config/systemd/user/default.target.wants/pipewire-pulse.service new file mode 120000 index 0000000..f631f69 --- /dev/null +++ b/.config/systemd/user/default.target.wants/pipewire-pulse.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire-pulse.service
\ No newline at end of file diff --git a/.config/systemd/user/default.target.wants/pipewire.service b/.config/systemd/user/default.target.wants/pipewire.service new file mode 120000 index 0000000..47f171d --- /dev/null +++ b/.config/systemd/user/default.target.wants/pipewire.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire.service
\ No newline at end of file diff --git a/.config/systemd/user/default.target.wants/rclone@kugis.service b/.config/systemd/user/default.target.wants/rclone@kugis.service new file mode 120000 index 0000000..4dc3b50 --- /dev/null +++ b/.config/systemd/user/default.target.wants/rclone@kugis.service @@ -0,0 +1 @@ +/home/lk/.config/systemd/user/rclone@.service
\ No newline at end of file diff --git a/.config/systemd/user/rclone@.service b/.config/systemd/user/rclone@.service new file mode 100644 index 0000000..1c77377 --- /dev/null +++ b/.config/systemd/user/rclone@.service @@ -0,0 +1,23 @@ +[Unit] +Description=rclone: Remote FUSE filesystem for cloud storage config %i +Documentation=man:rclone(1) +After=network-online.target +Wants=network-online.target + +[Service] +Type=notify +ExecStartPre=-/usr/bin/mkdir -p %h/mnt/%i +ExecStart= \ + /usr/bin/rclone mount \ + --config=%h/.config/rclone/rclone.conf \ + --vfs-cache-mode full \ + --vfs-cache-max-size 1000M \ + --log-level INFO \ + --log-file /tmp/rclone-%i.log \ + --umask 007 \ + --allow-other \ + %i: %h/mnt/%i +ExecStop=/bin/fusermount -u %h/mnt/%i + +[Install] +WantedBy=default.target
\ No newline at end of file diff --git a/.config/systemd/user/sockets.target.wants/pipewire-pulse.socket b/.config/systemd/user/sockets.target.wants/pipewire-pulse.socket new file mode 120000 index 0000000..45f6209 --- /dev/null +++ b/.config/systemd/user/sockets.target.wants/pipewire-pulse.socket @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire-pulse.socket
\ No newline at end of file diff --git a/.config/systemd/user/sockets.target.wants/pipewire.socket b/.config/systemd/user/sockets.target.wants/pipewire.socket new file mode 120000 index 0000000..d871d81 --- /dev/null +++ b/.config/systemd/user/sockets.target.wants/pipewire.socket @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire.socket
\ No newline at end of file diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc new file mode 100644 index 0000000..1d35efa --- /dev/null +++ b/.config/waybar/config.jsonc @@ -0,0 +1,196 @@ +{ + "layer": "top", + "position": "top", + "height": 30, + "spacing": 1, + "margin": 0, + "modules-left": ["sway/workspaces", "sway/mode", "custom/weather", "custom/quote"], + "modules-center": [], //battery //disk // uptime //updates // systray + "modules-right": ["clock","pulseaudio", "backlight", "network", "cpu", "memory", "temperature", "battery", "disk", "custom/uptime", "custom/updates", "tray"], + + + "sway/workspaces": { + "disable-scroll": true, + "all-outputs": true, + "format": "{name}", + "format-icons": { + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "6": "", + "7": "", + "8": "", + "9": "", + "10": "" + }, + "persistent_workspaces": { + "1": [], + "2": [], + "3": [], + "4": [], + "5": [] + } + }, + + "sway/mode": { + "format": "<span style=\"italic\">{}</span>" + }, + + "custom/playerctl": { + "format": " {}", + "return-type": "json", + "max-length": 40, + "exec": "playerctl -a metadata --format '{\"text\": \"{{artist}} - {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{artist}} - {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F", + "on-click": "playerctl play-pause", + "on-click-right": "playerctl next", + }, + + "custom/weather": { + "exec": "curl 'https://wttr.in/?format=1'", + "interval": 3600, + "format": "{}", + "tooltip": true + }, + + "custom/quote": { + "format": " {}", + "interval": 3600, + "exec": "fortune -s", + "on-click": "fortune | yad --text-info --width=400 --height=200 --title='Fortune'", + "tooltip": true + }, + + "custom/updates": { + "format": " {}", + "exec": "checkupdates | wc -l", + "interval": 3600, + "on-click": "foot -e sudo pacman -Syu", + "signal": 8 + }, + + "custom/uptime": { + "format": " {}", + "exec": "uptime -p | sed 's/up //; s/ days/d/; s/ hours/h/; s/ minutes/m/'", + "interval": 60 + }, + + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "tooltip": true + }, + + "clock": { + "format": " {:%H:%M}", + "format-alt": " {:%Y-%m-%d}", + "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>", + "calendar": { + "mode" : "month", + "mode-mon-col" : 3, + "weeks-pos" : "right", + "on-scroll" : 1, + "on-click-right": "mode", + "format": { + "months": "<span color='#d3c6aa'><b>{}</b></span>", + "days": "<span color='#e67e80'>{}</span>", + "weeks": "<span color='#a7c080'><b>W{}</b></span>", + "weekdays": "<span color='#7fbbb3'><b>{}</b></span>", + "today": "<span color='#dbbc7f'><b><u>{}</u></b></span>" + } + }, + "actions": { + "on-click-right": "mode", + "on-click-forward": "tz_up", + "on-click-backward": "tz_down", + "on-scroll-up": "shift_up", + "on-scroll-down": "shift_down" + } + }, + + "cpu": { + "format": " {usage}%", + "tooltip": true, + "interval": 1, + "on-click": "foot -e htop" + }, + + "memory": { + "format": " {}%", + "interval": 1, + "on-click": "foot -e htop" + }, + + "temperature": { + "critical-threshold": 80, + "format": "{icon} {temperatureC}°C", + "format-icons": ["", "", ""], + "on-click": "foot -e s-tui" + }, + + "battery": { + "states": { + "good": 95, + "warning": 30, + "critical": 15 + }, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-alt": "{icon} {time}", + "format-icons": ["", "", "", "", ""] + }, + + "network": { + "format-wifi": " {essid} ({signalStrength}%)", + "format-ethernet": " {ifname}", + "format-linked": " {ifname} (No IP)", + "format-disconnected": "⚠ Disconnected", + "format-alt": "{ifname}: {ipaddr}/{cidr}", + "tooltip-format": "{ifname}: {ipaddr}", + "on-click": "foot -e nmtui" + }, + + "pulseaudio": { + "format": "{icon} {volume}%", + "format-bluetooth": " {volume}%", + "format-bluetooth-muted": " {icon}", + "format-muted": "", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "pavucontrol", + "on-click-right": "pactl set-sink-mute @DEFAULT_SINK@ toggle", + "on-scroll-up": "pactl set-sink-volume @DEFAULT_SINK@ +2%", + "on-scroll-down": "pactl set-sink-volume @DEFAULT_SINK@ -2%" + }, + + "backlight": { + "format": "{icon} {percent}%", + "format-icons": ["", "", ""], + "on-scroll-up": "brightnessctl set +5%", + "on-scroll-down": "brightnessctl set 5%-" + }, + + "disk": { + "interval": 30, + "format": " {percentage_used}%", + "path": "/", + "on-click": "foot -e gdu /" + }, + + "tray": { + "icon-size": 18, + "spacing": 5 + } +} diff --git a/.config/waybar/style.css b/.config/waybar/style.css new file mode 100644 index 0000000..84dbdf5 --- /dev/null +++ b/.config/waybar/style.css @@ -0,0 +1,301 @@ +/*Colorschemes, there are Gruvbox, Tokyo Night, and Everforest by default. The way it works is whichever colorscheme section is defined last will be used. Here everforest is last, but put any one you want to use as the last one.*/ + + + +/* Tokyo Night */ +@define-color background #1a1b26; +@define-color background-light #24283b; +@define-color foreground #c0caf5; +@define-color black #15161e; +@define-color red #f7768e; +@define-color green #9ece6a; +@define-color yellow #e0af68; +@define-color blue #7aa2f7; +@define-color magenta #bb9af7; +@define-color cyan #7dcfff; +@define-color white #a9b1d6; +@define-color orange #ff9e64; + + +/* Everforest Dark Colors */ +@define-color background #2b3339; +@define-color background-light #323c41; +@define-color foreground #d3c6aa; +@define-color black #3c474d; +@define-color red #e67e80; +@define-color green #a7c080; +@define-color yellow #dbbc7f; +@define-color blue #7fbbb3; +@define-color magenta #d699b6; +@define-color cyan #83c092; +@define-color white #d3c6aa; +@define-color orange #e69875; + +/* Pastel TTY Colors */ +@define-color background #212121; +@define-color background-light #3a3a3a; +@define-color foreground #e0e0e0; +@define-color black #5a5a5a; +@define-color red #ff9a9e; +@define-color green #b5e8a9; +@define-color yellow #ffe6a7; +@define-color blue #63a4ff; +@define-color magenta #dda0dd; +@define-color cyan #a3e8e8; +@define-color white #ffffff; +@define-color orange #ff8952; + + + +/* Gruvbox Dark Colors */ +@define-color background #282828; +@define-color background-light #3c3836; +@define-color foreground #ebdbb2; +@define-color black #32302f; +@define-color red #cc241d; +@define-color green #98971a; +@define-color yellow #d79921; +@define-color blue #458588; +@define-color magenta #b16286; +@define-color cyan #689d6a; +@define-color white #ebdbb2; +@define-color orange #d65d0e; + + + +/* Module-specific colors */ +@define-color workspaces-color @foreground; +@define-color workspaces-focused-bg @green; +@define-color workspaces-focused-fg @cyan; +@define-color workspaces-urgent-bg @red; +@define-color workspaces-urgent-fg @black; + +/* Text and border colors for modules */ +@define-color mode-color @orange; +@define-color mpd-color @magenta; +@define-color weather-color @magenta; +@define-color playerctl-color @magenta; +@define-color clock-color @blue; +@define-color cpu-color @green; +@define-color memory-color @magenta; +@define-color temperature-color @yellow; +@define-color temperature-critical-color @red; +@define-color battery-color @cyan; +@define-color battery-charging-color @green; +@define-color battery-warning-color @yellow; +@define-color battery-critical-color @red; +@define-color network-color @blue; +@define-color network-disconnected-color @red; +@define-color pulseaudio-color @orange; +@define-color pulseaudio-muted-color @red; +@define-color backlight-color @yellow; +@define-color disk-color @cyan; +@define-color uptime-color @green; +@define-color updates-color @orange; +@define-color quote-color @green; +@define-color idle-inhibitor-color @foreground; +@define-color idle-inhibitor-active-color @red; + +* { + /* Base styling for all modules */ + border: none; + border-radius: 0; + font-family: "FontAwesome", "Terminus", "Roboto", "sans-serif"; + font-size: 14px; + min-height: 0; +} + +window#waybar { + background-color: @background; + color: @foreground; +} + + + +/* Common module styling with border-bottom */ +#mode, #mpd, #custom-weather, #custom-playerctl, #clock, #cpu, +#memory, #temperature, #battery, #network, #pulseaudio, +#backlight, #disk, #custom-uptime, #custom-updates, #custom-quote, +#idle_inhibitor, #tray { + padding: 0 10px; + margin: 0 2px; + border-bottom: 2px solid transparent; + background-color: transparent; +} + +/* Workspaces styling */ +#workspaces button { + padding: 0 10px; + background-color: transparent; + color: @workspaces-color; + margin: 0; +} + +#workspaces button:hover { + background: @background-light; + box-shadow: inherit; +} + +#workspaces button.focused { + box-shadow: inset 0 -2px @workspaces-focused-fg; + color: @workspaces-focused-fg; + font-weight: 900; +} + +#workspaces button.urgent { + background-color: @workspaces-urgent-bg; + color: @workspaces-urgent-fg; +} + +/* Module-specific styling */ +#mode { + color: @mode-color; + border-bottom-color: @mode-color; +} + +#mpd { + color: @mpd-color; + border-bottom-color: @mpd-color; +} + +#mpd.disconnected, +#mpd.stopped { + color: @foreground; + border-bottom-color: transparent; +} + +#custom-weather { + color: @weather-color; + border-bottom-color: @weather-color; +} + +#custom-playerctl { + color: @playerctl-color; + border-bottom-color: @playerctl-color; +} + +#custom-playerctl.Playing { + color: @cyan; + border-bottom-color: @cyan; +} + +#custom-playerctl.Paused { + color: @orange; + border-bottom-color: @orange; +} + +#clock { + color: @clock-color; + border-bottom-color: @clock-color; +} + +#cpu { + color: @cpu-color; + border-bottom-color: @cpu-color; +} + +#memory { + color: @memory-color; + border-bottom-color: @memory-color; +} + +#temperature { + color: @temperature-color; + border-bottom-color: @temperature-color; +} + +#temperature.critical { + color: @temperature-critical-color; + border-bottom-color: @temperature-critical-color; +} + +#battery { + color: @battery-color; + border-bottom-color: @battery-color; +} + +#battery.charging, #battery.plugged { + color: @battery-charging-color; + border-bottom-color: @battery-charging-color; +} + +#battery.warning:not(.charging) { + color: @battery-warning-color; + border-bottom-color: @battery-warning-color; +} + +#battery.critical:not(.charging) { + color: @battery-critical-color; + border-bottom-color: @battery-critical-color; +} + +#network { + color: @network-color; + border-bottom-color: @network-color; +} + +#network.disconnected { + color: @network-disconnected-color; + border-bottom-color: @network-disconnected-color; +} + +#pulseaudio { + color: @pulseaudio-color; + border-bottom-color: @pulseaudio-color; +} + +#pulseaudio.muted { + color: @pulseaudio-muted-color; + border-bottom-color: @pulseaudio-muted-color; +} + +#backlight { + color: @backlight-color; + border-bottom-color: @backlight-color; +} + +#disk { + color: @disk-color; + border-bottom-color: @disk-color; +} + +#custom-uptime { + color: @uptime-color; + border-bottom-color: @uptime-color; +} + +#custom-updates { + color: @updates-color; + border-bottom-color: @updates-color; +} + +#custom-quote { + color: @quote-color; + border-bottom-color: @quote-color; +} + +#idle_inhibitor { + color: @idle-inhibitor-color; + border-bottom-color: transparent; +} + +#idle_inhibitor.activated { + color: @idle-inhibitor-active-color; + border-bottom-color: @idle-inhibitor-active-color; +} + +#tray { + background-color: transparent; + padding: 0 10px; + margin: 0 2px; +} + +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; + color: @red; + border-bottom-color: @red; +} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..abdd979 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dwl"] + path = dwl + url = lk@kug.is:git/gwl.git diff --git a/dwl b/dwl new file mode 160000 +Subproject 9fa9a6305223f0548ae5664b9be16e39ba59eeb diff --git a/dwl-patches/README.md b/dwl-patches/README.md new file mode 100644 index 0000000..b16f982 --- /dev/null +++ b/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...<branch-name> --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 instead of 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-patches/patches/accessnthmon/README.md b/dwl-patches/patches/accessnthmon/README.md new file mode 100644 index 0000000..b7cd6a9 --- /dev/null +++ b/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-patches/patches/accessnthmon/accessnthmon.patch b/dwl-patches/patches/accessnthmon/accessnthmon.patch new file mode 100644 index 0000000..f69f57d --- /dev/null +++ b/dwl-patches/patches/accessnthmon/accessnthmon.patch @@ -0,0 +1,122 @@ +From 5f531bfb1387ded7b8817faf7df760d3b998742b Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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 <time.h> + #include <unistd.h> + #include <wayland-server-core.h> ++#include <wayland-util.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> + #include <wlr/render/allocator.h> +@@ -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-patches/patches/alwayscenter/README.md b/dwl-patches/patches/alwayscenter/README.md new file mode 100644 index 0000000..63d6f86 --- /dev/null +++ b/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-patches/patches/alwayscenter/alwayscenter.patch b/dwl-patches/patches/alwayscenter/alwayscenter.patch new file mode 100644 index 0000000..a046ef7 --- /dev/null +++ b/dwl-patches/patches/alwayscenter/alwayscenter.patch @@ -0,0 +1,38 @@ +From 6616470ef135019ef4c767003a66df76df45f53e Mon Sep 17 00:00:00 2001 +From: Guido Cella <guido@guidocella.xyz> +Date: Wed, 5 Jun 2024 12:05:16 +0200 +Subject: [PATCH] center floating windows + +--- + dwl.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 6f041a0..79ace52 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -472,6 +472,10 @@ 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; ++ } + setmon(c, mon, newtags); + } + +@@ -1677,6 +1681,10 @@ mapnotify(struct wl_listener *listener, void *data) + * try to apply rules for them */ + 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.45.1 + diff --git a/dwl-patches/patches/attachbottom/README.md b/dwl-patches/patches/attachbottom/README.md new file mode 100644 index 0000000..80e12a0 --- /dev/null +++ b/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-patches/patches/attachbottom/attachbottom.patch b/dwl-patches/patches/attachbottom/attachbottom.patch new file mode 100644 index 0000000..470a79b --- /dev/null +++ b/dwl-patches/patches/attachbottom/attachbottom.patch @@ -0,0 +1,29 @@ +From 0dda3ed8634154fd3887b71133b451d66a11b61d Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/attachfocused/README.md b/dwl-patches/patches/attachfocused/README.md new file mode 100644 index 0000000..a4f6049 --- /dev/null +++ b/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-patches/patches/attachfocused/attachfocused.patch b/dwl-patches/patches/attachfocused/attachfocused.patch new file mode 100644 index 0000000..2b8cd38 --- /dev/null +++ b/dwl-patches/patches/attachfocused/attachfocused.patch @@ -0,0 +1,29 @@ +From d03851c14073874f5b3d19a095e184dc24d219cd Mon Sep 17 00:00:00 2001 +From: MayOrMayNotBeACat <maybeacat804@gmail.com> +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-patches/patches/attachtop/README.md b/dwl-patches/patches/attachtop/README.md new file mode 100644 index 0000000..3762f1a --- /dev/null +++ b/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) +- [2024-04-23](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/attachtop/attachtop.patch) + +### Authors +- [Nikita Ivanov](https://codeberg.org/nikitaivanov) diff --git a/dwl-patches/patches/attachtop/attachtop.patch b/dwl-patches/patches/attachtop/attachtop.patch new file mode 100644 index 0000000..78a77ca --- /dev/null +++ b/dwl-patches/patches/attachtop/attachtop.patch @@ -0,0 +1,36 @@ +From 42a66d6e06c38e913766ce625f049fdbc3dd0d12 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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..c0a3d74 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) || c->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.44.0 + diff --git a/dwl-patches/patches/autostart/README.md b/dwl-patches/patches/autostart/README.md new file mode 100644 index 0000000..1fd26d3 --- /dev/null +++ b/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-patches/patches/autostart/autostart-0.7.patch b/dwl-patches/patches/autostart/autostart-0.7.patch new file mode 100644 index 0000000..12e6d7e --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/autostart/autostart.patch b/dwl-patches/patches/autostart/autostart.patch new file mode 100644 index 0000000..71d8718 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/bar-systray/README.md b/dwl-patches/patches/bar-systray/README.md new file mode 100644 index 0000000..b504173 --- /dev/null +++ b/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-patches/patches/bar-systray/bar-systray-0.7.patch b/dwl-patches/patches/bar-systray/bar-systray-0.7.patch new file mode 100644 index 0000000..1dda4c6 --- /dev/null +++ b/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 <vetu104@proton.me> +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 <dbus/dbus.h> ++#include <stdlib.h> ++#include <wayland-server-core.h> ++ ++#include <fcntl.h> ++#include <stddef.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <unistd.h> ++ ++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 <dbus/dbus.h> ++#include <wayland-server-core.h> ++ ++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 <dbus/dbus.h> + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -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 <dbus/dbus.h> ++ ++#include <errno.h> ++#include <stddef.h> ++ ++// 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 <dbus/dbus.h> ++ ++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 <fcft/fcft.h> ++#include <pixman.h> ++ ++#include <ctype.h> ++#include <stdint.h> ++#include <stdlib.h> ++#include <string.h> ++ ++#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 <fcft/fcft.h> ++#include <pixman.h> ++ ++#include <stddef.h> ++#include <stdint.h> ++ ++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 <dbus/dbus.h> ++ ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++ ++// 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 <wayland-util.h> ++ ++/* ++ * 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 <dbus/dbus.h> ++#include <wayland-server-core.h> ++#include <wayland-util.h> ++ ++#include <errno.h> ++#include <signal.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <sys/types.h> ++#include <sys/wait.h> ++#include <time.h> ++#include <unistd.h> ++ ++// 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 <dbus/dbus.h> ++#include <wayland-server-core.h> ++ ++/* 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 <fcft/fcft.h> ++#include <pixman.h> ++#include <wayland-util.h> ++ ++#include <stddef.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++ ++#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 <pixman.h> ++#include <wayland-util.h> ++ ++#include <stdint.h> ++ ++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 <dbus/dbus.h> ++#include <wayland-util.h> ++ ++#include <errno.h> ++#include <stdio.h> ++#include <string.h> ++ ++// 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 = ++ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" ++ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" ++ "<node>\n" ++ " <interface name=\"" DBUS_INTERFACE_PROPERTIES ++ "\">\n" ++ " <method name=\"Get\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" ++ " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" ++ " </method>\n" ++ " <method name=\"GetAll\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n" ++ " </method>\n" ++ " <method name=\"Set\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" ++ " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n" ++ " </method>\n" ++ " <signal name=\"PropertiesChanged\">\n" ++ " <arg type=\"s\" name=\"interface_name\"/>\n" ++ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" ++ " <arg type=\"as\" name=\"invalidated_properties\"/>\n" ++ " </signal>\n" ++ " </interface>\n" ++ " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE ++ "\">\n" ++ " <method name=\"Introspect\">\n" ++ " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n" ++ " </method>\n" ++ " </interface>\n" ++ " <interface name=\"" DBUS_INTERFACE_PEER ++ "\">\n" ++ " <method name=\"Ping\"/>\n" ++ " <method name=\"GetMachineId\">\n" ++ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" ++ " </method>\n" ++ " </interface>\n" ++ " <interface name=\"" SNW_IFACE ++ "\">\n" ++ " <!-- methods -->\n" ++ " <method name=\"RegisterStatusNotifierItem\">\n" ++ " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" ++ " </method>\n" ++ " <!-- properties -->\n" ++ " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" ++ " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" ++ " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" ++ " <!-- signals -->\n" ++ " <signal name=\"StatusNotifierHostRegistered\">\n" ++ " </signal>\n" ++ " </interface>\n" ++ "</node>\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 <dbus/dbus.h> ++#include <wayland-server-core.h> ++#include <wayland-util.h> ++ ++/* ++ * 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-patches/patches/bar/README.md b/dwl-patches/patches/bar/README.md new file mode 100644 index 0000000..aac598a --- /dev/null +++ b/dwl-patches/patches/bar/README.md @@ -0,0 +1,33 @@ +### 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
+- [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.
+
+
+
+### Authors
+- [sewn](https://codeberg.org/sewn)
+
+### Credits
+- [MadcowOG](https://github.com/MadcowOG)
+- [kolumni](https://github.com/kolunmi/dwlb)
+
diff --git a/dwl-patches/patches/bar/bar-0.6.patch b/dwl-patches/patches/bar/bar-0.6.patch new file mode 100644 index 0000000..70c9f12 --- /dev/null +++ b/dwl-patches/patches/bar/bar-0.6.patch @@ -0,0 +1,1245 @@ +From 85e40afc2ad8acba453ce8c57233542e340c1c2b Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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 <sewn@disroot.org> ++ * Copyright (c) 2024 notchoc <notchoc@disroot.org> ++ * ++ * 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 <bjoern@hoehrmann.de> ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include <stdlib.h> ++#include <fcft/fcft.h> ++#include <pixman-1/pixman.h> ++ ++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 <libinput.h> + #include <linux/input-event-codes.h> + #include <math.h> ++#include <libdrm/drm_fourcc.h> + #include <signal.h> + #include <stdio.h> + #include <stdlib.h> +@@ -57,6 +58,7 @@ + #include <wlr/types/wlr_xdg_decoration_v1.h> + #include <wlr/types/wlr_xdg_output_v1.h> + #include <wlr/types/wlr_xdg_shell.h> ++#include <wlr/interfaces/wlr_buffer.h> + #include <wlr/util/log.h> + #include <wlr/util/region.h> + #include <xkbcommon/xkbcommon.h> +@@ -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-patches/patches/bar/bar-0.7.patch b/dwl-patches/patches/bar/bar-0.7.patch new file mode 100644 index 0000000..523ca36 --- /dev/null +++ b/dwl-patches/patches/bar/bar-0.7.patch @@ -0,0 +1,1245 @@ +From 1431cf1e9e03c8e59050af3b37514a6a2293d71d Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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 <sewn@disroot.org> ++ * Copyright (c) 2024 notchoc <notchoc@disroot.org> ++ * ++ * 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 <bjoern@hoehrmann.de> ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include <stdlib.h> ++#include <fcft/fcft.h> ++#include <pixman-1/pixman.h> ++ ++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 <libinput.h> + #include <linux/input-event-codes.h> + #include <math.h> ++#include <libdrm/drm_fourcc.h> + #include <signal.h> + #include <stdio.h> + #include <stdlib.h> +@@ -58,6 +59,7 @@ + #include <wlr/types/wlr_xdg_decoration_v1.h> + #include <wlr/types/wlr_xdg_output_v1.h> + #include <wlr/types/wlr_xdg_shell.h> ++#include <wlr/interfaces/wlr_buffer.h> + #include <wlr/util/log.h> + #include <wlr/util/region.h> + #include <xkbcommon/xkbcommon.h> +@@ -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-patches/patches/bar/bar.png b/dwl-patches/patches/bar/bar.png Binary files differnew file mode 100644 index 0000000..276256b --- /dev/null +++ b/dwl-patches/patches/bar/bar.png diff --git a/dwl-patches/patches/barborder/README.md b/dwl-patches/patches/barborder/README.md new file mode 100644 index 0000000..6eef3ba --- /dev/null +++ b/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-patches/patches/barborder/barborder.patch b/dwl-patches/patches/barborder/barborder.patch new file mode 100644 index 0000000..106a2ac --- /dev/null +++ b/dwl-patches/patches/barborder/barborder.patch @@ -0,0 +1,136 @@ +From 7d95ce0fba8f172748bbd71b4c03ce12acd54eea Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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-patches/patches/barcolors/README.md b/dwl-patches/patches/barcolors/README.md new file mode 100644 index 0000000..640961b --- /dev/null +++ b/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-patches/patches/barcolors/barcolors.patch b/dwl-patches/patches/barcolors/barcolors.patch new file mode 100644 index 0000000..b95f78e --- /dev/null +++ b/dwl-patches/patches/barcolors/barcolors.patch @@ -0,0 +1,144 @@ +From d2b529d9ebee6b2e625afd5c89c2ede5bb0ca91b Mon Sep 17 00:00:00 2001 +From: Kerberoge <sjoerdenjh@gmail.com> +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-patches/patches/barheight/README.md b/dwl-patches/patches/barheight/README.md new file mode 100644 index 0000000..4dad736 --- /dev/null +++ b/dwl-patches/patches/barheight/README.md @@ -0,0 +1,11 @@ +### Description
+
+Adds the ability to change the [bar's](https://codeberg.org/dwl/dwl-patches/wiki/bar) height.
+
+### Download
+- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/barheight/barheight.patch) (bar 0.7)
+- [git branch](https://codeberg.org/Oak/dwl/src/branch/barheight)
+
+### Authors
+- [Oak](https://codeberg.org/oak) + diff --git a/dwl-patches/patches/barheight/barheight.patch b/dwl-patches/patches/barheight/barheight.patch new file mode 100644 index 0000000..4e28691 --- /dev/null +++ b/dwl-patches/patches/barheight/barheight.patch @@ -0,0 +1,38 @@ +From d2f3ac840845802eaf9ff7daf406f04722fd02aa Mon Sep 17 00:00:00 2001 +From: Oak <Oak@petrifiedoak.com> +Date: Sun, 25 Aug 2024 17:43:17 +0200 +Subject: [PATCH] Implement barheight patch + +--- + config.def.h | 1 + + dwl.c | 2 +- + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..f11089c 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 user_bh = 30; /* 0 means that dwl will calculate barheight, >= 1 means dwl will use user_bh as the bar height. */ + 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"}; +diff --git a/dwl.c b/dwl.c +index ece537a..2863202 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -3183,7 +3183,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 = user_bh ? user_bh : m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); + } + +-- +2.46.0 + diff --git a/dwl-patches/patches/barpadding/README.md b/dwl-patches/patches/barpadding/README.md new file mode 100644 index 0000000..1d0f17b --- /dev/null +++ b/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-patches/patches/barpadding/barpadding.patch b/dwl-patches/patches/barpadding/barpadding.patch new file mode 100644 index 0000000..e5cc185 --- /dev/null +++ b/dwl-patches/patches/barpadding/barpadding.patch @@ -0,0 +1,73 @@ +From f01cea73042155e856b2f41452724fe5c895eee4 Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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-patches/patches/bartruecenteredtitle/README.md b/dwl-patches/patches/bartruecenteredtitle/README.md new file mode 100644 index 0000000..94bfcd9 --- /dev/null +++ b/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.<br>Requires [the bar patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar) to be applied beforehand. + + + +### Download +- [v0.7/v0.6](/dwl/dwl-patches/raw/branch/main/patches/bartruecenteredtitle/bartruecenteredtitle.patch)<br>Works on both v0.7 and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) +- [sewn](https://codeberg.org/sewn) diff --git a/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch b/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch new file mode 100644 index 0000000..ce02a51 --- /dev/null +++ b/dwl-patches/patches/bartruecenteredtitle/bartruecenteredtitle.patch @@ -0,0 +1,46 @@ +From 17501c9f28226b1f332d6842be0d7f50ba618a29 Mon Sep 17 00:00:00 2001 +From: moonsabre <moonsabre@tuta.io> +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-patches/patches/bartruecenteredtitle/centeredtitle.webp b/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp Binary files differnew file mode 100644 index 0000000..b1ad696 --- /dev/null +++ b/dwl-patches/patches/bartruecenteredtitle/centeredtitle.webp diff --git a/dwl-patches/patches/borders/README.md b/dwl-patches/patches/borders/README.md new file mode 100644 index 0000000..b046307 --- /dev/null +++ b/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.
+
+
+
+<details>
+<summary>Preview</summary>
+<pre>
+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`:
+<img src="https://i.imgur.com/msead2K.png"/>
+
+and `border_color_type` set to `BrdStart`:
+<img src="https://i.imgur.com/ssgPG36.png"/>
+
+and `border_color_type` set to `BrdEnd`:
+<img src="https://i.imgur.com/i2Xtjy6.png"/>
+
+and `border_color_type` set to `BrdStartEnd`:
+<img src="https://i.imgur.com/fnkitdR.png"/>
+</pre>
+</details>
+
+### 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-patches/patches/borders/borders.patch b/dwl-patches/patches/borders/borders.patch new file mode 100644 index 0000000..c8cc847 --- /dev/null +++ b/dwl-patches/patches/borders/borders.patch @@ -0,0 +1,213 @@ +From b12cfff672f0705d8259cf26b3a574faa5ca43ae Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/bottomstack/README.md b/dwl-patches/patches/bottomstack/README.md new file mode 100644 index 0000000..fa9ede6 --- /dev/null +++ b/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-patches/patches/bottomstack/bottomstack.patch b/dwl-patches/patches/bottomstack/bottomstack.patch new file mode 100644 index 0000000..c2b9331 --- /dev/null +++ b/dwl-patches/patches/bottomstack/bottomstack.patch @@ -0,0 +1,140 @@ +From b352fb08f40b1ee2d8c4748be4922df711e3aaa9 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <wlr/types/wlr_xdg_decoration_v1.h> + #include <wlr/types/wlr_xdg_output_v1.h> + #include <wlr/types/wlr_xdg_shell.h> ++#include <wlr/util/box.h> + #include <wlr/util/log.h> + #include <wlr/util/region.h> + #include <xkbcommon/xkbcommon.h> +@@ -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-patches/patches/btrtile/README.md b/dwl-patches/patches/btrtile/README.md new file mode 100644 index 0000000..4766aa7 --- /dev/null +++ b/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. + + + +--- + +# 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-patches/patches/btrtile/btrtile-v0.7-gaps.patch b/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch new file mode 100644 index 0000000..90354d1 --- /dev/null +++ b/dwl-patches/patches/btrtile/btrtile-v0.7-gaps.patch @@ -0,0 +1,922 @@ +From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001 +From: julmajustus <julmajustus@tutanota.com> +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 <jmakkone@student.hive.fi> +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* 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 <limits.h> + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -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-patches/patches/btrtile/btrtile-v0.7.patch b/dwl-patches/patches/btrtile/btrtile-v0.7.patch new file mode 100644 index 0000000..b85473e --- /dev/null +++ b/dwl-patches/patches/btrtile/btrtile-v0.7.patch @@ -0,0 +1,903 @@ +From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 +From: julmajustus <julmajustus@tutanota.com> +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 <jmakkone@student.hive.fi> +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* 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 <limits.h> + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -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-patches/patches/buttonbystate/README.md b/dwl-patches/patches/buttonbystate/README.md new file mode 100644 index 0000000..cbb0eee --- /dev/null +++ b/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-patches/patches/buttonbystate/buttonbystate.patch b/dwl-patches/patches/buttonbystate/buttonbystate.patch new file mode 100644 index 0000000..d669aff --- /dev/null +++ b/dwl-patches/patches/buttonbystate/buttonbystate.patch @@ -0,0 +1,78 @@ +From 4141aa9455e4b4a5b4a235475c70e8c100ec663e Mon Sep 17 00:00:00 2001 +From: nullsystem <nullsystem.aongp@slmail.me> +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-patches/patches/center-terminal/README.md b/dwl-patches/patches/center-terminal/README.md new file mode 100644 index 0000000..cfd70a2 --- /dev/null +++ b/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-patches/patches/center-terminal/center-terminal.patch b/dwl-patches/patches/center-terminal/center-terminal.patch new file mode 100644 index 0000000..53e27e1 --- /dev/null +++ b/dwl-patches/patches/center-terminal/center-terminal.patch @@ -0,0 +1,102 @@ +From dfaafc1624438d2157cbb15b496e8177544491e0 Mon Sep 17 00:00:00 2001 +From: Guido Cella <guido@guidocella.xyz> +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 | 19 +++++++++++++++++++ + 2 files changed, 20 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 ad21e1b..b126b32 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -8,6 +8,7 @@ + #include <signal.h> + #include <stdio.h> + #include <stdlib.h> ++#include <strings.h> + #include <sys/wait.h> + #include <time.h> + #include <unistd.h> +@@ -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,8 @@ applyrules(Client *c) + } + } + } ++ if (!strcasecmp(appid, termcmd[0])) ++ c->centered = true; + setmon(c, mon, newtags); + } + +@@ -2720,6 +2727,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; +@@ -2732,6 +2744,13 @@ tile(Monitor *m) + } + } + ++void ++togglecenter(const Arg *arg) ++{ ++ center = !center; ++ tile(selmon); ++} ++ + void + togglefloating(const Arg *arg) + { +-- +2.47.1 + diff --git a/dwl-patches/patches/centeredmaster/README.md b/dwl-patches/patches/centeredmaster/README.md new file mode 100644 index 0000000..2ba6dd9 --- /dev/null +++ b/dwl-patches/patches/centeredmaster/README.md @@ -0,0 +1,33 @@ +### Description
+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 || ||
+|| || || || || || || ||
+|+--------++--------++--------+| |+--------++--------++--------+|
++------------------------------+ +------------------------------+
+```
+
+
+### 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-patches/patches/centeredmaster/centeredmaster.patch b/dwl-patches/patches/centeredmaster/centeredmaster.patch new file mode 100644 index 0000000..c29bd5f --- /dev/null +++ b/dwl-patches/patches/centeredmaster/centeredmaster.patch @@ -0,0 +1,135 @@ +From b1ca46930756b59c1ebba0b8c7871b85ff84f62f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/cfact-centeredmaster/README.md b/dwl-patches/patches/cfact-centeredmaster/README.md new file mode 100644 index 0000000..5141eb1 --- /dev/null +++ b/dwl-patches/patches/cfact-centeredmaster/README.md @@ -0,0 +1,13 @@ +### Description +Port of the cfact patch for the centeredmaster layout. + +Inspired by the original patch for dwm (https://dwm.suckless.org/patches/cfacts/) + +This patch requires both [cfact](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/cfact) and [centeredmaster](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/centeredmaster) patches. + +### Download + + - [2024-06-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/cfact-centeredmaster/cfact-centeredmaster.patch) + +### Authors +- [acadmendes](https://codeberg.org/acadmendes) diff --git a/dwl-patches/patches/cfact-centeredmaster/cfact-centeredmaster.patch b/dwl-patches/patches/cfact-centeredmaster/cfact-centeredmaster.patch new file mode 100644 index 0000000..663e5ff --- /dev/null +++ b/dwl-patches/patches/cfact-centeredmaster/cfact-centeredmaster.patch @@ -0,0 +1,83 @@ +From e3ad25b5149df936155cb51927f16648a9838bc0 Mon Sep 17 00:00:00 2001 +From: estevao <estevao.mendes@acad.ufsm.br> +Date: Thu, 25 Jul 2024 13:20:50 -0300 +Subject: [PATCH] cfact patch for centeredmaster layout + +--- + dwl.c | 42 ++++++++++++++++++++++++++++++++++++++---- + 1 file changed, 38 insertions(+), 4 deletions(-) + +diff --git a/dwl.c b/dwl.c +index 91c1511..37732c0 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -654,6 +656,7 @@ void + centeredmaster(Monitor *m) + { + unsigned int h, mw, mx, my, oty, ety, tw; ++ float mweight = 0, ltweight = 0, rtweight = 0; + int i, n; + Client *c; + +@@ -672,7 +675,7 @@ centeredmaster(Monitor *m) + + if (n > m->nmaster) { + /* go mfact box in the center if more than nmaster clients */ +- mw = ROUND(m->nmaster ? m->w.width * m->mfact : 0); ++ mw = roundf(m->nmaster ? m->w.width * m->mfact : 0); + tw = m->w.width - mw; + + if (n - m->nmaster > 1) { +@@ -682,6 +685,20 @@ centeredmaster(Monitor *m) + } + } + ++ 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 if ( (i - m->nmaster)%2 ){ ++ ltweight += c->cweight; ++ }else{ ++ rtweight += c->cweight; ++ } ++ i++; ++ } ++ + i = 0; + oty = 0; + ety = 0; +@@ -691,22 +708,24 @@ centeredmaster(Monitor *m) + 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); ++ h = (m->w.height - my)*(c->cweight/mweight); + resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + my, .width = mw, + .height = h}, 0); + my += c->geom.height; ++ mweight -= c->cweight; + } else { + /* stack clients are stacked vertically */ + if ((i - m->nmaster) % 2) { +- h = (m->w.height - ety) / ( (1 + n - i) / 2); ++ h = (m->w.height - ety)*(c->cweight/ltweight); + resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ety, .width = tw, + .height = h}, 0); + ety += c->geom.height; ++ ltweight -= c->cweight; + } else { +- h = (m->w.height - oty) / ((1 + n - i) / 2); ++ h = (m->w.height - oty)*(c->cweight/rtweight); + 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; ++ rtweight -= c->cweight; + } + } + i++; +-- +2.45.2 + diff --git a/dwl-patches/patches/cfact-snail/README.md b/dwl-patches/patches/cfact-snail/README.md new file mode 100644 index 0000000..488ab98 --- /dev/null +++ b/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-patches/patches/cfact-snail/cfact-snail.patch b/dwl-patches/patches/cfact-snail/cfact-snail.patch new file mode 100644 index 0000000..213c3fd --- /dev/null +++ b/dwl-patches/patches/cfact-snail/cfact-snail.patch @@ -0,0 +1,68 @@ +From 9f8109182a7d173d2a2cb30c089a7e7b9ffe0a5e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/cfact/README.md b/dwl-patches/patches/cfact/README.md new file mode 100644 index 0000000..166b4e2 --- /dev/null +++ b/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-patches/patches/cfact/cfact-v0.6.patch b/dwl-patches/patches/cfact/cfact-v0.6.patch new file mode 100644 index 0000000..c1505fb --- /dev/null +++ b/dwl-patches/patches/cfact/cfact-v0.6.patch @@ -0,0 +1,121 @@ +From 52d8ed5ece7c96ea02441faaa4da2b0f51c8ebc4 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/cfact/cfact-v0.7-gaps.patch b/dwl-patches/patches/cfact/cfact-v0.7-gaps.patch new file mode 100644 index 0000000..e080238 --- /dev/null +++ b/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 <git@gavinm.us> +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-patches/patches/cfact/cfact-v0.7.patch b/dwl-patches/patches/cfact/cfact-v0.7.patch new file mode 100644 index 0000000..7d28a8b --- /dev/null +++ b/dwl-patches/patches/cfact/cfact-v0.7.patch @@ -0,0 +1,121 @@ +From e82e15860c36a70539625b8fe7b4bd54d0721705 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/cfact/cfact.patch b/dwl-patches/patches/cfact/cfact.patch new file mode 100644 index 0000000..c91d3c0 --- /dev/null +++ b/dwl-patches/patches/cfact/cfact.patch @@ -0,0 +1,121 @@ +From 98fe302cd240b519c28c886250273854844ab2c7 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/chainkeys/README.md b/dwl-patches/patches/chainkeys/README.md new file mode 100644 index 0000000..fa6d185 --- /dev/null +++ b/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-patches/patches/chainkeys/chainkeys.patch b/dwl-patches/patches/chainkeys/chainkeys.patch new file mode 100644 index 0000000..a0daefd --- /dev/null +++ b/dwl-patches/patches/chainkeys/chainkeys.patch @@ -0,0 +1,157 @@ +From 226e204ec7fb6d6840a984ef8e8ec1d2514e985f Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/client-opacity/README.md b/dwl-patches/patches/client-opacity/README.md new file mode 100644 index 0000000..ed82d81 --- /dev/null +++ b/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-patches/patches/client-opacity/client-opacity.patch b/dwl-patches/patches/client-opacity/client-opacity.patch new file mode 100644 index 0000000..da2faf7 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/column/README.md b/dwl-patches/patches/column/README.md new file mode 100644 index 0000000..9d1b205 --- /dev/null +++ b/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-patches/patches/column/column.patch b/dwl-patches/patches/column/column.patch new file mode 100644 index 0000000..0f6a531 --- /dev/null +++ b/dwl-patches/patches/column/column.patch @@ -0,0 +1,79 @@ +From d1eb2061c619d0bbd7a0ecda0fe77409f3a6c399 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/coredump/README.md b/dwl-patches/patches/coredump/README.md new file mode 100644 index 0000000..5300890 --- /dev/null +++ b/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-patches/patches/coredump/coredump-0.7.patch b/dwl-patches/patches/coredump/coredump-0.7.patch new file mode 100644 index 0000000..f39e7af --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <signal.h> + #include <stdio.h> + #include <stdlib.h> ++#include <sys/resource.h> + #include <sys/wait.h> + #include <time.h> + #include <unistd.h> +@@ -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-patches/patches/coredump/coredump.patch b/dwl-patches/patches/coredump/coredump.patch new file mode 100644 index 0000000..98ffbd8 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <signal.h> + #include <stdio.h> + #include <stdlib.h> ++#include <sys/resource.h> + #include <sys/wait.h> + #include <time.h> + #include <unistd.h> +@@ -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-patches/patches/cursortheme/README.md b/dwl-patches/patches/cursortheme/README.md new file mode 100644 index 0000000..ac94800 --- /dev/null +++ b/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-patches/patches/cursortheme/cursortheme.patch b/dwl-patches/patches/cursortheme/cursortheme.patch new file mode 100644 index 0000000..3c34222 --- /dev/null +++ b/dwl-patches/patches/cursortheme/cursortheme.patch @@ -0,0 +1,46 @@ +From f08376a2a04929a3907612e6c1f980ad3cdf939f Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/customfloat/README.md b/dwl-patches/patches/customfloat/README.md new file mode 100644 index 0000000..9f2bda5 --- /dev/null +++ b/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.
+
+<details>
+<summary>Explanation of center_relative_to_monitor:</summary>
+<pre>
+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.
+<img src="https://i.imgur.com/xhejzPh.png"/>
+</pre>
+</details>
+
+### 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-patches/patches/customfloat/customfloat.patch b/dwl-patches/patches/customfloat/customfloat.patch new file mode 100644 index 0000000..d295571 --- /dev/null +++ b/dwl-patches/patches/customfloat/customfloat.patch @@ -0,0 +1,93 @@ +From 4f19f5499610d56f2616da5d44039403ac9d4c06 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/deck/README.md b/dwl-patches/patches/deck/README.md new file mode 100644 index 0000000..419f7a4 --- /dev/null +++ b/dwl-patches/patches/deck/README.md @@ -0,0 +1,34 @@ +### Description +Adds a layout with a monocle layout for clients in the stack (port of the [deck layout for dwm](https://dwm.suckless.org/patches/deck/)); stacked clients are like a deck of cards (see below) + +``` +Tile: ++-----------------+--------+ +| | | +| | S1 | +| | | +| M +--------+ +| | | +| | S2 | +| | | ++-----------------+--------+ + +Deck: ++-----------------+--------+ +| | | +| | | +| | | +| M | S1 | +| | | +| | | +| | | ++-----------------+--------+ +``` + +### Download +- [git branch](https://codeberg.org/anabasis/dwl/src/branch/deck) +- [2024-05-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/deck/deck.patch) + +### Authors +- [anabasis](https://codeberg.org/anabasis) +- [Palanix](https://codeberg.org/Palanix) diff --git a/dwl-patches/patches/deck/deck.patch b/dwl-patches/patches/deck/deck.patch new file mode 100644 index 0000000..e9cb720 --- /dev/null +++ b/dwl-patches/patches/deck/deck.patch @@ -0,0 +1,87 @@ +From d56f732d3b5bba4ea0bdf56a91d0992b0cb25bfb Mon Sep 17 00:00:00 2001 +From: anabasis <anabasis@noreply.codeberg.org> +Date: Fri, 10 May 2024 13:45:33 -0400 +Subject: [PATCH] add deck layout + +--- + config.def.h | 2 ++ + dwl.c | 36 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8f498d2..9238da0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -33,6 +33,7 @@ static const Layout layouts[] = { + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, ++ { "[D]", deck }, + }; + + /* 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_r, 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 bf763df..1a7ce8e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -267,6 +267,7 @@ static void createpointerconstraint(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 deck(Monitor *m); + 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); +@@ -1080,6 +1081,41 @@ cursorwarptohint(void) + } + } + ++void ++deck(Monitor *m) ++{ ++ unsigned int mw, my; ++ 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 ? 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->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; ++ } else { ++ resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y, ++ .width = m->w.width - mw, .height = m->w.height}, 0); ++ if (c == focustop(m)) ++ wlr_scene_node_raise_to_top(&c->scene->node); ++ } ++ i++; ++ } ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +-- +2.45.0 + diff --git a/dwl-patches/patches/define-modkey-with-make-argument/README.md b/dwl-patches/patches/define-modkey-with-make-argument/README.md new file mode 100644 index 0000000..2fac947 --- /dev/null +++ b/dwl-patches/patches/define-modkey-with-make-argument/README.md @@ -0,0 +1,18 @@ +### Description
+This patch adds the ability to define the modkey with a make argument like so:
+
+```
+make MODKEY=WLR_MODIFIER_ALT
+make MODKEY=WLR_MODIFIER_LOGO
+make MODKEY=WLR_MODIFIER_CTRL
+make MODKEY=WLR_MODIFIER_SHIFT
+```
+
+It can be used to compile multiple times quickly, you can also have a main session and sub session with different modkeys.
+
+### Download
+- [git branch](https://codeberg.org/Abanoub/dwl/src/branch/define-modkey-patch)
+- [2024-02-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/define-modkey-with-make-argument/define-modkey-with-make-argument.patch)
+
+### Authors
+- [Abanoub](https://codeberg.org/Abanoub)
diff --git a/dwl-patches/patches/define-modkey-with-make-argument/define-modkey-with-make-argument.patch b/dwl-patches/patches/define-modkey-with-make-argument/define-modkey-with-make-argument.patch new file mode 100644 index 0000000..86faf11 --- /dev/null +++ b/dwl-patches/patches/define-modkey-with-make-argument/define-modkey-with-make-argument.patch @@ -0,0 +1,49 @@ +From ec6a6a4fe56ef5bdf45633966345f0d0338776d5 Mon Sep 17 00:00:00 2001 +From: Abanoub <abanoubsameh@protonmail.com> +Date: Fri, 19 Jan 2024 21:42:42 +0200 +Subject: [PATCH] Add the ability to define a MODKEY as a make argument + +--- + Makefile | 6 +++++- + config.def.h | 4 +++- + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/Makefile b/Makefile +index 0822ddc..d169a97 100644 +--- a/Makefile ++++ b/Makefile +@@ -13,6 +13,10 @@ PKGS = wlroots wayland-server xkbcommon libinput $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(LIBS) + ++ifneq ($(MODKEY),) ++MODKEYVAL = -DMODKEY=$(MODKEY) ++endif ++ + all: dwl + dwl: dwl.o util.o + $(CC) dwl.o util.o $(LDLIBS) $(LDFLAGS) $(DWLCFLAGS) -o $@ +@@ -63,4 +67,4 @@ uninstall: + + .SUFFIXES: .c .o + .c.o: +- $(CC) $(CPPFLAGS) $(DWLCFLAGS) -c $< ++ $(CC) $(CPPFLAGS) $(DWLCFLAGS) $(MODKEYVAL) -c $< +diff --git a/config.def.h b/config.def.h +index 9009517..b1e53cd 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -102,7 +102,9 @@ 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 ++#ifndef MODKEY ++# define MODKEY WLR_MODIFIER_ALT ++#endif + + #define TAGKEYS(KEY,SKEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ +-- +2.43.0 + diff --git a/dwl-patches/patches/dim-unfocused/README.md b/dwl-patches/patches/dim-unfocused/README.md new file mode 100644 index 0000000..dad3ef0 --- /dev/null +++ b/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-patches/patches/dim-unfocused/dim-unfocused-20230516.patch b/dwl-patches/patches/dim-unfocused/dim-unfocused-20230516.patch new file mode 100644 index 0000000..88504cd --- /dev/null +++ b/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-patches/patches/dim-unfocused/dim-unfocused-20240416.patch b/dwl-patches/patches/dim-unfocused/dim-unfocused-20240416.patch new file mode 100644 index 0000000..701c450 --- /dev/null +++ b/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-patches/patches/dim-unfocused/dim-unfocused-20240714.patch b/dwl-patches/patches/dim-unfocused/dim-unfocused-20240714.patch new file mode 100644 index 0000000..0b60d60 --- /dev/null +++ b/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-patches/patches/dim-unfocused/dim-unfocused-20240903.patch b/dwl-patches/patches/dim-unfocused/dim-unfocused-20240903.patch new file mode 100644 index 0000000..fc48b0b --- /dev/null +++ b/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-patches/patches/dim-unfocused/dim-unfocused.patch b/dwl-patches/patches/dim-unfocused/dim-unfocused.patch new file mode 100644 index 0000000..f73d886 --- /dev/null +++ b/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-patches/patches/disable-keybindings-on-fullscreen/README.md b/dwl-patches/patches/disable-keybindings-on-fullscreen/README.md new file mode 100644 index 0000000..9c438bb --- /dev/null +++ b/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-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch b/dwl-patches/patches/disable-keybindings-on-fullscreen/disable-keybindings-on-fullscreen.patch new file mode 100644 index 0000000..57a18e1 --- /dev/null +++ b/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 <ju7t1xe@gmail.com> +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-patches/patches/dragmfact/README.md b/dwl-patches/patches/dragmfact/README.md new file mode 100644 index 0000000..3112d44 --- /dev/null +++ b/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-patches/patches/dragmfact/dragmfact-v0.6.patch b/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch new file mode 100644 index 0000000..2435cbf --- /dev/null +++ b/dwl-patches/patches/dragmfact/dragmfact-v0.6.patch @@ -0,0 +1,59 @@ +From aeee958aec3e0774f3ea8baefe028e1a8cc2d2ce Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/dragmfact/dragmfact-v0.7.patch b/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch new file mode 100644 index 0000000..f0ea5d3 --- /dev/null +++ b/dwl-patches/patches/dragmfact/dragmfact-v0.7.patch @@ -0,0 +1,59 @@ +From ae44bfc181fc1532d2f0c4deb20e20634050a661 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/dragmfact/dragmfact.patch b/dwl-patches/patches/dragmfact/dragmfact.patch new file mode 100644 index 0000000..e2fdbac --- /dev/null +++ b/dwl-patches/patches/dragmfact/dragmfact.patch @@ -0,0 +1,59 @@ +From 435cdf673e5a8080123109dbf874aac2ccef1498 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/dragresize/README.md b/dwl-patches/patches/dragresize/README.md new file mode 100644 index 0000000..c890ce7 --- /dev/null +++ b/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-patches/patches/dragresize/dragresize.patch b/dwl-patches/patches/dragresize/dragresize.patch new file mode 100644 index 0000000..1e4cfe0 --- /dev/null +++ b/dwl-patches/patches/dragresize/dragresize.patch @@ -0,0 +1,210 @@ +From 83ae7d4816a49f46063ab16ffecd32b2e7e61784 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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-patches/patches/en-keycodes/README.md b/dwl-patches/patches/en-keycodes/README.md new file mode 100644 index 0000000..ee3aae4 --- /dev/null +++ b/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) (<dima@dimakrasner.com>) diff --git a/dwl-patches/patches/en-keycodes/en-keycodes.patch b/dwl-patches/patches/en-keycodes/en-keycodes.patch new file mode 100644 index 0000000..445d900 --- /dev/null +++ b/dwl-patches/patches/en-keycodes/en-keycodes.patch @@ -0,0 +1,73 @@ +From cd61fac9cb6e9d0172e2f7a01e6a514d676ba5f0 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/envcfg/README.md b/dwl-patches/patches/envcfg/README.md new file mode 100644 index 0000000..07e5547 --- /dev/null +++ b/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) (<dima@dimakrasner.com>)
diff --git a/dwl-patches/patches/envcfg/envcfg.patch b/dwl-patches/patches/envcfg/envcfg.patch new file mode 100644 index 0000000..ca9328d --- /dev/null +++ b/dwl-patches/patches/envcfg/envcfg.patch @@ -0,0 +1,302 @@ +From 3c3ea42cd50bfa5111be69b3c1f71afa0443bb53 Mon Sep 17 00:00:00 2001 +From: Dima Krasner <dima@dimakrasner.com> +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-patches/patches/fakefullscreenclient/README.md b/dwl-patches/patches/fakefullscreenclient/README.md new file mode 100644 index 0000000..6a98f0d --- /dev/null +++ b/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-patches/patches/fakefullscreenclient/fakefullscreenclient.patch b/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch new file mode 100644 index 0000000..848f350 --- /dev/null +++ b/dwl-patches/patches/fakefullscreenclient/fakefullscreenclient.patch @@ -0,0 +1,87 @@ +From 2ec6d0c668b4daee601337f8da45ccfa3a7d5fc6 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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-patches/patches/fallback/README.md b/dwl-patches/patches/fallback/README.md new file mode 100644 index 0000000..e9de8f4 --- /dev/null +++ b/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) (<dima@dimakrasner.com>) diff --git a/dwl-patches/patches/fallback/fallback.patch b/dwl-patches/patches/fallback/fallback.patch new file mode 100644 index 0000000..b734625 --- /dev/null +++ b/dwl-patches/patches/fallback/fallback.patch @@ -0,0 +1,44 @@ +From a94c52af879027e654f67c36621a8c9b2f338f56 Mon Sep 17 00:00:00 2001 +From: Dima Krasner <dima@dimakrasner.com> +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-patches/patches/float-unfocused-border-color/README.md b/dwl-patches/patches/float-unfocused-border-color/README.md new file mode 100644 index 0000000..8069ca2 --- /dev/null +++ b/dwl-patches/patches/float-unfocused-border-color/README.md @@ -0,0 +1,12 @@ +### Description +A revive of the floatBorderColor patch. + +This patch allows you to set a color for floating windows when they are unfocused. + +### Download +- [git branch](https://codeberg.org/yuki-was-taken/dwl-patch/src/branch/float-unfocused-border-color/) +- [2024-06-07](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/float-unfocused-border-color/float-unfocused-border-color.patch) + +### Authors +- [yuki](https://codeberg.org/yuki-was-taken) +- [Palanix (Original Author)](https://codeberg.org/Palanix) diff --git a/dwl-patches/patches/float-unfocused-border-color/float-unfocused-border-color.patch b/dwl-patches/patches/float-unfocused-border-color/float-unfocused-border-color.patch new file mode 100644 index 0000000..fe60f3d --- /dev/null +++ b/dwl-patches/patches/float-unfocused-border-color/float-unfocused-border-color.patch @@ -0,0 +1,60 @@ +From 591c031a4d8e62acfef4ef41816c1fbbb8b1473a Mon Sep 17 00:00:00 2001 +From: yuki <yukiat@proton.me> +Date: Fri, 7 Jun 2024 11:44:37 +0800 +Subject: [PATCH] Added float-unfocused-border-color patch. + +--- + config.def.h | 1 + + dwl.c | 11 ++++++++--- + 2 files changed, 9 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index a784eb4..8131af5 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,6 +11,7 @@ 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 float floatcolor[] = 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 6f041a0..777c0e1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -632,6 +632,7 @@ buttonpress(struct wl_listener *listener, void *data) + /* 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; +@@ -1348,9 +1349,8 @@ 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_activate_surface(old, 0); ++ client_set_border_color(old_c, old_c->isfloating ? floatcolor : bordercolor); ++ client_activate_surface(old, 0); + } + } + printstatus(); +@@ -2218,6 +2218,11 @@ setfloating(Client *c, int floating) + wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen || + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); ++ if (!grabc && floating) ++ for (int i = 0; i < 4; i++) { ++ wlr_scene_rect_set_color(c->border[i], floatcolor); ++ wlr_scene_node_lower_to_bottom(&c->border[i]->node); ++ } + arrange(c->mon); + printstatus(); + } +-- +2.45.2
\ No newline at end of file diff --git a/dwl-patches/patches/focusdir/README.md b/dwl-patches/patches/focusdir/README.md new file mode 100644 index 0000000..a2ed478 --- /dev/null +++ b/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-patches/patches/focusdir/focusdir.patch b/dwl-patches/patches/focusdir/focusdir.patch new file mode 100644 index 0000000..72b8c5a --- /dev/null +++ b/dwl-patches/patches/focusdir/focusdir.patch @@ -0,0 +1,98 @@ +From a0e71a687b7fcaebdaf1da80c09bf5563bff46b1 Mon Sep 17 00:00:00 2001 +From: ldev <ldev@ldev.eu.org> +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 <limits.h> + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -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-patches/patches/follow/README.md b/dwl-patches/patches/follow/README.md new file mode 100644 index 0000000..d2597cd --- /dev/null +++ b/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.<br>No dependencies. + + + +### Download +- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/follow/follow.patch)<br>Targets tag v0.7, but also works on git master and v0.6. + +### Author +- [moonsabre](https://codeberg.org/moonsabre) diff --git a/dwl-patches/patches/follow/follow.patch b/dwl-patches/patches/follow/follow.patch new file mode 100644 index 0000000..d260a75 --- /dev/null +++ b/dwl-patches/patches/follow/follow.patch @@ -0,0 +1,38 @@ +From c4de366aa5e2a465f5e2963f4ffed7adde77929a Mon Sep 17 00:00:00 2001 +From: moonsabre <moonsabre@tuta.io> +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-patches/patches/follow/follow.webp b/dwl-patches/patches/follow/follow.webp Binary files differnew file mode 100644 index 0000000..b629315 --- /dev/null +++ b/dwl-patches/patches/follow/follow.webp diff --git a/dwl-patches/patches/foreign-toplevel-management/README.md b/dwl-patches/patches/foreign-toplevel-management/README.md new file mode 100644 index 0000000..6e30328 --- /dev/null +++ b/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-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch b/dwl-patches/patches/foreign-toplevel-management/foreign-toplevel-management.patch new file mode 100644 index 0000000..3dfa14f --- /dev/null +++ b/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 <gean.marroquin@gmail.com> +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 <wlr/types/wlr_data_device.h> + #include <wlr/types/wlr_drm.h> + #include <wlr/types/wlr_export_dmabuf_v1.h> ++#include <wlr/types/wlr_foreign_toplevel_management_v1.h> + #include <wlr/types/wlr_fractional_scale_v1.h> + #include <wlr/types/wlr_gamma_control_v1.h> + #include <wlr/types/wlr_idle_inhibit_v1.h> +@@ -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 @@ ++<?xml version="1.0" encoding="UTF-8"?> ++<protocol name="wlr_foreign_toplevel_management_unstable_v1"> ++ <copyright> ++ 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. ++ </copyright> ++ ++ <interface name="zwlr_foreign_toplevel_manager_v1" version="3"> ++ <description summary="list and control opened apps"> ++ 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 ++ </description> ++ ++ <event name="toplevel"> ++ <description summary="a toplevel has been created"> ++ 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. ++ </description> ++ <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/> ++ </event> ++ ++ <request name="stop"> ++ <description summary="stop sending events"> ++ 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. ++ </description> ++ </request> ++ ++ <event name="finished" type="destructor"> ++ <description summary="the compositor has finished with the toplevel manager"> ++ 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. ++ </description> ++ </event> ++ </interface> ++ ++ <interface name="zwlr_foreign_toplevel_handle_v1" version="3"> ++ <description summary="an opened toplevel"> ++ 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. ++ </description> ++ ++ <event name="title"> ++ <description summary="title change"> ++ This event is emitted whenever the title of the toplevel changes. ++ </description> ++ <arg name="title" type="string"/> ++ </event> ++ ++ <event name="app_id"> ++ <description summary="app-id change"> ++ This event is emitted whenever the app-id of the toplevel changes. ++ </description> ++ <arg name="app_id" type="string"/> ++ </event> ++ ++ <event name="output_enter"> ++ <description summary="toplevel entered an output"> ++ This event is emitted whenever the toplevel becomes visible on ++ the given output. A toplevel may be visible on multiple outputs. ++ </description> ++ <arg name="output" type="object" interface="wl_output"/> ++ </event> ++ ++ <event name="output_leave"> ++ <description summary="toplevel left an output"> ++ 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. ++ </description> ++ <arg name="output" type="object" interface="wl_output"/> ++ </event> ++ ++ <request name="set_maximized"> ++ <description summary="requests that the toplevel be maximized"> ++ Requests that the toplevel be maximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ </description> ++ </request> ++ ++ <request name="unset_maximized"> ++ <description summary="requests that the toplevel be unmaximized"> ++ Requests that the toplevel be unmaximized. If the maximized state actually ++ changes, this will be indicated by the state event. ++ </description> ++ </request> ++ ++ <request name="set_minimized"> ++ <description summary="requests that the toplevel be minimized"> ++ Requests that the toplevel be minimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ </description> ++ </request> ++ ++ <request name="unset_minimized"> ++ <description summary="requests that the toplevel be unminimized"> ++ Requests that the toplevel be unminimized. If the minimized state actually ++ changes, this will be indicated by the state event. ++ </description> ++ </request> ++ ++ <request name="activate"> ++ <description summary="activate the toplevel"> ++ Request that this toplevel be activated on the given seat. ++ There is no guarantee the toplevel will be actually activated. ++ </description> ++ <arg name="seat" type="object" interface="wl_seat"/> ++ </request> ++ ++ <enum name="state"> ++ <description summary="types of states on the toplevel"> ++ The different states that a toplevel can have. These have the same meaning ++ as the states with the same names defined in xdg-toplevel ++ </description> ++ ++ <entry name="maximized" value="0" summary="the toplevel is maximized"/> ++ <entry name="minimized" value="1" summary="the toplevel is minimized"/> ++ <entry name="activated" value="2" summary="the toplevel is active"/> ++ <entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/> ++ </enum> ++ ++ <event name="state"> ++ <description summary="the toplevel state changed"> ++ 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. ++ </description> ++ ++ <arg name="state" type="array"/> ++ </event> ++ ++ <event name="done"> ++ <description summary="all information about the toplevel has been sent"> ++ 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. ++ </description> ++ </event> ++ ++ <request name="close"> ++ <description summary="request that the toplevel be closed"> ++ 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. ++ </description> ++ </request> ++ ++ <request name="set_rectangle"> ++ <description summary="the rectangle which represents the toplevel"> ++ 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. ++ </description> ++ ++ <arg name="surface" type="object" interface="wl_surface"/> ++ <arg name="x" type="int"/> ++ <arg name="y" type="int"/> ++ <arg name="width" type="int"/> ++ <arg name="height" type="int"/> ++ </request> ++ ++ <enum name="error"> ++ <entry name="invalid_rectangle" value="0" ++ summary="the provided rectangle is invalid"/> ++ </enum> ++ ++ <event name="closed"> ++ <description summary="this toplevel has been destroyed"> ++ 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. ++ </description> ++ </event> ++ ++ <request name="destroy" type="destructor"> ++ <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object"> ++ 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. ++ </description> ++ </request> ++ ++ <!-- Version 2 additions --> ++ ++ <request name="set_fullscreen" since="2"> ++ <description summary="request that the toplevel be fullscreened"> ++ 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. ++ </description> ++ <arg name="output" type="object" interface="wl_output" allow-null="true"/> ++ </request> ++ ++ <request name="unset_fullscreen" since="2"> ++ <description summary="request that the toplevel be unfullscreened"> ++ Requests that the toplevel be unfullscreened. If the fullscreen state ++ actually changes, this will be indicated by the state event. ++ </description> ++ </request> ++ ++ <!-- Version 3 additions --> ++ ++ <event name="parent" since="3"> ++ <description summary="parent change"> ++ This event is emitted whenever the parent of the toplevel changes. ++ ++ No event is emitted when the parent handle is destroyed by the client. ++ </description> ++ <arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/> ++ </event> ++ </interface> ++</protocol> +-- +2.43.2 + diff --git a/dwl-patches/patches/fullscreenadaptivesync/README.md b/dwl-patches/patches/fullscreenadaptivesync/README.md new file mode 100644 index 0000000..6f7118c --- /dev/null +++ b/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-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch b/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch new file mode 100644 index 0000000..ccc012b --- /dev/null +++ b/dwl-patches/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch @@ -0,0 +1,122 @@ +From c003f450c197a0c960bbb355511f8dca7a35e3c3 Mon Sep 17 00:00:00 2001 +From: julmajustus <julmajustus@tutanota.com> +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-patches/patches/gaplessgrid/README.md b/dwl-patches/patches/gaplessgrid/README.md new file mode 100644 index 0000000..6921d54 --- /dev/null +++ b/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-patches/patches/gaplessgrid/gaplessgrid-20230801.patch b/dwl-patches/patches/gaplessgrid/gaplessgrid-20230801.patch new file mode 100644 index 0000000..2ade7da --- /dev/null +++ b/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-patches/patches/gaplessgrid/gaplessgrid-20240714.patch b/dwl-patches/patches/gaplessgrid/gaplessgrid-20240714.patch new file mode 100644 index 0000000..cf430b9 --- /dev/null +++ b/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-patches/patches/gaplessgrid/gaplessgrid.patch b/dwl-patches/patches/gaplessgrid/gaplessgrid.patch new file mode 100644 index 0000000..2af1072 --- /dev/null +++ b/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-patches/patches/gaps/README.md b/dwl-patches/patches/gaps/README.md new file mode 100644 index 0000000..4925a47 --- /dev/null +++ b/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-patches/patches/gaps/gaps.patch b/dwl-patches/patches/gaps/gaps.patch new file mode 100644 index 0000000..c025baf --- /dev/null +++ b/dwl-patches/patches/gaps/gaps.patch @@ -0,0 +1,127 @@ +From 50e3dd4746b6cb719efb9f8213b94ac52a5320d9 Mon Sep 17 00:00:00 2001 +From: peesock <kcormn@gmail.com> +Date: Mon, 24 Jun 2024 20:06:42 -0700 +Subject: [PATCH] gaps! + +Co-authored-by: sewn <sewn@disroot.org> +Co-authored-by: serenevoid <ajuph9224@gmail.com> +--- + 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-patches/patches/genericgaps/README.md b/dwl-patches/patches/genericgaps/README.md new file mode 100644 index 0000000..f27897d --- /dev/null +++ b/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-patches/patches/genericgaps/genericgaps-0.7.patch b/dwl-patches/patches/genericgaps/genericgaps-0.7.patch new file mode 100644 index 0000000..ff27889 --- /dev/null +++ b/dwl-patches/patches/genericgaps/genericgaps-0.7.patch @@ -0,0 +1,375 @@ +From a11488df97c0592486c3ce86d9c5dbddb0f88524 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/genericgaps/genericgaps.patch b/dwl-patches/patches/genericgaps/genericgaps.patch new file mode 100644 index 0000000..aa62b92 --- /dev/null +++ b/dwl-patches/patches/genericgaps/genericgaps.patch @@ -0,0 +1,350 @@ +From f2a1b84369266d252fbea57c6f1eb64253617452 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/gestures/README.md b/dwl-patches/patches/gestures/README.md new file mode 100644 index 0000000..0fbe418 --- /dev/null +++ b/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-patches/patches/gestures/gestures.patch b/dwl-patches/patches/gestures/gestures.patch new file mode 100644 index 0000000..4f74325 --- /dev/null +++ b/dwl-patches/patches/gestures/gestures.patch @@ -0,0 +1,169 @@ +From 42f97e88bd901d81b81da61c44a790b583706308 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/globalkey/README.md b/dwl-patches/patches/globalkey/README.md new file mode 100644 index 0000000..d250f92 --- /dev/null +++ b/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-patches/patches/globalkey/globalkey.patch b/dwl-patches/patches/globalkey/globalkey.patch new file mode 100644 index 0000000..07d3856 --- /dev/null +++ b/dwl-patches/patches/globalkey/globalkey.patch @@ -0,0 +1,131 @@ +From f36e3f134c9f14a9821783d9908471ed0bdca0ed Mon Sep 17 00:00:00 2001 +From: korei999 <ju7t1xe@gmail.com> +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-patches/patches/headless/README.md b/dwl-patches/patches/headless/README.md new file mode 100644 index 0000000..990a5d9 --- /dev/null +++ b/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-patches/patches/headless/headless.patch b/dwl-patches/patches/headless/headless.patch new file mode 100644 index 0000000..fb01fda --- /dev/null +++ b/dwl-patches/patches/headless/headless.patch @@ -0,0 +1,130 @@ +From 2e1123af5c7ae4354ec997d59cb36143fb2fdd27 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <unistd.h> + #include <wayland-server-core.h> + #include <wlr/backend.h> ++#include <wlr/backend/headless.h> + #include <wlr/backend/libinput.h> ++#include <wlr/backend/multi.h> ++#include <wlr/backend/wayland.h> + #include <wlr/render/allocator.h> + #include <wlr/render/wlr_renderer.h> + #include <wlr/types/wlr_compositor.h> +@@ -58,6 +61,9 @@ + #include <xcb/xcb.h> + #include <xcb/xcb_icccm.h> + #endif ++#if WLR_HAS_X11_BACKEND ++#include <wlr/backend/x11.h> ++#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-patches/patches/hide-behind-fullscreen/README.md b/dwl-patches/patches/hide-behind-fullscreen/README.md new file mode 100644 index 0000000..0b4bebe --- /dev/null +++ b/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-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch b/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen-0.7.patch new file mode 100644 index 0000000..de2dfc6 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch b/dwl-patches/patches/hide-behind-fullscreen/hide-behind-fullscreen.patch new file mode 100644 index 0000000..f82d0e6 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/hide-behind-monocle/README.md b/dwl-patches/patches/hide-behind-monocle/README.md new file mode 100644 index 0000000..507d2e1 --- /dev/null +++ b/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-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch b/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle-0.7.patch new file mode 100644 index 0000000..76b6e96 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/hide-behind-monocle/hide-behind-monocle.patch b/dwl-patches/patches/hide-behind-monocle/hide-behind-monocle.patch new file mode 100644 index 0000000..dfe23dd --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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-patches/patches/hide_vacant_tags/README.md b/dwl-patches/patches/hide_vacant_tags/README.md new file mode 100644 index 0000000..fae2a4d --- /dev/null +++ b/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-patches/patches/hide_vacant_tags/hide_vacant_tags.patch b/dwl-patches/patches/hide_vacant_tags/hide_vacant_tags.patch new file mode 100644 index 0000000..ae3966e --- /dev/null +++ b/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 <sewn@disroot.org> +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-patches/patches/hiderule/README.md b/dwl-patches/patches/hiderule/README.md new file mode 100644 index 0000000..bed2475 --- /dev/null +++ b/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-patches/patches/hiderule/hiderule.patch b/dwl-patches/patches/hiderule/hiderule.patch new file mode 100644 index 0000000..8ed1a84 --- /dev/null +++ b/dwl-patches/patches/hiderule/hiderule.patch @@ -0,0 +1,57 @@ +From fb48ec754d63b3d8e40fff2d047050675887d7f4 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell <m@minego.net> +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-patches/patches/hot-reload/README.md b/dwl-patches/patches/hot-reload/README.md new file mode 100644 index 0000000..e65e894 --- /dev/null +++ b/dwl-patches/patches/hot-reload/README.md @@ -0,0 +1,40 @@ +### 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. + +#### 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. + +### Download +- [0.7](/dwl/dwl-patches/raw/branch/main/patches/hot-reload/hot-reload-0.7.patch) +- [main 2025-02-14](/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-patches/patches/hot-reload/hot-reload-0.7.patch b/dwl-patches/patches/hot-reload/hot-reload-0.7.patch new file mode 100644 index 0000000..c8a8cce --- /dev/null +++ b/dwl-patches/patches/hot-reload/hot-reload-0.7.patch @@ -0,0 +1,770 @@ +From caa1adaf02ab4f9a326761ade5d1346149bc7c59 Mon Sep 17 00:00:00 2001 +From: Sivecano <sivecano@gmail.com> +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..70d3d0f 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 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..7e059ad 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 <dlfcn.h> ++#include <limits.h> ++#include <unistd.h> ++#include <libgen.h> ++#include <errno.h> ++ + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -67,6 +76,7 @@ + #include <xcb/xcb_icccm.h> + #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.48.1 + diff --git a/dwl-patches/patches/hot-reload/hot-reload.patch b/dwl-patches/patches/hot-reload/hot-reload.patch new file mode 100644 index 0000000..84ad9f5 --- /dev/null +++ b/dwl-patches/patches/hot-reload/hot-reload.patch @@ -0,0 +1,1009 @@ +From 79fbc2405919a049a35dd58e860b6519ebb7943b Mon Sep 17 00:00:00 2001 +From: Sivecano <sivecano@gmail.com> +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 | 475 +++++++++++++++++++++++++++++++++++++++------------ + util.c | 34 ++++ + util.h | 6 + + 5 files changed, 421 insertions(+), 118 deletions(-) + +diff --git a/Makefile b/Makefile +index 578194f..0714ed1 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 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 ec4ca86..8d1eceb 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 <dlfcn.h> ++#include <limits.h> ++#include <unistd.h> ++#include <libgen.h> ++#include <errno.h> ++ + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -68,6 +77,7 @@ + #include <xcb/xcb_icccm.h> + #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}; +@@ -449,8 +535,38 @@ static struct wl_listener xwayland_ready = {.notify = xwaylandready}; + static struct wlr_xwayland *xwayland; + #endif + ++/* undoes the shadowing of static from above */ ++#endif ++#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 ++ ++#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" +@@ -692,10 +808,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; +@@ -707,7 +825,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 */ +@@ -719,6 +837,8 @@ cleanup(void) + wlr_scene_node_destroy(&scene->tree.node); + } + ++#ifdef HOT ++ + void + cleanupmon(struct wl_listener *listener, void *data) + { +@@ -732,10 +852,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); +@@ -748,37 +868,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 + } + +@@ -905,8 +1025,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); +- free(listener); ++ UNLISTEN(listener); + } + + void +@@ -1236,8 +1355,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 +@@ -1246,8 +1365,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); +- free(listener); ++ UNLISTEN(listener); + } + + void +@@ -1256,8 +1374,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); +- free(listener); ++ UNLISTEN(listener); + } + + void +@@ -1266,9 +1383,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); +@@ -1287,9 +1404,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; +@@ -1303,7 +1420,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; +@@ -1323,23 +1440,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); + } +@@ -1354,7 +1471,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); + } + +@@ -1370,9 +1487,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); + } +@@ -1538,8 +1655,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); + +@@ -2229,6 +2346,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) + { +@@ -2268,11 +2387,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 +@@ -2288,6 +2407,9 @@ run(char *startup_cmd) + wl_display_run(dpy); + } + ++#endif ++#ifdef HOT ++ + void + setcursor(struct wl_listener *listener, void *data) + { +@@ -2434,17 +2556,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. */ +@@ -2460,7 +2584,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); +@@ -2472,7 +2596,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: +@@ -2519,24 +2643,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 +@@ -2548,19 +2672,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); +@@ -2570,10 +2694,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); + +@@ -2601,14 +2725,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 +@@ -2616,27 +2740,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 +@@ -2648,8 +2772,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 { +@@ -2658,6 +2782,9 @@ setup(void) + #endif + } + ++#endif ++#ifdef HOT ++ + void + spawn(const Arg *arg) + { +@@ -3139,8 +3266,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 +@@ -3175,17 +3302,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 +@@ -3205,3 +3456,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.48.1 + diff --git a/dwl-patches/patches/inputdevicerules/README.md b/dwl-patches/patches/inputdevicerules/README.md new file mode 100644 index 0000000..3f87ef9 --- /dev/null +++ b/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-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch b/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch new file mode 100644 index 0000000..b9e9fd2 --- /dev/null +++ b/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.6.patch @@ -0,0 +1,173 @@ +From 620a68ffbcecf996916a8fd637f0bcff7a72e004 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch b/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch new file mode 100644 index 0000000..18f6f9e --- /dev/null +++ b/dwl-patches/patches/inputdevicerules/inputdevicerules-v0.7.patch @@ -0,0 +1,173 @@ +From 89f870a04f903681b0a7a0ac4eb1ae70c4984b46 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/ipc/README.md b/dwl-patches/patches/ipc/README.md new file mode 100644 index 0000000..bf648a4 --- /dev/null +++ b/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-patches/patches/ipc/focus-tagset-output.patch b/dwl-patches/patches/ipc/focus-tagset-output.patch new file mode 100644 index 0000000..2ea3015 --- /dev/null +++ b/dwl-patches/patches/ipc/focus-tagset-output.patch @@ -0,0 +1,24 @@ +From b65477621f0438b0ed6c405f74c9d15e8fb57e96 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@disroot.org> +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-patches/patches/ipc/ipc.patch b/dwl-patches/patches/ipc/ipc.patch new file mode 100644 index 0000000..9f989e7 --- /dev/null +++ b/dwl-patches/patches/ipc/ipc.patch @@ -0,0 +1,597 @@ +From 6c6d655b68770ce82a24fde9b58c4d97b672553a Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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 <xcb/xcb_icccm.h> + #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 @@ ++<?xml version="1.0" encoding="utf-8"?> ++<!-- ++This is largely ripped from somebar's ipc patchset; just with some personal modifications. ++I would probably just submit raphi's patchset but I don't think that would be polite. ++--> ++<protocol name="dwl_ipc_unstable_v2"> ++ <description summary="inter-proccess-communication about dwl's state"> ++ 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. ++ </description> ++ ++ <interface name="zdwl_ipc_manager_v2" version="2"> ++ <description summary="manage dwl state"> ++ 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. ++ </description> ++ ++ <request name="release" type="destructor"> ++ <description summary="release dwl_ipc_manager"> ++ Indicates that the client will not the dwl_ipc_manager object anymore. ++ Objects created through this instance are not affected. ++ </description> ++ </request> ++ ++ <request name="get_output"> ++ <description summary="get a dwl_ipc_outout for a wl_output"> ++ Get a dwl_ipc_outout for the specified wl_output. ++ </description> ++ <arg name="id" type="new_id" interface="zdwl_ipc_output_v2"/> ++ <arg name="output" type="object" interface="wl_output"/> ++ </request> ++ ++ <event name="tags"> ++ <description summary="Announces tag amount"> ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all tags. ++ </description> ++ <arg name="amount" type="uint"/> ++ </event> ++ ++ <event name="layout"> ++ <description summary="Announces a layout"> ++ This event is sent after binding. ++ A roundtrip after binding guarantees the client recieved all layouts. ++ </description> ++ <arg name="name" type="string"/> ++ </event> ++ </interface> ++ ++ <interface name="zdwl_ipc_output_v2" version="2"> ++ <description summary="control dwl output"> ++ 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. ++ </description> ++ ++ <enum name="tag_state"> ++ <entry name="none" value="0" summary="no state"/> ++ <entry name="active" value="1" summary="tag is active"/> ++ <entry name="urgent" value="2" summary="tag has at least one urgent client"/> ++ </enum> ++ ++ <request name="release" type="destructor"> ++ <description summary="release dwl_ipc_outout"> ++ Indicates to that the client no longer needs this dwl_ipc_output. ++ </description> ++ </request> ++ ++ <event name="toggle_visibility"> ++ <description summary="Toggle client visibilty"> ++ Indicates the client should hide or show themselves. ++ If the client is visible then hide, if hidden then show. ++ </description> ++ </event> ++ ++ <event name="active"> ++ <description summary="Update the selected output."> ++ Indicates if the output is active. Zero is invalid, nonzero is valid. ++ </description> ++ <arg name="active" type="uint"/> ++ </event> ++ ++ <event name="tag"> ++ <description summary="Update the state of a tag."> ++ Indicates that a tag has been updated. ++ </description> ++ <arg name="tag" type="uint" summary="Index of the tag"/> ++ <arg name="state" type="uint" enum="tag_state" summary="The state of the tag."/> ++ <arg name="clients" type="uint" summary="The number of clients in the tag."/> ++ <arg name="focused" type="uint" summary="If there is a focused client. Nonzero being valid, zero being invalid."/> ++ </event> ++ ++ <event name="layout"> ++ <description summary="Update the layout."> ++ Indicates a new layout is selected. ++ </description> ++ <arg name="layout" type="uint" summary="Index of the layout."/> ++ </event> ++ ++ <event name="title"> ++ <description summary="Update the title."> ++ Indicates the title has changed. ++ </description> ++ <arg name="title" type="string" summary="The new title name."/> ++ </event> ++ ++ <event name="appid" since="1"> ++ <description summary="Update the appid."> ++ Indicates the appid has changed. ++ </description> ++ <arg name="appid" type="string" summary="The new appid."/> ++ </event> ++ ++ <event name="layout_symbol" since="1"> ++ <description summary="Update the current layout symbol"> ++ 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. ++ </description> ++ <arg name="layout" type="string" summary="The new layout"/> ++ </event> ++ ++ <event name="frame"> ++ <description summary="The update sequence is done."> ++ Indicates that a sequence of status updates have finished and the client should redraw. ++ </description> ++ </event> ++ ++ <request name="set_tags"> ++ <description summary="Set the active tags of this output"/> ++ <arg name="tagmask" type="uint" summary="bitmask of the tags that should be set."/> ++ <arg name="toggle_tagset" type="uint" summary="toggle the selected tagset, zero for invalid, nonzero for valid."/> ++ </request> ++ ++ <request name="set_client_tags"> ++ <description summary="Set the tags of the focused client."> ++ The tags are updated as follows: ++ new_tags = (current_tags AND and_tags) XOR xor_tags ++ </description> ++ <arg name="and_tags" type="uint"/> ++ <arg name="xor_tags" type="uint"/> ++ </request> ++ ++ <request name="set_layout"> ++ <description summary="Set the layout of this output"/> ++ <arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/> ++ </request> ++ ++ <!-- Version 2 --> ++ <event name="fullscreen" since="2"> ++ <description summary="Update fullscreen status"> ++ Indicates if the selected client on this output is fullscreen. ++ </description> ++ <arg name="is_fullscreen" type="uint" summary="If the selected client is fullscreen. Nonzero is valid, zero invalid"/> ++ </event> ++ ++ <event name="floating" since="2"> ++ <description summary="Update the floating status"> ++ Indicates if the selected client on this output is floating. ++ </description> ++ <arg name="is_floating" type="uint" summary="If the selected client is floating. Nonzero is valid, zero invalid"/> ++ </event> ++ </interface> ++</protocol> +-- +2.43.0 + diff --git a/dwl-patches/patches/ipc/ipcpertag.patch b/dwl-patches/patches/ipc/ipcpertag.patch new file mode 100644 index 0000000..97e3c33 --- /dev/null +++ b/dwl-patches/patches/ipc/ipcpertag.patch @@ -0,0 +1,97 @@ +From c7d77ff4dec1da5a68b4da8aa42d4ed78dc41a00 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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-patches/patches/kblayout/README.md b/dwl-patches/patches/kblayout/README.md new file mode 100644 index 0000000..259694c --- /dev/null +++ b/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-patches/patches/kblayout/kblayout.patch b/dwl-patches/patches/kblayout/kblayout.patch new file mode 100644 index 0000000..707a4fc --- /dev/null +++ b/dwl-patches/patches/kblayout/kblayout.patch @@ -0,0 +1,187 @@ +From ad18a8b8e9de138c3d89246ac0e25c0467ff5971 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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 <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> ++#include <wlr/interfaces/wlr_keyboard.h> + #include <wlr/render/allocator.h> + #include <wlr/render/wlr_renderer.h> + #include <wlr/types/wlr_alpha_modifier_v1.h> +@@ -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-patches/patches/keyboardshortcutsinhibit/README.md b/dwl-patches/patches/keyboardshortcutsinhibit/README.md new file mode 100644 index 0000000..c52de56 --- /dev/null +++ b/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-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch b/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch new file mode 100644 index 0000000..bea327e --- /dev/null +++ b/dwl-patches/patches/keyboardshortcutsinhibit/keyboardshortcutsinhibit.patch @@ -0,0 +1,101 @@ +From ac1fa09172a401427cabbda045688903bdd3cbe7 Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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 <wlr/types/wlr_input_device.h> + #include <wlr/types/wlr_keyboard.h> + #include <wlr/types/wlr_keyboard_group.h> ++#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h> + #include <wlr/types/wlr_layer_shell_v1.h> + #include <wlr/types/wlr_linux_dmabuf_v1.h> + #include <wlr/types/wlr_output.h> +@@ -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-patches/patches/keycodes/README.md b/dwl-patches/patches/keycodes/README.md new file mode 100644 index 0000000..b56adc3 --- /dev/null +++ b/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-patches/patches/keycodes/keycodes-0.7.patch b/dwl-patches/patches/keycodes/keycodes-0.7.patch new file mode 100644 index 0000000..7852307 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <errno.h> ++#include <stdbool.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <inttypes.h> ++#include <unistd.h> ++ ++#include <xkbcommon/xkbcommon.h> ++ ++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-patches/patches/keycodes/keycodes.patch b/dwl-patches/patches/keycodes/keycodes.patch new file mode 100644 index 0000000..cc7e896 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <errno.h> ++#include <stdbool.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <inttypes.h> ++#include <unistd.h> ++ ++#include <xkbcommon/xkbcommon.h> ++ ++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-patches/patches/limitnmaster/README.md b/dwl-patches/patches/limitnmaster/README.md new file mode 100644 index 0000000..e750300 --- /dev/null +++ b/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-patches/patches/limitnmaster/limitnmaster.patch b/dwl-patches/patches/limitnmaster/limitnmaster.patch new file mode 100644 index 0000000..f4339c8 --- /dev/null +++ b/dwl-patches/patches/limitnmaster/limitnmaster.patch @@ -0,0 +1,33 @@ +From 20c948398af900564a59007fc08d15eaa0b65da3 Mon Sep 17 00:00:00 2001 +From: Gavin M <github@gavinm.us> +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-patches/patches/lockedkeys/README.md b/dwl-patches/patches/lockedkeys/README.md new file mode 100644 index 0000000..50f0fd2 --- /dev/null +++ b/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-patches/patches/lockedkeys/lockedkeys.patch b/dwl-patches/patches/lockedkeys/lockedkeys.patch new file mode 100644 index 0000000..f683536 --- /dev/null +++ b/dwl-patches/patches/lockedkeys/lockedkeys.patch @@ -0,0 +1,79 @@ +From 70dc03a3817b8fd933244c2db1bb849d9626b12b Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/mastercolumn/README.md b/dwl-patches/patches/mastercolumn/README.md new file mode 100644 index 0000000..14db13c --- /dev/null +++ b/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-patches/patches/mastercolumn/mastercolumn-gaps.patch b/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch new file mode 100644 index 0000000..943c4bd --- /dev/null +++ b/dwl-patches/patches/mastercolumn/mastercolumn-gaps.patch @@ -0,0 +1,63 @@ +From b6f2ee09778cdea8a1450d16bcf24a8a75e10b40 Mon Sep 17 00:00:00 2001 +From: moe <moemmakki@gmail.com> +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-patches/patches/mastercolumn/mastercolumn.patch b/dwl-patches/patches/mastercolumn/mastercolumn.patch new file mode 100644 index 0000000..425e414 --- /dev/null +++ b/dwl-patches/patches/mastercolumn/mastercolumn.patch @@ -0,0 +1,87 @@ +From eb0c6ff53ba823f26d13f18627a084959c353627 Mon Sep 17 00:00:00 2001 +From: moe <moemmakki@gmail.com> +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-patches/patches/menu/README.md b/dwl-patches/patches/menu/README.md new file mode 100644 index 0000000..ad37016 --- /dev/null +++ b/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-patches/patches/menu/menu.patch b/dwl-patches/patches/menu/menu.patch new file mode 100644 index 0000000..c1dea4d --- /dev/null +++ b/dwl-patches/patches/menu/menu.patch @@ -0,0 +1,227 @@ +From da9861cf0448ca94011470634fd61c3ef2129a25 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/menurule/README.md b/dwl-patches/patches/menurule/README.md new file mode 100644 index 0000000..8d8b757 --- /dev/null +++ b/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 `<`. + + + +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-patches/patches/menurule/menurule.patch b/dwl-patches/patches/menurule/menurule.patch new file mode 100644 index 0000000..b74a600 --- /dev/null +++ b/dwl-patches/patches/menurule/menurule.patch @@ -0,0 +1,167 @@ +From 7b578d9f4647d84f79a2e8a46a1c65cbacf8d90b Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/menurule/menurule.png b/dwl-patches/patches/menurule/menurule.png Binary files differnew file mode 100644 index 0000000..20faac2 --- /dev/null +++ b/dwl-patches/patches/menurule/menurule.png diff --git a/dwl-patches/patches/meson/README.md b/dwl-patches/patches/meson/README.md new file mode 100644 index 0000000..4641bbf --- /dev/null +++ b/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-patches/patches/meson/meson.patch b/dwl-patches/patches/meson/meson.patch new file mode 100644 index 0000000..6d78036 --- /dev/null +++ b/dwl-patches/patches/meson/meson.patch @@ -0,0 +1,135 @@ +From 136cdeb302fdfe28e5cd5c6a1693b05c3d1bfb58 Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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-patches/patches/minimalborders/README.md b/dwl-patches/patches/minimalborders/README.md new file mode 100644 index 0000000..2a670c9 --- /dev/null +++ b/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 */
+```
+
+<details>
+<summary>Preview:</summary>
+<pre>
+with:
+
+```c
+static const unsigned int borderpx = 10; /* border pixel of windows */
+```
+
+Before applying the patch
+<img src="https://i.imgur.com/VQfXCjp.png"/>
+
+After applying the patch
+<img src="https://i.imgur.com/I7s0Xkv.png"/>
+</pre>
+</details>
+
+### 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-patches/patches/minimalborders/minimalborders.patch b/dwl-patches/patches/minimalborders/minimalborders.patch new file mode 100644 index 0000000..cb19900 --- /dev/null +++ b/dwl-patches/patches/minimalborders/minimalborders.patch @@ -0,0 +1,161 @@ +From ce0eb92fb100801f343fbe9b76639847a9e39160 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/modes/README.md b/dwl-patches/patches/modes/README.md new file mode 100644 index 0000000..c855af9 --- /dev/null +++ b/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 <kbd>modkey</kbd> + <kbd>b</kbd>. Then, you can press <kbd>f</kbd> 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-patches/patches/modes/modes.patch b/dwl-patches/patches/modes/modes.patch new file mode 100644 index 0000000..5ecf846 --- /dev/null +++ b/dwl-patches/patches/modes/modes.patch @@ -0,0 +1,165 @@ +From a32b85018ff2cea0fc9f9137789860a4aadc3b3a Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/monitorconfig/README.md b/dwl-patches/patches/monitorconfig/README.md new file mode 100644 index 0000000..313e5a0 --- /dev/null +++ b/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-patches/patches/monitorconfig/monitorconfig.patch b/dwl-patches/patches/monitorconfig/monitorconfig.patch new file mode 100644 index 0000000..4922f8d --- /dev/null +++ b/dwl-patches/patches/monitorconfig/monitorconfig.patch @@ -0,0 +1,95 @@ +From 73f70cd9d817a307030f360f6c8a2500046b8b76 Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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 <leohdz172@proton.me> +--- + 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-patches/patches/movecenter/README.md b/dwl-patches/patches/movecenter/README.md new file mode 100644 index 0000000..aff8f7d --- /dev/null +++ b/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 <kbd>MODKEY</kbd> + <kbd>x</kbd> 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. + +<details> +<summary>Explanation of respect_monitor_reserved_area:</summary> +<pre> +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. +<img src="https://i.imgur.com/xhejzPh.png"/> +</pre> +</details> + +### 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-patches/patches/movecenter/movecenter.patch b/dwl-patches/patches/movecenter/movecenter.patch new file mode 100644 index 0000000..f96bd36 --- /dev/null +++ b/dwl-patches/patches/movecenter/movecenter.patch @@ -0,0 +1,82 @@ +From bc5206882c71b32198dae5f1c85601a863a7c0a9 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/moveresizekb/README.md b/dwl-patches/patches/moveresizekb/README.md new file mode 100644 index 0000000..0c53caf --- /dev/null +++ b/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 |
+| :--- | :--- |
+| <kbd>MODKEY</kbd> + <kbd>Up</kbd> | move 40px up |
+| <kbd>MODKEY</kbd> + <kbd>Down</kbd> | move 40px down |
+| <kbd>MODKEY</kbd> + <kbd>Left</kbd> | move 40px left |
+| <kbd>MODKEY</kbd> + <kbd>Right</kbd> | move 40px right |
+| <kbd>MODKEY</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> | shrink height 40px |
+| <kbd>MODKEY</kbd> + <kbd>Shift</kbd> + <kbd>Down</kbd> | grow height 40px |
+| <kbd>MODKEY</kbd> + <kbd>Shift</kbd> + <kbd>Left</kbd> | shrink width 40px |
+| <kbd>MODKEY</kbd> + <kbd>Shift</kbd> + <kbd>Right</kbd> | 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-patches/patches/moveresizekb/moveresizekb.patch b/dwl-patches/patches/moveresizekb/moveresizekb.patch new file mode 100644 index 0000000..aeb1e5e --- /dev/null +++ b/dwl-patches/patches/moveresizekb/moveresizekb.patch @@ -0,0 +1,70 @@ +From c8af428f964679089599e4ffbe7d28d08a4e875f Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/movestack/README.md b/dwl-patches/patches/movestack/README.md new file mode 100644 index 0000000..f50bf68 --- /dev/null +++ b/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-patches/patches/movestack/movestack.patch b/dwl-patches/patches/movestack/movestack.patch new file mode 100644 index 0000000..2543c66 --- /dev/null +++ b/dwl-patches/patches/movestack/movestack.patch @@ -0,0 +1,87 @@ +From 08230817bd3926e29d9897657eb1852cb27d461f Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/namedscratchpads/README.md b/dwl-patches/patches/namedscratchpads/README.md new file mode 100644 index 0000000..e436583 --- /dev/null +++ b/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-patches/patches/namedscratchpads/namedscratchpads.patch b/dwl-patches/patches/namedscratchpads/namedscratchpads.patch new file mode 100644 index 0000000..7a0a257 --- /dev/null +++ b/dwl-patches/patches/namedscratchpads/namedscratchpads.patch @@ -0,0 +1,278 @@ +From bd83d56b3a268112b029961ad6ff4232b2d6f00d Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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 <benc@benc.cc> +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-patches/patches/naturalscrolltrackpad/README.md b/dwl-patches/patches/naturalscrolltrackpad/README.md new file mode 100644 index 0000000..591e80a --- /dev/null +++ b/dwl-patches/patches/naturalscrolltrackpad/README.md @@ -0,0 +1,14 @@ +### Description
+
+Set natural scrolling only for trackpads. Without this patch, setting
+`natural_scrolling` to 1 in `config.h` results in a regular mouse wheel having
+natural scrolling enabled as well.
+
+### Download
+
+- [0.7](/dwl/dwl-patches/raw/branch/main/patches/naturalscrolltrackpad/naturalscrolltrackpad.patch)
+
+### Authors
+
+- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV))
+- [Neuromagus](https://codeberg.org/neuromagus)
diff --git a/dwl-patches/patches/naturalscrolltrackpad/naturalscrolltrackpad.patch b/dwl-patches/patches/naturalscrolltrackpad/naturalscrolltrackpad.patch new file mode 100644 index 0000000..fd6645b --- /dev/null +++ b/dwl-patches/patches/naturalscrolltrackpad/naturalscrolltrackpad.patch @@ -0,0 +1,30 @@ +From 63f38e7ccda8067a0558a6e81baad89ffbba9d6a Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +Date: Sat, 8 Feb 2025 16:31:09 +0100 +Subject: [PATCH] Set natural scrolling only for trackpads + +--- + dwl.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/dwl.c b/dwl.c +index def2562..f6bb544 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1085,10 +1085,10 @@ createpointer(struct wlr_pointer *pointer) + 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, natural_scrolling); ++ } + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); +-- +2.48.1 + diff --git a/dwl-patches/patches/nextlayout/README.md b/dwl-patches/patches/nextlayout/README.md new file mode 100644 index 0000000..ea1d4a6 --- /dev/null +++ b/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-patches/patches/nextlayout/nextlayout.patch b/dwl-patches/patches/nextlayout/nextlayout.patch new file mode 100644 index 0000000..11f4dc7 --- /dev/null +++ b/dwl-patches/patches/nextlayout/nextlayout.patch @@ -0,0 +1,65 @@ +From 7d8cfa63681830a3af4512799b8260f8249bc514 Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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-patches/patches/numlock-capslock/README.md b/dwl-patches/patches/numlock-capslock/README.md new file mode 100644 index 0000000..54a4f34 --- /dev/null +++ b/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-patches/patches/numlock-capslock/numlock-capslock-0.7.patch b/dwl-patches/patches/numlock-capslock/numlock-capslock-0.7.patch new file mode 100644 index 0000000..7b30be3 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> ++#include <wlr/interfaces/wlr_keyboard.h> + #include <wlr/render/allocator.h> + #include <wlr/render/wlr_renderer.h> + #include <wlr/types/wlr_alpha_modifier_v1.h> +@@ -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-patches/patches/numlock-capslock/numlock-capslock.patch b/dwl-patches/patches/numlock-capslock/numlock-capslock.patch new file mode 100644 index 0000000..adf9f38 --- /dev/null +++ b/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?= + <leohdz172@proton.me> +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 <leohdz172@proton.me> +--- + 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 <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> ++#include <wlr/interfaces/wlr_keyboard.h> + #include <wlr/render/allocator.h> + #include <wlr/render/wlr_renderer.h> + #include <wlr/types/wlr_alpha_modifier_v1.h> +@@ -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-patches/patches/passthrough/README.md b/dwl-patches/patches/passthrough/README.md new file mode 100644 index 0000000..0380951 --- /dev/null +++ b/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-patches/patches/passthrough/passthrough.patch b/dwl-patches/patches/passthrough/passthrough.patch new file mode 100644 index 0000000..d87f00e --- /dev/null +++ b/dwl-patches/patches/passthrough/passthrough.patch @@ -0,0 +1,82 @@ +From cd67c8386b0188daa15348c1d0d99187a556e461 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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-patches/patches/perinputconfig/README.md b/dwl-patches/patches/perinputconfig/README.md new file mode 100644 index 0000000..ab1602c --- /dev/null +++ b/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-patches/patches/perinputconfig/perinputconfig-v0.5.patch b/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch new file mode 100644 index 0000000..25a2a92 --- /dev/null +++ b/dwl-patches/patches/perinputconfig/perinputconfig-v0.5.patch @@ -0,0 +1,173 @@ +From 9388faea3c4648aa99c01b9e4ce9287237b28b38 Mon Sep 17 00:00:00 2001 +From: nullsystem <nullsystem.aongp@slmail.me> +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-patches/patches/perinputconfig/perinputconfig.patch b/dwl-patches/patches/perinputconfig/perinputconfig.patch new file mode 100644 index 0000000..dae4bc8 --- /dev/null +++ b/dwl-patches/patches/perinputconfig/perinputconfig.patch @@ -0,0 +1,295 @@ +From c268707811fb5d8244115f23a0430f024e4e11a9 Mon Sep 17 00:00:00 2001 +From: nullsystem <nullsystem.aongp@slmail.me> +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-patches/patches/pertag/README.md b/dwl-patches/patches/pertag/README.md new file mode 100644 index 0000000..7473da0 --- /dev/null +++ b/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-patches/patches/pertag/pertag.patch b/dwl-patches/patches/pertag/pertag.patch new file mode 100644 index 0000000..971732a --- /dev/null +++ b/dwl-patches/patches/pertag/pertag.patch @@ -0,0 +1,170 @@ +From d3b551ffe3ec85e16341962e322150b81af6722f Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/pointer-gestures-unstable-v1/README.md b/dwl-patches/patches/pointer-gestures-unstable-v1/README.md new file mode 100644 index 0000000..3ca5432 --- /dev/null +++ b/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-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch b/dwl-patches/patches/pointer-gestures-unstable-v1/pointer-gestures-unstable-v1.patch new file mode 100644 index 0000000..f76648a --- /dev/null +++ b/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 <gean.marroquin@gmail.com> +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 <wlr/types/wlr_output_power_management_v1.h> + #include <wlr/types/wlr_pointer.h> + #include <wlr/types/wlr_pointer_constraints_v1.h> ++#include <wlr/types/wlr_pointer_gestures_v1.h> + #include <wlr/types/wlr_presentation_time.h> + #include <wlr/types/wlr_primary_selection.h> + #include <wlr/types/wlr_primary_selection_v1.h> +@@ -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-patches/patches/primaryselection/README.md b/dwl-patches/patches/primaryselection/README.md new file mode 100644 index 0000000..1154452 --- /dev/null +++ b/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-patches/patches/primaryselection/primaryselection.patch b/dwl-patches/patches/primaryselection/primaryselection.patch new file mode 100644 index 0000000..ab6bb64 --- /dev/null +++ b/dwl-patches/patches/primaryselection/primaryselection.patch @@ -0,0 +1,50 @@ +From 4fc77fde2f6015564544e029f9905fc1678fcb59 Mon Sep 17 00:00:00 2001 +From: nullsystem <nullsystem.aongp@slmail.me> +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-patches/patches/push/README.md b/dwl-patches/patches/push/README.md new file mode 100644 index 0000000..5582bfd --- /dev/null +++ b/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-patches/patches/push/push-0.7.patch b/dwl-patches/patches/push/push-0.7.patch new file mode 100644 index 0000000..4afad97 --- /dev/null +++ b/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" <djpohly@gmail.com> +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 <leohdz172@proton.me> +--- + 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-patches/patches/push/push.patch b/dwl-patches/patches/push/push.patch new file mode 100644 index 0000000..5f6cba9 --- /dev/null +++ b/dwl-patches/patches/push/push.patch @@ -0,0 +1,127 @@ +From 06d86c26da60e4196ec3c5228b04c66ac042a1f8 Mon Sep 17 00:00:00 2001 +From: "Devin J. Pohly" <djpohly@gmail.com> +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 <leohdz172@proton.me> +--- + 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-patches/patches/regexrules/README.md b/dwl-patches/patches/regexrules/README.md new file mode 100644 index 0000000..8c014b2 --- /dev/null +++ b/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-patches/patches/regexrules/regexrules.patch b/dwl-patches/patches/regexrules/regexrules.patch new file mode 100644 index 0000000..f7207d3 --- /dev/null +++ b/dwl-patches/patches/regexrules/regexrules.patch @@ -0,0 +1,76 @@ +From 7fed9449575b1e4f58d519d2f87b7e66e2056125 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <sys/wait.h> + #include <time.h> + #include <unistd.h> ++#include <regex.h> + #include <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> +@@ -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-patches/patches/regions/README.md b/dwl-patches/patches/regions/README.md new file mode 100644 index 0000000..8b5679a --- /dev/null +++ b/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-patches/patches/regions/regions.patch b/dwl-patches/patches/regions/regions.patch new file mode 100644 index 0000000..207c0bd --- /dev/null +++ b/dwl-patches/patches/regions/regions.patch @@ -0,0 +1,71 @@ +From 9991f8bbf2e379dfca8eb356c03d3d20085255a8 Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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-patches/patches/relative-mouse-resize/README.md b/dwl-patches/patches/relative-mouse-resize/README.md new file mode 100644 index 0000000..ee462ce --- /dev/null +++ b/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-patches/patches/relative-mouse-resize/relative-mouse-resize.patch b/dwl-patches/patches/relative-mouse-resize/relative-mouse-resize.patch new file mode 100644 index 0000000..e1dfc57 --- /dev/null +++ b/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 <gean.marroquin@gmail.com> +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-patches/patches/restore-monitor/README.md b/dwl-patches/patches/restore-monitor/README.md new file mode 100644 index 0000000..8380967 --- /dev/null +++ b/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-patches/patches/restore-monitor/restore-monitor.patch b/dwl-patches/patches/restore-monitor/restore-monitor.patch new file mode 100644 index 0000000..0d6da98 --- /dev/null +++ b/dwl-patches/patches/restore-monitor/restore-monitor.patch @@ -0,0 +1,82 @@ +From e42ca1c539437d3098d80983cfe2ad6f938d7a08 Mon Sep 17 00:00:00 2001 +From: Eldar Yusupov <eyusupov@gmail.com> +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-patches/patches/right/README.md b/dwl-patches/patches/right/README.md new file mode 100644 index 0000000..7708ded --- /dev/null +++ b/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) (<dima@dimakrasner.com>) diff --git a/dwl-patches/patches/right/right.patch b/dwl-patches/patches/right/right.patch new file mode 100644 index 0000000..baf8eeb --- /dev/null +++ b/dwl-patches/patches/right/right.patch @@ -0,0 +1,50 @@ +From 9d4b0ebb95381525351b05aad6a98f1747ae96e2 Mon Sep 17 00:00:00 2001 +From: Dima Krasner <dima@dimakrasner.com> +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-patches/patches/rlimit_max/README.md b/dwl-patches/patches/rlimit_max/README.md new file mode 100644 index 0000000..2bf7355 --- /dev/null +++ b/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-patches/patches/rlimit_max/rlimit_max.patch b/dwl-patches/patches/rlimit_max/rlimit_max.patch new file mode 100644 index 0000000..81111b3 --- /dev/null +++ b/dwl-patches/patches/rlimit_max/rlimit_max.patch @@ -0,0 +1,91 @@ +From d25a8222651671613322677d17b2f987135e02cd Mon Sep 17 00:00:00 2001 +From: sewn <sewn@disroot.org> +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 <signal.h> + #include <stdio.h> + #include <stdlib.h> ++#include <sys/resource.h> + #include <sys/wait.h> + #include <time.h> + #include <unistd.h> +@@ -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-patches/patches/rotate-clients/README.md b/dwl-patches/patches/rotate-clients/README.md new file mode 100644 index 0000000..aaf8f67 --- /dev/null +++ b/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-patches/patches/rotate-clients/rotate-clients.patch b/dwl-patches/patches/rotate-clients/rotate-clients.patch new file mode 100644 index 0000000..8f5f557 --- /dev/null +++ b/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-patches/patches/rotatetags/README.md b/dwl-patches/patches/rotatetags/README.md new file mode 100644 index 0000000..f981847 --- /dev/null +++ b/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-patches/patches/rotatetags/rotatetags.patch b/dwl-patches/patches/rotatetags/rotatetags.patch new file mode 100644 index 0000000..94e776f --- /dev/null +++ b/dwl-patches/patches/rotatetags/rotatetags.patch @@ -0,0 +1,91 @@ +From 308c668010bb7526ea40ad12dbaa1af62f9d7421 Mon Sep 17 00:00:00 2001 +From: korei999 <ju7t1xe@gmail.com> +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-patches/patches/scenefx/README.md b/dwl-patches/patches/scenefx/README.md new file mode 100644 index 0000000..24bb3ee --- /dev/null +++ b/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 ...` + +<details> +<summary>Preview</summary> +<pre> +<img src="https://i.imgur.com/nJIX6lp.png"/> +</pre> +</details> + +### Download + +- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.8-a/scenefx-b) + +- [0.8](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 + + <details> + <summary>Preview</summary> + <pre> + <img src="https://i.imgur.com/4kFhSaS.png"/> + <img src="https://i.imgur.com/9ZQAUXx.png"/> + </pre> + </details> + +- [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-patches/patches/scenefx/scenefx.patch b/dwl-patches/patches/scenefx/scenefx.patch new file mode 100644 index 0000000..c556cb4 --- /dev/null +++ b/dwl-patches/patches/scenefx/scenefx.patch @@ -0,0 +1,700 @@ +From d209aeaee541a045de9dba2154bbbd5475bba2ec Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <stdlib.h> + #include <sys/wait.h> + #include <time.h> ++#include <scenefx/render/fx_renderer/fx_renderer.h> ++#include <scenefx/types/fx/blur_data.h> ++#include <scenefx/types/fx/clipped_region.h> ++#include <scenefx/types/fx/corner_location.h> ++#include <scenefx/types/wlr_scene.h> + #include <unistd.h> + #include <wayland-server-core.h> ++#include <wayland-util.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> + #include <wlr/render/allocator.h> +@@ -43,7 +49,6 @@ + #include <wlr/types/wlr_primary_selection.h> + #include <wlr/types/wlr_primary_selection_v1.h> + #include <wlr/types/wlr_relative_pointer_v1.h> +-#include <wlr/types/wlr_scene.h> + #include <wlr/types/wlr_screencopy_v1.h> + #include <wlr/types/wlr_seat.h> + #include <wlr/types/wlr_server_decoration.h> +@@ -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-patches/patches/scroll-factor/README.md b/dwl-patches/patches/scroll-factor/README.md new file mode 100644 index 0000000..9c4be3f --- /dev/null +++ b/dwl-patches/patches/scroll-factor/README.md @@ -0,0 +1,9 @@ +### Description +This patch adds scroll factor to dwl. The settings can be found in the trackpad section of the config. This allows user to control the sensitivity of 2-finger touchpad scrolling. + +### Download +- [git branch](https://codeberg.org/singul4ri7y/dwl/src/branch/scroll-factor) +- [2024-07-12](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/scroll-factor/scroll-factor.patch) + +### Authors +- [singul4ri7y](https://codeberg.org/singul4ri7y) diff --git a/dwl-patches/patches/scroll-factor/scroll-factor.patch b/dwl-patches/patches/scroll-factor/scroll-factor.patch new file mode 100644 index 0000000..d645837 --- /dev/null +++ b/dwl-patches/patches/scroll-factor/scroll-factor.patch @@ -0,0 +1,54 @@ +From 3e765d49976685a8772bd3e12a8c5546868a97f7 Mon Sep 17 00:00:00 2001 +From: SD Asif Hossein <s.dah.ingularity47@gmail.com> +Date: Fri, 12 Jul 2024 14:28:06 +0600 +Subject: [PATCH] Added scroll-factor patch + +--- + config.def.h | 3 +++ + dwl.c | 11 +++++++++-- + 2 files changed, 12 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..83c4b87 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -71,6 +71,9 @@ 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; ++/* Scroll sensitivity */ ++static const double scroll_factor = 1.0f; ++ + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +diff --git a/dwl.c b/dwl.c +index dc0437e..7e8d50d 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -581,13 +581,20 @@ 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; ++ double delta = event->delta; ++ int32_t delta_disc = event->delta_discrete; ++ if(event->source == WLR_AXIS_SOURCE_FINGER) { ++ delta *= scroll_factor; ++ delta_disc = (int32_t)round(scroll_factor * delta_disc); ++ } ++ + 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->time_msec, event->orientation, delta, ++ delta_disc, event->source); + } + + void +-- +2.45.2 + diff --git a/dwl-patches/patches/setrule/README.md b/dwl-patches/patches/setrule/README.md new file mode 100644 index 0000000..bdb7408 --- /dev/null +++ b/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-patches/patches/setrule/setrule.patch b/dwl-patches/patches/setrule/setrule.patch new file mode 100644 index 0000000..6f9a1a3 --- /dev/null +++ b/dwl-patches/patches/setrule/setrule.patch @@ -0,0 +1,155 @@ +From 3c78308f0d74ac6ef112804333f82c098e33bb40 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/setupenv/README.md b/dwl-patches/patches/setupenv/README.md new file mode 100644 index 0000000..776c328 --- /dev/null +++ b/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-patches/patches/setupenv/setupenv.patch b/dwl-patches/patches/setupenv/setupenv.patch new file mode 100644 index 0000000..17d2ad4 --- /dev/null +++ b/dwl-patches/patches/setupenv/setupenv.patch @@ -0,0 +1,54 @@ +From 11ee2fc23ef5728d1e132f338c08a7805c6109b2 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@proton.me> +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-patches/patches/shiftview/README.md b/dwl-patches/patches/shiftview/README.md new file mode 100644 index 0000000..2d25820 --- /dev/null +++ b/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-patches/patches/shiftview/shiftview.patch b/dwl-patches/patches/shiftview/shiftview.patch new file mode 100644 index 0000000..f556916 --- /dev/null +++ b/dwl-patches/patches/shiftview/shiftview.patch @@ -0,0 +1,76 @@ +From 40f9140742277d0298988990264f4b6a738f8122 Mon Sep 17 00:00:00 2001 +From: Guido Cella <guido@guidocella.xyz> +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-patches/patches/simple_scratchpad/README.md b/dwl-patches/patches/simple_scratchpad/README.md new file mode 100644 index 0000000..8076d96 --- /dev/null +++ b/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. + + +--- + +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-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch b/dwl-patches/patches/simple_scratchpad/simple_scratchpad-v0.7.patch new file mode 100644 index 0000000..a273559 --- /dev/null +++ b/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 <julmajustus@tutanota.com> +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 <jmakkone@student.hive.fi> +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* 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-patches/patches/simpleborders/README.md b/dwl-patches/patches/simpleborders/README.md new file mode 100644 index 0000000..29dcf5e --- /dev/null +++ b/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-patches/patches/simpleborders/simpleborders-v0.6.patch b/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch new file mode 100644 index 0000000..a4797bb --- /dev/null +++ b/dwl-patches/patches/simpleborders/simpleborders-v0.6.patch @@ -0,0 +1,73 @@ +From 681e520deaeee460647de36f5312af3cb0c31f4a Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/simpleborders/simpleborders-v0.7.patch b/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch new file mode 100644 index 0000000..e782d84 --- /dev/null +++ b/dwl-patches/patches/simpleborders/simpleborders-v0.7.patch @@ -0,0 +1,73 @@ +From 09759c3ef75158c366e9fc63814485fbb31a3ccf Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/singlemaster/README.md b/dwl-patches/patches/singlemaster/README.md new file mode 100644 index 0000000..99cb1e6 --- /dev/null +++ b/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-patches/patches/singlemaster/singlemaster.patch b/dwl-patches/patches/singlemaster/singlemaster.patch new file mode 100644 index 0000000..ffa6045 --- /dev/null +++ b/dwl-patches/patches/singlemaster/singlemaster.patch @@ -0,0 +1,155 @@ +From 610362d2f38260ad2a06ffbd91d089ba738429e9 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/singletagset-pertag/README.md b/dwl-patches/patches/singletagset-pertag/README.md new file mode 100644 index 0000000..13ab951 --- /dev/null +++ b/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-patches/patches/singletagset-pertag/singletagset-pertag.patch b/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch new file mode 100644 index 0000000..5fe4f47 --- /dev/null +++ b/dwl-patches/patches/singletagset-pertag/singletagset-pertag.patch @@ -0,0 +1,261 @@ +From 4f6bdd3ea1fcc83abd962e9a2dc7737519164a6b Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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<<getunusedtag()) & TAGMASK; ++ m->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-patches/patches/singletagset-sticky/README.md b/dwl-patches/patches/singletagset-sticky/README.md new file mode 100644 index 0000000..f1f9fd3 --- /dev/null +++ b/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-patches/patches/singletagset-sticky/singletagset-sticky.patch b/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch new file mode 100644 index 0000000..ce6a40c --- /dev/null +++ b/dwl-patches/patches/singletagset-sticky/singletagset-sticky.patch @@ -0,0 +1,27 @@ +From a37d65c8601a7c5b03c53bb99956da8a24952628 Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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-patches/patches/singletagset/README.md b/dwl-patches/patches/singletagset/README.md new file mode 100644 index 0000000..920d8a4 --- /dev/null +++ b/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-patches/patches/singletagset/singletagset-v0.6.patch b/dwl-patches/patches/singletagset/singletagset-v0.6.patch new file mode 100644 index 0000000..708774b --- /dev/null +++ b/dwl-patches/patches/singletagset/singletagset-v0.6.patch @@ -0,0 +1,293 @@ +From 140fe587bf34cf43e44acaf365b16ec3e385f742 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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<<getunusedtag()) & TAGMASK; + for (r = monrules; r < END(monrules); r++) { + if (!r->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<<i))) { ++ is_used = 1; ++ } ++ } ++ ++ if (!is_used) { ++ return i; ++ } ++ } ++ return i; ++} ++ + void + handlesig(int signo) + { +@@ -1915,8 +1991,6 @@ printstatus(void) + 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; +@@ -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-patches/patches/singletagset/singletagset-v0.7.patch b/dwl-patches/patches/singletagset/singletagset-v0.7.patch new file mode 100644 index 0000000..9c2f98b --- /dev/null +++ b/dwl-patches/patches/singletagset/singletagset-v0.7.patch @@ -0,0 +1,312 @@ +From bd90ec504369a69dbefc34cb72265d15c7795944 Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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<<getunusedtag()) & TAGMASK; + for (r = monrules; r < END(monrules); r++) { + if (!r->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<<i))) { ++ is_used = 1; ++ } ++ } ++ ++ if (!is_used) { ++ return i; ++ } ++ } ++ return i; ++} ++ + void + gpureset(struct wl_listener *listener, void *data) + { +@@ -2042,8 +2120,6 @@ printstatus(void) + 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; +@@ -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-patches/patches/skipfocus/README.md b/dwl-patches/patches/skipfocus/README.md new file mode 100644 index 0000000..4fb6e8a --- /dev/null +++ b/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-patches/patches/skipfocus/skipfocus-20240714.patch b/dwl-patches/patches/skipfocus/skipfocus-20240714.patch new file mode 100644 index 0000000..2715fb9 --- /dev/null +++ b/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-patches/patches/skipfocus/skipfocus.patch b/dwl-patches/patches/skipfocus/skipfocus.patch new file mode 100644 index 0000000..ed445ec --- /dev/null +++ b/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-patches/patches/skipfocus/skipfocus20240108.patch b/dwl-patches/patches/skipfocus/skipfocus20240108.patch new file mode 100644 index 0000000..10d5f4f --- /dev/null +++ b/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-patches/patches/smartborders/README.md b/dwl-patches/patches/smartborders/README.md new file mode 100644 index 0000000..6fe0e59 --- /dev/null +++ b/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-patches/patches/smartborders/smartborders-0.7.patch b/dwl-patches/patches/smartborders/smartborders-0.7.patch new file mode 100644 index 0000000..732f06f --- /dev/null +++ b/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?= + <leohdz172@protonmail.com> +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 <dwl@ivories.org> +Co-authored-by: Andrey Proskurin <andreyproskurin@protonmail.com> +Signed-off-by: Leonardo Hernández Hernández <leohdz172@proton.me> +--- + 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-patches/patches/smartborders/smartborders.patch b/dwl-patches/patches/smartborders/smartborders.patch new file mode 100644 index 0000000..5eac3ee --- /dev/null +++ b/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?= + <leohdz172@protonmail.com> +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 <dwl@ivories.org> +Co-authored-by: Andrey Proskurin <andreyproskurin@protonmail.com> +Signed-off-by: Leonardo Hernández Hernández <leohdz172@proton.me> +--- + 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-patches/patches/snail-gaps/README.md b/dwl-patches/patches/snail-gaps/README.md new file mode 100644 index 0000000..ba26f13 --- /dev/null +++ b/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-patches/patches/snail-gaps/snail-gaps.patch b/dwl-patches/patches/snail-gaps/snail-gaps.patch new file mode 100644 index 0000000..51d2812 --- /dev/null +++ b/dwl-patches/patches/snail-gaps/snail-gaps.patch @@ -0,0 +1,160 @@ +From 804e69ca4bf586bcec46e018630f94c1c4e0b7e7 Mon Sep 17 00:00:00 2001 +From: JoaoCostaIFG <joaocosta.work@posteo.net> +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-patches/patches/snail/README.md b/dwl-patches/patches/snail/README.md new file mode 100644 index 0000000..0c94b79 --- /dev/null +++ b/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) (<dima@dimakrasner.com>)
+- [Nikita Ivanov](https://github.com/NikitaIvanovV) (fix for flickering)
diff --git a/dwl-patches/patches/snail/snail.patch b/dwl-patches/patches/snail/snail.patch new file mode 100644 index 0000000..061030a --- /dev/null +++ b/dwl-patches/patches/snail/snail.patch @@ -0,0 +1,250 @@ +From 39b6da6082db641191699be906e485f8c8fbc4e6 Mon Sep 17 00:00:00 2001 +From: Dima Krasner <dima@dimakrasner.com> +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-patches/patches/spawninfo/README.md b/dwl-patches/patches/spawninfo/README.md new file mode 100644 index 0000000..5ee345d --- /dev/null +++ b/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-patches/patches/spawninfo/pamixerc b/dwl-patches/patches/spawninfo/pamixerc new file mode 100755 index 0000000..91a41c4 --- /dev/null +++ b/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-patches/patches/spawninfo/screenshotwin b/dwl-patches/patches/spawninfo/screenshotwin new file mode 100755 index 0000000..c8a3ebf --- /dev/null +++ b/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-patches/patches/spawninfo/spawninfo.patch b/dwl-patches/patches/spawninfo/spawninfo.patch new file mode 100644 index 0000000..e452a4e --- /dev/null +++ b/dwl-patches/patches/spawninfo/spawninfo.patch @@ -0,0 +1,110 @@ +From 83b8dc03f5ea40f472e90d452671f8e55faf2c4c Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/stacker/README.md b/dwl-patches/patches/stacker/README.md new file mode 100644 index 0000000..c7778a0 --- /dev/null +++ b/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-patches/patches/stacker/stacker.patch b/dwl-patches/patches/stacker/stacker.patch new file mode 100644 index 0000000..dba1cb0 --- /dev/null +++ b/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?= <git@jeromecst.com> +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-patches/patches/startargv/README.md b/dwl-patches/patches/startargv/README.md new file mode 100644 index 0000000..bc15225 --- /dev/null +++ b/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-patches/patches/startargv/startargv.patch b/dwl-patches/patches/startargv/startargv.patch new file mode 100644 index 0000000..5697b9c --- /dev/null +++ b/dwl-patches/patches/startargv/startargv.patch @@ -0,0 +1,58 @@ +From a823f7923f8d2006eedc013ca1c1ba7a496543d5 Mon Sep 17 00:00:00 2001 +From: choc <notchoc@disroot.org> +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-patches/patches/sticky/README.md b/dwl-patches/patches/sticky/README.md new file mode 100644 index 0000000..af0e81f --- /dev/null +++ b/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-patches/patches/sticky/sticky.patch b/dwl-patches/patches/sticky/sticky.patch new file mode 100644 index 0000000..ece8308 --- /dev/null +++ b/dwl-patches/patches/sticky/sticky.patch @@ -0,0 +1,85 @@ +From f113cdc0b4cecceaaf28679489852ae61a1aa3f5 Mon Sep 17 00:00:00 2001 +From: Rutherther <rutherther@proton.me> +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-patches/patches/swallow/README.md b/dwl-patches/patches/swallow/README.md new file mode 100644 index 0000000..6c16162 --- /dev/null +++ b/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-patches/patches/swallow/swallow-freebsd.patch b/dwl-patches/patches/swallow/swallow-freebsd.patch new file mode 100644 index 0000000..795a5e1 --- /dev/null +++ b/dwl-patches/patches/swallow/swallow-freebsd.patch @@ -0,0 +1,54 @@ +From 301e1b368d92a32a7bdcd4bd1f0ede0295977e3e Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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 <xcb/xcb_icccm.h> + #endif + ++#ifdef __FreeBSD__ ++#define __BSD_VISIBLE ++#include <sys/types.h> ++#include <sys/param.h> ++#include <sys/sysctl.h> ++#include <sys/user.h> ++#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-patches/patches/swallow/swallow.patch b/dwl-patches/patches/swallow/swallow.patch new file mode 100644 index 0000000..ee1a1c1 --- /dev/null +++ b/dwl-patches/patches/swallow/swallow.patch @@ -0,0 +1,351 @@ +From a220e1ed4b04a66c837dfc8e3363d3e696cbf541 Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com> +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-patches/patches/swapandfocusdir/README.md b/dwl-patches/patches/swapandfocusdir/README.md new file mode 100644 index 0000000..2861a07 --- /dev/null +++ b/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-patches/patches/swapandfocusdir/swapandfocusdir.patch b/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch new file mode 100644 index 0000000..3636387 --- /dev/null +++ b/dwl-patches/patches/swapandfocusdir/swapandfocusdir.patch @@ -0,0 +1,221 @@ +From 285470897406b653e77d732a77356aaf9a70b799 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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-patches/patches/switchtotag/README.md b/dwl-patches/patches/switchtotag/README.md new file mode 100644 index 0000000..55175d1 --- /dev/null +++ b/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-patches/patches/switchtotag/switchtotag.patch b/dwl-patches/patches/switchtotag/switchtotag.patch new file mode 100644 index 0000000..bbe1638 --- /dev/null +++ b/dwl-patches/patches/switchtotag/switchtotag.patch @@ -0,0 +1,116 @@ +From 08cfc2eceb34316cfd7eaf591a8a631e3d58ff3a Mon Sep 17 00:00:00 2001 +From: Guido Cella <guido@guidocella.xyz> +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-patches/patches/tab/README.md b/dwl-patches/patches/tab/README.md new file mode 100644 index 0000000..9f08765 --- /dev/null +++ b/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-patches/patches/tab/tab-0.7-bar.patch b/dwl-patches/patches/tab/tab-0.7-bar.patch new file mode 100644 index 0000000..4a79bbc --- /dev/null +++ b/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 <codeberg@gavinm.us> +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 <time.h> + #include <unistd.h> + #include <wayland-server-core.h> ++#include <wayland-util.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> + #include <wlr/render/allocator.h> +@@ -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-patches/patches/tab/tab-0.7.patch b/dwl-patches/patches/tab/tab-0.7.patch new file mode 100644 index 0000000..b7edd4f --- /dev/null +++ b/dwl-patches/patches/tab/tab-0.7.patch @@ -0,0 +1,1252 @@ +From 419fa4d6212806d9e4a3439568f8a103a57de912 Mon Sep 17 00:00:00 2001 +From: dev-gm <codeberg@gavinm.us> +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 <sewn@disroot.org> ++ * Copyright (c) 2024 notchoc <notchoc@disroot.org> ++ * ++ * 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 <bjoern@hoehrmann.de> ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include <stdlib.h> ++#include <fcft/fcft.h> ++#include <pixman-1/pixman.h> ++ ++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 <libinput.h> + #include <linux/input-event-codes.h> + #include <math.h> ++#include <libdrm/drm_fourcc.h> + #include <signal.h> + #include <stdio.h> + #include <stdlib.h> +@@ -12,6 +13,7 @@ + #include <time.h> + #include <unistd.h> + #include <wayland-server-core.h> ++#include <wayland-util.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> + #include <wlr/render/allocator.h> +@@ -58,6 +60,7 @@ + #include <wlr/types/wlr_xdg_decoration_v1.h> + #include <wlr/types/wlr_xdg_output_v1.h> + #include <wlr/types/wlr_xdg_shell.h> ++#include <wlr/interfaces/wlr_buffer.h> + #include <wlr/util/log.h> + #include <wlr/util/region.h> + #include <xkbcommon/xkbcommon.h> +@@ -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-patches/patches/tab/tab.patch b/dwl-patches/patches/tab/tab.patch new file mode 100644 index 0000000..49276ad --- /dev/null +++ b/dwl-patches/patches/tab/tab.patch @@ -0,0 +1,1252 @@ +From d722eef53d488c9e9ed04991112376035e8c9b25 Mon Sep 17 00:00:00 2001 +From: dev-gm <codeberg@gavinm.us> +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 <sewn@disroot.org> ++ * Copyright (c) 2024 notchoc <notchoc@disroot.org> ++ * ++ * 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 <bjoern@hoehrmann.de> ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include <stdlib.h> ++#include <fcft/fcft.h> ++#include <pixman-1/pixman.h> ++ ++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 <libinput.h> + #include <linux/input-event-codes.h> + #include <math.h> ++#include <libdrm/drm_fourcc.h> + #include <signal.h> + #include <stdio.h> + #include <stdlib.h> +@@ -12,6 +13,7 @@ + #include <time.h> + #include <unistd.h> + #include <wayland-server-core.h> ++#include <wayland-util.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> + #include <wlr/render/allocator.h> +@@ -59,6 +61,7 @@ + #include <wlr/types/wlr_xdg_decoration_v1.h> + #include <wlr/types/wlr_xdg_output_v1.h> + #include <wlr/types/wlr_xdg_shell.h> ++#include <wlr/interfaces/wlr_buffer.h> + #include <wlr/util/log.h> + #include <wlr/util/region.h> + #include <xkbcommon/xkbcommon.h> +@@ -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-patches/patches/tablet-input/README.md b/dwl-patches/patches/tablet-input/README.md new file mode 100644 index 0000000..3b3be09 --- /dev/null +++ b/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-patches/patches/tablet-input/tablet-input-0.7.patch b/dwl-patches/patches/tablet-input/tablet-input-0.7.patch new file mode 100644 index 0000000..37fdf8a --- /dev/null +++ b/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 <notchoc@proton.me> +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 <wlr/types/wlr_session_lock_v1.h> + #include <wlr/types/wlr_single_pixel_buffer_v1.h> + #include <wlr/types/wlr_subcompositor.h> ++#include <wlr/types/wlr_tablet_tool.h> ++#include <wlr/types/wlr_tablet_pad.h> ++#include <wlr/types/wlr_tablet_v2.h> + #include <wlr/types/wlr_viewporter.h> + #include <wlr/types/wlr_virtual_keyboard_v1.h> + #include <wlr/types/wlr_virtual_pointer_v1.h> +@@ -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-patches/patches/tablet-input/tablet-input.patch b/dwl-patches/patches/tablet-input/tablet-input.patch new file mode 100644 index 0000000..e7d1211 --- /dev/null +++ b/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 <dwl@ivories.org> +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 <wlr/types/wlr_session_lock_v1.h> + #include <wlr/types/wlr_single_pixel_buffer_v1.h> + #include <wlr/types/wlr_subcompositor.h> ++#include <wlr/types/wlr_tablet_tool.h> ++#include <wlr/types/wlr_tablet_pad.h> ++#include <wlr/types/wlr_tablet_v2.h> + #include <wlr/types/wlr_viewporter.h> + #include <wlr/types/wlr_virtual_keyboard_v1.h> + #include <wlr/types/wlr_virtual_pointer_v1.h> +@@ -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-patches/patches/tagshift/README.md b/dwl-patches/patches/tagshift/README.md new file mode 100644 index 0000000..e3d7a03 --- /dev/null +++ b/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-patches/patches/tagshift/tagshift.patch b/dwl-patches/patches/tagshift/tagshift.patch new file mode 100644 index 0000000..fac6033 --- /dev/null +++ b/dwl-patches/patches/tagshift/tagshift.patch @@ -0,0 +1,83 @@ +From 938c63ad0a8a706fba0b4db1c66397e9defdcb92 Mon Sep 17 00:00:00 2001 +From: h3nc4 <me@h3nc4.com> +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-patches/patches/tearing/README.md b/dwl-patches/patches/tearing/README.md new file mode 100644 index 0000000..45efbf9 --- /dev/null +++ b/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-patches/patches/tearing/tearing.patch b/dwl-patches/patches/tearing/tearing.patch new file mode 100644 index 0000000..feea1fd --- /dev/null +++ b/dwl-patches/patches/tearing/tearing.patch @@ -0,0 +1,362 @@ +From c5216442cfb22eed7c2d4196f46c9ad8db3a5425 Mon Sep 17 00:00:00 2001 +From: korei999 <ju7t1xe@gmail.com> +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 <wlr/types/wlr_session_lock_v1.h> + #include <wlr/types/wlr_single_pixel_buffer_v1.h> + #include <wlr/types/wlr_subcompositor.h> ++#include <wlr/types/wlr_tearing_control_v1.h> + #include <wlr/types/wlr_viewporter.h> + #include <wlr/types/wlr_virtual_keyboard_v1.h> + #include <wlr/types/wlr_virtual_pointer_v1.h> +@@ -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-patches/patches/titleurgent/README.md b/dwl-patches/patches/titleurgent/README.md new file mode 100644 index 0000000..aa4c99d --- /dev/null +++ b/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-patches/patches/titleurgent/titleurgent.patch b/dwl-patches/patches/titleurgent/titleurgent.patch new file mode 100644 index 0000000..9a73f66 --- /dev/null +++ b/dwl-patches/patches/titleurgent/titleurgent.patch @@ -0,0 +1,80 @@ +From ab002204c8bb7c8c36828aeb89ef3cf9b0447ea6 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/toggle_constraints/README.md b/dwl-patches/patches/toggle_constraints/README.md new file mode 100644 index 0000000..7f2be77 --- /dev/null +++ b/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-patches/patches/toggle_constraints/toggle_constraints.patch b/dwl-patches/patches/toggle_constraints/toggle_constraints.patch new file mode 100644 index 0000000..e823377 --- /dev/null +++ b/dwl-patches/patches/toggle_constraints/toggle_constraints.patch @@ -0,0 +1,86 @@ +From 13bb9d86a0da2ebc82c20ee721b3eeb28592b0a0 Mon Sep 17 00:00:00 2001 +From: David Donahue <david.donahue2996@gmail.com> +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-patches/patches/togglekblayoutandoptions/README.md b/dwl-patches/patches/togglekblayoutandoptions/README.md new file mode 100644 index 0000000..883dd6d --- /dev/null +++ b/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-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch b/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch new file mode 100644 index 0000000..dc7fd81 --- /dev/null +++ b/dwl-patches/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch @@ -0,0 +1,160 @@ +From 9ee76adc7c6567650e7b34273f953fed03191c05 Mon Sep 17 00:00:00 2001 +From: LaKato <lakato@noreply.codeberg.org> +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-patches/patches/touch-input/README.md b/dwl-patches/patches/touch-input/README.md new file mode 100644 index 0000000..3d37fc2 --- /dev/null +++ b/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-patches/patches/touch-input/touch-input.patch b/dwl-patches/patches/touch-input/touch-input.patch new file mode 100644 index 0000000..bfd33e7 --- /dev/null +++ b/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 <dwl@ivories.org> +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 <wlr/types/wlr_session_lock_v1.h> + #include <wlr/types/wlr_single_pixel_buffer_v1.h> + #include <wlr/types/wlr_subcompositor.h> ++#include <wlr/types/wlr_touch.h> + #include <wlr/types/wlr_viewporter.h> + #include <wlr/types/wlr_virtual_keyboard_v1.h> + #include <wlr/types/wlr_virtual_pointer_v1.h> +@@ -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-patches/patches/unclutter/README.md b/dwl-patches/patches/unclutter/README.md new file mode 100644 index 0000000..0eeec1f --- /dev/null +++ b/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)
+- [2024-08-06](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-patches/patches/unclutter/unclutter.patch b/dwl-patches/patches/unclutter/unclutter.patch new file mode 100644 index 0000000..406e5a7 --- /dev/null +++ b/dwl-patches/patches/unclutter/unclutter.patch @@ -0,0 +1,188 @@ +From 52d5a05a6b4ca9555c072e103c2db454d2b35021 Mon Sep 17 00:00:00 2001 +From: Guido Cella <guido@guidocella.xyz> +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 | 70 ++++++++++++++++++++++++++++++++++++++++++++++------ + 2 files changed, 65 insertions(+), 7 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 72892d9..4090d73 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -292,6 +292,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); +@@ -396,6 +398,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; +@@ -588,6 +598,7 @@ 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); ++ 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. */ +@@ -606,6 +617,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: +@@ -1517,6 +1529,32 @@ handlesig(int signo) + } + } + ++void ++handlecursoractivity(void) ++{ ++ wl_event_source_timer_update(hide_source, cursor_timeout * 1000); ++ ++ if (!cursor_hidden) ++ return; ++ ++ cursor_hidden = false; ++ ++ if (last_cursor.shape) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(last_cursor.shape)); ++ else ++ wlr_cursor_set_surface(cursor, last_cursor.surface, ++ last_cursor.hotspot_x, last_cursor.hotspot_y); ++} ++ ++int ++hidecursor(void *data) ++{ ++ wlr_cursor_unset_image(cursor); ++ cursor_hidden = true; ++ return 1; ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1856,6 +1894,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) +@@ -1880,7 +1919,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); +@@ -2263,6 +2302,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 +@@ -2286,9 +2326,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 +@@ -2300,9 +2347,14 @@ setcursorshape(struct wl_listener *listener, void *data) + /* This can be sent by any client, so we check to make sure this one is + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_xcursor(cursor, cursor_mgr, +- wlr_cursor_shape_v1_name(event->shape)); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = event->shape; ++ last_cursor.surface = NULL; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(event->shape)); ++ } + } + + void +@@ -2604,6 +2656,9 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + LISTEN_STATIC(&cursor_shape_mgr->events.request_set_shape, setcursorshape); + ++ 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, +@@ -2986,6 +3041,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.45.2 + diff --git a/dwl-patches/patches/ungroup-keyboards/README.md b/dwl-patches/patches/ungroup-keyboards/README.md new file mode 100644 index 0000000..e56f94b --- /dev/null +++ b/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-patches/patches/ungroup-keyboards/ungroup-keyboards.patch b/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch new file mode 100644 index 0000000..b3249b6 --- /dev/null +++ b/dwl-patches/patches/ungroup-keyboards/ungroup-keyboards.patch @@ -0,0 +1,97 @@ +From d9b9797680ae58bdb910e3bc1f71408f6b67c0d5 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/vanitygaps/README.md b/dwl-patches/patches/vanitygaps/README.md new file mode 100644 index 0000000..cbdf6b4 --- /dev/null +++ b/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-patches/patches/vanitygaps/vanitygaps-0.7.patch b/dwl-patches/patches/vanitygaps/vanitygaps-0.7.patch new file mode 100644 index 0000000..622b444 --- /dev/null +++ b/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 <leohdz172@proton.me> +--- + 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?= + <leohdz172@protonmail.com> +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 <leohdz172@proton.me> +--- + 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-patches/patches/vanitygaps/vanitygaps.patch b/dwl-patches/patches/vanitygaps/vanitygaps.patch new file mode 100644 index 0000000..db1ddf4 --- /dev/null +++ b/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 <leohdz172@proton.me> +--- + 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?= + <leohdz172@protonmail.com> +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 <leohdz172@proton.me> +--- + 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-patches/patches/varcol/README.md b/dwl-patches/patches/varcol/README.md new file mode 100644 index 0000000..9e5b1b0 --- /dev/null +++ b/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-patches/patches/varcol/varcol.patch b/dwl-patches/patches/varcol/varcol.patch new file mode 100644 index 0000000..b94baa2 --- /dev/null +++ b/dwl-patches/patches/varcol/varcol.patch @@ -0,0 +1,618 @@ +From 24c197ea93388c222b72c2192b968aa80b444a9a Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell <m@minego.net> +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 <math.h> ++ ++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-patches/patches/viewnextocctag/README.md b/dwl-patches/patches/viewnextocctag/README.md new file mode 100644 index 0000000..d1a43d2 --- /dev/null +++ b/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-patches/patches/viewnextocctag/viewnextocctag.patch b/dwl-patches/patches/viewnextocctag/viewnextocctag.patch new file mode 100644 index 0000000..f797431 --- /dev/null +++ b/dwl-patches/patches/viewnextocctag/viewnextocctag.patch @@ -0,0 +1,92 @@ +From 330fa634a83e9b332494fade75552e02583bad6c Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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<TAGCOUNT; i++) { ++ seltag = (direction > 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-patches/patches/virtual-pointer/README.md b/dwl-patches/patches/virtual-pointer/README.md new file mode 100644 index 0000000..451d26e --- /dev/null +++ b/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-patches/patches/virtual-pointer/virtual-pointer.patch b/dwl-patches/patches/virtual-pointer/virtual-pointer.patch new file mode 100644 index 0000000..e0a1803 --- /dev/null +++ b/dwl-patches/patches/virtual-pointer/virtual-pointer.patch @@ -0,0 +1,126 @@ +From 4ab53a41256c8f2eac4c003c43b798b6aa312919 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <wlr/types/wlr_subcompositor.h> + #include <wlr/types/wlr_viewporter.h> + #include <wlr/types/wlr_virtual_keyboard_v1.h> ++#include <wlr/types/wlr_virtual_pointer_v1.h> + #include <wlr/types/wlr_xcursor_manager.h> + #include <wlr/types/wlr_xdg_activation_v1.h> + #include <wlr/types/wlr_xdg_decoration_v1.h> +@@ -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-patches/patches/warpcursor/README.md b/dwl-patches/patches/warpcursor/README.md new file mode 100644 index 0000000..34dadb9 --- /dev/null +++ b/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-patches/patches/warpcursor/warpcursor.patch b/dwl-patches/patches/warpcursor/warpcursor.patch new file mode 100644 index 0000000..6ac0329 --- /dev/null +++ b/dwl-patches/patches/warpcursor/warpcursor.patch @@ -0,0 +1,71 @@ +From 4951ccc89dac2b557994b2f6c3aacb2398e2d1b1 Mon Sep 17 00:00:00 2001 +From: Ben Collerson <benc@benc.cc> +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-patches/patches/wayland-socket-handover/README.md b/dwl-patches/patches/wayland-socket-handover/README.md new file mode 100644 index 0000000..ffc278a --- /dev/null +++ b/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-patches/patches/wayland-socket-handover/wayland-socket-handover.patch b/dwl-patches/patches/wayland-socket-handover/wayland-socket-handover.patch new file mode 100644 index 0000000..ad026ff --- /dev/null +++ b/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 <ferdinand.bachmann@yrlf.at> +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 <sys/wait.h> + #include <time.h> + #include <unistd.h> ++#include <fcntl.h> + #include <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> +@@ -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-patches/patches/winview/README.md b/dwl-patches/patches/winview/README.md new file mode 100644 index 0000000..4fb8dd5 --- /dev/null +++ b/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 <https://dwm.suckless.org/patches/winview/>. 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-patches/patches/winview/winview-20240606.patch b/dwl-patches/patches/winview/winview-20240606.patch new file mode 100644 index 0000000..d3fc099 --- /dev/null +++ b/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-patches/patches/winview/winview.patch b/dwl-patches/patches/winview/winview.patch new file mode 100644 index 0000000..66e6b20 --- /dev/null +++ b/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-patches/patches/xwayland-handle-minimize/README.md b/dwl-patches/patches/xwayland-handle-minimize/README.md new file mode 100644 index 0000000..dde0935 --- /dev/null +++ b/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-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch b/dwl-patches/patches/xwayland-handle-minimize/xwayland-handle-minimize.patch new file mode 100644 index 0000000..d6d1f8f --- /dev/null +++ b/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 <ju7t1xe@gmail.com> +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-patches/patches/zoomswap/README.md b/dwl-patches/patches/zoomswap/README.md new file mode 100644 index 0000000..83cbeb1 --- /dev/null +++ b/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-patches/patches/zoomswap/zoomswap-v0.6.patch b/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch new file mode 100644 index 0000000..93de288 --- /dev/null +++ b/dwl-patches/patches/zoomswap/zoomswap-v0.6.patch @@ -0,0 +1,55 @@ +From 2b4e1a8bbb2d17a3da5ca54f2995469dfec5bbbb Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/zoomswap/zoomswap-v0.7.patch b/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch new file mode 100644 index 0000000..d20ff32 --- /dev/null +++ b/dwl-patches/patches/zoomswap/zoomswap-v0.7.patch @@ -0,0 +1,55 @@ +From cd4ce3f98b4231515c1363070a7fb5f9654a40bc Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/patches/zoomswap/zoomswap.patch b/dwl-patches/patches/zoomswap/zoomswap.patch new file mode 100644 index 0000000..76e4466 --- /dev/null +++ b/dwl-patches/patches/zoomswap/zoomswap.patch @@ -0,0 +1,55 @@ +From c82be3e2069fdb9cbea0da22a4c9ed69f4ab5aea Mon Sep 17 00:00:00 2001 +From: Palanix <palanixyt@gmail.com> +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-patches/stale-patches/bar-systray-old/README.md b/dwl-patches/stale-patches/bar-systray-old/README.md new file mode 100644 index 0000000..9c8b94e --- /dev/null +++ b/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 ^^ + + + +**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-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch b/dwl-patches/stale-patches/bar-systray-old/bar-systray-0.7.patch new file mode 100644 index 0000000..eae4561 --- /dev/null +++ b/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?= <janne.vetelainen@elisanet.fi> +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 <snsystray.h> ++#include <gdk/gdk.h> ++#include <gio/gio.h> ++#include <glib-object.h> ++#include <glib.h> ++#include <gtk/gtk.h> ++#include <gtk4-layer-shell.h> ++#include <pthread.h> ++ + /* 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-patches/stale-patches/bar-systray-old/systray.png b/dwl-patches/stale-patches/bar-systray-old/systray.png Binary files differnew file mode 100644 index 0000000..54f9f98 --- /dev/null +++ b/dwl-patches/stale-patches/bar-systray-old/systray.png diff --git a/dwl-patches/stale-patches/master-right/README.md b/dwl-patches/stale-patches/master-right/README.md new file mode 100644 index 0000000..0f69bbc --- /dev/null +++ b/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-patches/stale-patches/master-right/master-right-0.7.patch b/dwl-patches/stale-patches/master-right/master-right-0.7.patch new file mode 100644 index 0000000..e4845f4 --- /dev/null +++ b/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 <leohdz172@outlook.com> +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 <leohdz172@proton.me> +--- + 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-patches/stale-patches/master-right/master-right.patch b/dwl-patches/stale-patches/master-right/master-right.patch new file mode 100644 index 0000000..70c80a1 --- /dev/null +++ b/dwl-patches/stale-patches/master-right/master-right.patch @@ -0,0 +1,35 @@ +From 0afd0a98998dda20e4fe4f4d2b5fcdec49c448c3 Mon Sep 17 00:00:00 2001 +From: Sevz17 <leohdz172@outlook.com> +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 <leohdz172@proton.me> +--- + 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-patches/stale-patches/press_repeat_release/README.md b/dwl-patches/stale-patches/press_repeat_release/README.md new file mode 100644 index 0000000..b9b4ff3 --- /dev/null +++ b/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-patches/stale-patches/press_repeat_release/press_repeat_release.patch b/dwl-patches/stale-patches/press_repeat_release/press_repeat_release.patch new file mode 100644 index 0000000..b86666d --- /dev/null +++ b/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 <m@minego.net> +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 <m@minego.net> +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-patches/stale-patches/remembertags/README.md b/dwl-patches/stale-patches/remembertags/README.md new file mode 100644 index 0000000..b45e87c --- /dev/null +++ b/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-patches/stale-patches/remembertags/remembertags.patch b/dwl-patches/stale-patches/remembertags/remembertags.patch new file mode 100644 index 0000000..fd6135e --- /dev/null +++ b/dwl-patches/stale-patches/remembertags/remembertags.patch @@ -0,0 +1,105 @@ +From fea6eb3cfc84ede8403c89a3230f5c658a6c7bd1 Mon Sep 17 00:00:00 2001 +From: Micah N Gorrell <m@minego.net> +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-patches/stale-patches/tab-pango/README.md b/dwl-patches/stale-patches/tab-pango/README.md new file mode 100644 index 0000000..c7e4a1c --- /dev/null +++ b/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-patches/stale-patches/tab-pango/tab.patch b/dwl-patches/stale-patches/tab-pango/tab.patch new file mode 100644 index 0000000..5b7b8a0 --- /dev/null +++ b/dwl-patches/stale-patches/tab-pango/tab.patch @@ -0,0 +1,461 @@ +From b624206781513cdff1b9609fc5ac4b848094e1b4 Mon Sep 17 00:00:00 2001 +From: Gavin M <github@gavinm.us> +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 <getopt.h> ++#include <cairo/cairo.h> ++#include <pango/pangocairo.h> ++#include <pango/pango-font.h> ++#include <pango/pango-layout.h> ++#include <libdrm/drm_fourcc.h> + #include <libinput.h> + #include <linux/input-event-codes.h> + #include <signal.h> +@@ -13,8 +18,10 @@ + #include <wayland-server-core.h> + #include <wlr/backend.h> + #include <wlr/backend/libinput.h> ++#include <wlr/interfaces/wlr_buffer.h> + #include <wlr/render/allocator.h> + #include <wlr/render/wlr_renderer.h> ++#include <wlr/types/wlr_buffer.h> + #include <wlr/types/wlr_compositor.h> + #include <wlr/types/wlr_cursor.h> + #include <wlr/types/wlr_cursor_shape_v1.h> +@@ -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-patches/stale-patches/togglekblayout/README.md b/dwl-patches/stale-patches/togglekblayout/README.md new file mode 100644 index 0000000..366a6d8 --- /dev/null +++ b/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-patches/stale-patches/togglekblayout/togglekblayout.patch b/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch new file mode 100644 index 0000000..4ef802a --- /dev/null +++ b/dwl-patches/stale-patches/togglekblayout/togglekblayout.patch @@ -0,0 +1,107 @@ +From 1bb99c78da484ce6036dc997962ed2f4c0d11208 Mon Sep 17 00:00:00 2001 +From: wochap <gean.marroquin@gmail.com> +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 <gean.marroquin@gmail.com> +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/scripts/dwl.sh b/scripts/dwl.sh new file mode 100755 index 0000000..a5ca49d --- /dev/null +++ b/scripts/dwl.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +dwl -s .local/bin/dwl-startup.sh |