diff --git a/README.md b/README.md index 5640102..2628c9a 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,4 @@ Then add `kutu.rb` to your `~/.xinitrc` file. ## TODO -Add `ruby` like configuration files support. +Add better `ruby` configuration files support. diff --git a/setup.sh b/setup.sh index b8a8d6e..64f54e9 100755 --- a/setup.sh +++ b/setup.sh @@ -9,13 +9,9 @@ if ! XCB=$(pkg-config --cflags --libs xcb xcb-icccm 2>/dev/null); then exit 1 fi -if ! xrandr --version >/dev/null 2>&1; then - echo "Error: xrandr not found. Please install xrandr." >&2 - exit 1 -fi - mkdir -p "$DIR/build" +# shellcheck disable=SC2086 gcc -shared -fPIC -Wall -Wextra -o "$DIR/build/X-kutu.so" "$DIR/src/c/X-kutu.c" $XCB if [ ! -f "$DIR/build/X-kutu.so" ]; then diff --git a/src/c/X-kutu.c b/src/c/X-kutu.c index ca24edc..27c35e7 100644 --- a/src/c/X-kutu.c +++ b/src/c/X-kutu.c @@ -21,7 +21,7 @@ void cleanup(void) { // Keybind function to setup a key grab if the keycode is clicked along with the // MOD key void add_keybind(int key, int mod) { - xcb_grab_key(conn, 0, scr->root, mod ? MOD : 0, key, XCB_GRAB_MODE_ASYNC, + xcb_grab_key(conn, 0, scr->root, mod, key, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); } @@ -31,7 +31,7 @@ void add_mousebind(int button, int mod) { xcb_grab_button(conn, 0, scr->root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, scr->root, XCB_NONE, - button, mod ? MOD : 0); + button, mod); } // Deploy function to initialize the X connection, set up event masks, and @@ -84,7 +84,38 @@ void subscribe(xcb_window_t win) { } // Kill a window -void kill(xcb_window_t window) { xcb_kill_client(conn, window); } +void kill(xcb_window_t window) { + xcb_intern_atom_cookie_t wm_protocols_cookie = + xcb_intern_atom(conn, 1, 12, "WM_PROTOCOLS"); + xcb_intern_atom_cookie_t wm_delete_window_cookie = + xcb_intern_atom(conn, 0, 16, "WM_DELETE_WINDOW"); + + xcb_intern_atom_reply_t *wm_protocols = + xcb_intern_atom_reply(conn, wm_protocols_cookie, NULL); + xcb_intern_atom_reply_t *wm_delete_window = + xcb_intern_atom_reply(conn, wm_delete_window_cookie, NULL); + + if (!wm_protocols || !wm_delete_window) { + // fallback to force kill + xcb_kill_client(conn, window); + xcb_flush(conn); + return; + } + + xcb_client_message_event_t ev = {0}; + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = window; + ev.type = wm_protocols->atom; + ev.format = 32; + ev.data.data32[0] = wm_delete_window->atom; + ev.data.data32[1] = XCB_CURRENT_TIME; + + xcb_send_event(conn, 0, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); + xcb_flush(conn); + + free(wm_protocols); + free(wm_delete_window); +} // Destroy a window void destroy(xcb_window_t win) { xcb_destroy_window(conn, win); } @@ -111,12 +142,12 @@ Geometry get_geometry(xcb_window_t win) { } // Get the current pointer position -Geometry get_pointer(void) { +PointerInfo get_pointer(void) { xcb_query_pointer_reply_t *geom; geom = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, scr->root), 0); - Geometry g = {geom->root_x, geom->root_y, 0, 0}; + PointerInfo p = {geom->root_x, geom->root_y, geom->child}; free(geom); - return g; + return p; } // Get the screen geometry @@ -301,7 +332,7 @@ Event translate_event(xcb_generic_event_t *ev) { ret.x = e->event_x; ret.y = e->event_y; ret.btn = e->detail; - ret.state = (e->state & MOD) != 0; + ret.state = e->state; } break; case XCB_MOTION_NOTIFY: { @@ -326,7 +357,7 @@ Event translate_event(xcb_generic_event_t *ev) { ret.type = 9; ret.window = e->child; ret.btn = e->detail; - ret.state = (e->state & MOD) != 0; + ret.state = e->state; } break; case XCB_KEY_RELEASE: { diff --git a/src/c/X-kutu.h b/src/c/X-kutu.h index 04b4512..961aaa6 100644 --- a/src/c/X-kutu.h +++ b/src/c/X-kutu.h @@ -3,6 +3,7 @@ // Standard headers #include +#include #include #include #include @@ -36,6 +37,12 @@ typedef struct Geometry { uint16_t height; } Geometry; +typedef struct PointerInfo { + int16_t x; + int16_t y; + uint32_t window; +} PointerInfo; + // Event structure to represent various X events typedef struct Event { int32_t type; @@ -70,7 +77,7 @@ void hide(xcb_window_t window); void send_to_top(xcb_window_t win); Geometry get_geometry(xcb_window_t win); -Geometry get_pointer(void); +PointerInfo get_pointer(void); Geometry get_screen(void); void free_geometry(Geometry *g); @@ -93,4 +100,8 @@ void ungrab_pointer(void); xcb_generic_event_t *next_event(void); Event translate_event(xcb_generic_event_t *ev); +extern inline int mod() { return MOD; } +extern inline int clk() { return XCB_MOD_MASK_LOCK; } +extern inline int num() { return XCB_MOD_MASK_2; } + #endif // X_KUTU_H diff --git a/src/ruby/X-kutu.rb b/src/ruby/X-kutu.rb index 403789f..22ec920 100644 --- a/src/ruby/X-kutu.rb +++ b/src/ruby/X-kutu.rb @@ -14,6 +14,12 @@ module X :height, :uint16 end + class PointerInfo < FFI::Struct + layout :x, :int16, + :y, :int16, + :window, :xcb_window_t + end + class Event < FFI::Struct layout :type, :int32, :window, :xcb_window_t, @@ -75,7 +81,7 @@ module X attach_function :send_to_top, [:xcb_window_t], :void attach_function :free_geometry, [:pointer], :void attach_function :get_geometry, [:xcb_window_t], Geometry.by_value - attach_function :get_pointer, [], Geometry.by_value + attach_function :get_pointer, [], PointerInfo.by_value attach_function :get_screen, [], Geometry.by_value attach_function :warp_pointer, [:xcb_window_t, :int, :int], :void attach_function :move_window, [:xcb_window_t, :int, :int], :void @@ -91,4 +97,7 @@ module X attach_function :grab_pointer, [:xcb_window_t], :void attach_function :ungrab_pointer, [], :void attach_function :get_root, [], :xcb_window_t + attach_function :mod, [], :int + attach_function :clk, [], :int + attach_function :num, [], :int end diff --git a/src/ruby/bindings.rb b/src/ruby/bindings.rb index d09d722..71768ca 100644 --- a/src/ruby/bindings.rb +++ b/src/ruby/bindings.rb @@ -1,20 +1,25 @@ -keybind 23 do |_event| +presets = { + normal: [0, X.clk], + mod: [X.mod, X.mod | X.clk] +} + +keybind 23, presets[:mod] do |_event| run "firefox" end -keybind 24 do |event| +keybind 24, presets[:mod] do |event| X.kill event[:window] end -keybind 25 do |_event| +keybind 25, presets[:mod] do |_event| run "kitty" end -keybind 26 do |_event| +keybind 26, presets[:mod] do |_event| run File.join(__dir__, "../shell/power.sh") end -keybind 123, 0 do |_event| +keybind 123, presets[:normal] do |_event| run %q( pactl set-sink-volume @DEFAULT_SINK@ +5%; vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n1); @@ -22,7 +27,7 @@ keybind 123, 0 do |_event| ) end -keybind 122, 0 do |_event| +keybind 122, presets[:normal] do |_event| run %q( pactl set-sink-volume @DEFAULT_SINK@ -5%; vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n1); @@ -30,7 +35,7 @@ keybind 122, 0 do |_event| ) end -keybind 121, 0 do |_event| +keybind 121, presets[:normal] do |_event| run %q( pactl set-sink-mute @DEFAULT_SINK@ toggle; vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\\+%' | head -n1); @@ -38,7 +43,7 @@ keybind 121, 0 do |_event| ) end -keybind 38 do |_event| +keybind 38, presets[:mod] do |_event| run %Q( maim -c 0.3,0.5,1.0,0.8 -s | tee /tmp/screenshot_temp.png | xclip -selection clipboard -t image/png; if [ -s '/tmp/screenshot_temp.png' ]; then @@ -47,7 +52,7 @@ keybind 38 do |_event| ) end -keybind 232, 0 do |_event| +keybind 232, presets[:normal] do |_event| run %q( brightnessctl set 5%-; pct=$(brightnessctl -m | cut -d, -f4 | tr -d ' %'); @@ -55,7 +60,7 @@ keybind 232, 0 do |_event| ) end -keybind 233, 0 do |_event| +keybind 233, presets[:normal] do |_event| run %q( brightnessctl set 5%+; pct=$(brightnessctl -m | cut -d, -f4 | tr -d ' %'); @@ -63,27 +68,31 @@ keybind 233, 0 do |_event| ) end -keybind 54 do |_event| +keybind 54, presets[:mod] do |_event| run "kitty -e fish -c \"y\"" end -keybind 53 do |_event| +keybind 53, presets[:mod] do |_event| run "kitty -e fish -c \"editor\"" end -keybind 40 do |_event| +keybind 41, presets[:mod] do |_event| + run "kitty -e fish -c \"btop\"" +end + +keybind 40, presets[:mod] do |_event| run File.join(__dir__, "../shell/run.sh") end -keybind 33 do |_event| +keybind 33, presets[:mod] do |_event| run "~/.config/polybar/launch.sh" end -keybind 45 do |_event| +keybind 45, presets[:mod] do |_event| run File.join(__dir__, "../shell/caffiene.sh") end -keybind 56 do |_event| +keybind 56, presets[:mod] do |_event| monitor = current_monitor create_workspace monitor persistence_path = File.join(__dir__, ".num.json") @@ -95,7 +104,7 @@ keybind 56 do |_event| File.write(persistence_path, JSON.pretty_generate(persistence)) end -keybind 57 do |_event| +keybind 57, presets[:mod] do |_event| monitor = current_monitor delete_workspace monitor[:workspaces].length - 1, monitor persistence_path = File.join(__dir__, ".num.json") @@ -107,7 +116,7 @@ keybind 57 do |_event| File.write(persistence_path, JSON.pretty_generate(persistence)) end -keybind 110, 0 do |_event| +keybind 110, presets[:normal] do |_event| run %q( dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Pause; dbus-send --print-reply --dest=$(busctl --user list | grep -oP 'org.mpris.MediaPlayer2.firefox.instance_1_\\d+') \ @@ -115,7 +124,15 @@ keybind 110, 0 do |_event| ) end -keybind 110 do |_event| +keybind 173, presets[:mod] do |_event| + run %q(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous) +end + +keybind 171, presets[:mod] do |_event| + run %q(dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next) +end + +keybind 110, presets[:mod] do |_event| monitor = current_monitor persistence_path = File.join(__dir__, ".num.json") persistence = File.exist?(persistence_path) ? @@ -131,7 +148,7 @@ keybind 110 do |_event| end end -keybind 115 do |event| +keybind 89, presets[:mod] do |event| monitor = current_monitor persistence_path = File.join(__dir__, ".num.json") persistence = File.exist?(persistence_path) ? @@ -144,80 +161,91 @@ keybind 115 do |event| ws.drop ws.tiled_windows.length, $windows[event[:window]] if $windows[event[:window]] end -keybind 118, 0 do |_event| +keybind 118, presets[:normal] do |_event| run "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause" end -keybind 117, 0 do |_event| +keybind 86, presets[:normal] do |_event| monitor = current_monitor next if monitor[:selected_workspace].zero? next_ws = (monitor[:selected_workspace] % (monitor[:workspaces].length - 1)) + 1 select_workspace next_ws, monitor end -keybind 117 do |event| +keybind 86, presets[:mod] do |event| monitor = current_monitor next if monitor[:selected_workspace].zero? ws = monitor[:workspaces][(monitor[:selected_workspace] % (monitor[:workspaces].length - 1)) + 1] ws.drop ws.tiled_windows.length, $windows[event[:window]] if $windows[event[:window]] end -keybind 112, 0 do |_event| +keybind 82, presets[:normal] do |_event| monitor = current_monitor next if monitor[:selected_workspace].zero? next_ws = ((monitor[:selected_workspace] - 2) % (monitor[:workspaces].length - 1)) + 1 select_workspace next_ws, monitor end -keybind 112 do |event| +keybind 82, presets[:mod] do |event| monitor = current_monitor next if monitor[:selected_workspace].zero? ws = monitor[:workspaces][((monitor[:selected_workspace] - 2) % (monitor[:workspaces].length - 1)) + 1] ws.drop ws.tiled_windows.length, $windows[event[:window]] if $windows[event[:window]] end -keybind 27 do |_event| +keybind 10, presets[:mod] do |_event| + run "setxkbmap us" +end + +keybind 11, presets[:mod] do |_event| + run "setxkbmap ara" +end + +keybind 27, presets[:mod] do |_event| load File.join(__dir__, "./bindings.rb") + load File.join(__dir__, "./events.rb") monitor = current_monitor next unless monitor monitor[:workspaces][monitor[:selected_workspace]].switch_direction end -keybind 55 do |_event| - run "rofi -modi 'clipboard:greenclip print' -show clipboard -run-command '{cmd}'" +keybind 55, presets[:mod] do |_event| + run %q( + CM_LAUNCHER=rofi clipmenu + ) end -keybind 39 do |event| +keybind 39, presets[:mod] do |event| window = $windows[event[:window]] window.toggle_floating if window end -mousebind 1 +mousebind 1, presets[:mod] {|x|} -mousebind 3 +mousebind 3, presets[:mod] {|x|} -mousebind 9, 0 do |_event| +mousebind 9, presets[:normal] do |_event| monitor = current_monitor next if monitor[:selected_workspace].zero? next_ws = (monitor[:selected_workspace] % (monitor[:workspaces].length - 1)) + 1 select_workspace next_ws, monitor end -mousebind 9 do |_event| +mousebind 9, presets[:mod] do |event| monitor = current_monitor next if monitor[:selected_workspace].zero? ws = monitor[:workspaces][(monitor[:selected_workspace] % (monitor[:workspaces].length - 1)) + 1] ws.drop ws.tiled_windows.length, $windows[event[:window]] if $windows[event[:window]] end -mousebind 8, 0 do |_event| +mousebind 8, presets[:normal] do |_event| monitor = current_monitor next if monitor[:selected_workspace].zero? next_ws = ((monitor[:selected_workspace] - 2) % (monitor[:workspaces].length - 1)) + 1 select_workspace next_ws, monitor end -mousebind 8 do |_event| +mousebind 8, presets[:mod] do |event| monitor = current_monitor next if monitor[:selected_workspace].zero? ws = monitor[:workspaces][((monitor[:selected_workspace] - 2) % (monitor[:workspaces].length - 1)) + 1] diff --git a/src/ruby/commands.rb b/src/ruby/commands.rb index 01f8d07..4145a34 100644 --- a/src/ruby/commands.rb +++ b/src/ruby/commands.rb @@ -19,8 +19,6 @@ def handle_command(command) reply[:workspace] = monitor[:selected_workspace] reply[:monitor] = $monitors.key(monitor) reply[:count] = monitor[:workspaces].length - when "topped" - reply[:topped] = $topped_windows when "stop" exit 1 end diff --git a/src/ruby/events.rb b/src/ruby/events.rb index 7bb795b..caba138 100644 --- a/src/ruby/events.rb +++ b/src/ruby/events.rb @@ -14,7 +14,6 @@ EVENT_TYPES = { 13 => :resize_request }.freeze - def handle_event(event) case EVENT_TYPES[event[:type]] @@ -29,6 +28,15 @@ def handle_event(event) $all_windows.delete event[:window] $windows[event[:window]]&.delete X.focus $root + X.flush + mouse_pos = X.get_pointer + X.focus mouse_pos[:window] if mouse_pos[:window] != 0 + + when :closed_temp + X.focus $root + X.flush + mouse_pos = X.get_pointer + X.focus mouse_pos[:window] if mouse_pos[:window] != 0 when :enter diff --git a/src/ruby/utils.rb b/src/ruby/utils.rb index 6f364bb..0d1edef 100644 --- a/src/ruby/utils.rb +++ b/src/ruby/utils.rb @@ -1,9 +1,17 @@ -def keybind(key, mod = 1, &block) +def keybind(key, mod, &block) + if mod.is_a? Array + mod.each { |m| keybind(key, m, &block) } + return + end X.add_keybind key, mod $keybind_actions[[key, mod]] = block if block end -def mousebind(btn, mod = 1, &block) +def mousebind(btn, mod, &block) + if mod.is_a? Array + mod.each { |m| mousebind(btn, m, &block) } + return + end X.add_mousebind btn, mod $mousebind_actions[[btn, mod]] = block if block end diff --git a/src/ruby/window.rb b/src/ruby/window.rb index dc7d7e2..97e56cd 100644 --- a/src/ruby/window.rb +++ b/src/ruby/window.rb @@ -1,3 +1,7 @@ +def godot_window?(window) + `xprop -id #{window} WM_CLASS`.downcase.include?("godot") +end + class Window attr_accessor :window_id, :floating, :workspace, :def_floating, :size, :x, :y, :width, :height @@ -5,8 +9,14 @@ class Window @size = 0 @workspace = workspace @window_id = window_id + geometry = X.get_geometry(window_id) + @x = geometry[:x] + @y = geometry[:y] + @width = geometry[:width] + @height = geometry[:height] wm_n_hints = X.get_wm_n_hints(window_id) - @floating = false + @floating = godot_window?(window_id) + @def_floating = @floating if wm_n_hints if fixed_size_or_aspect?(wm_n_hints) @floating = true @@ -24,7 +34,7 @@ class Window end X.set_wm_state window_id, 1 transient_for = X.get_wm_transient_for(window_id) - unless transient_for.zero? + if !transient_for.zero? && !godot_window?(window_id) @floating = true @def_floating = true @width = wm_n_hints[:max_width] diff --git a/src/ruby/workspace.rb b/src/ruby/workspace.rb index aaf4432..ec82579 100644 --- a/src/ruby/workspace.rb +++ b/src/ruby/workspace.rb @@ -156,5 +156,6 @@ class Workspace X.show window.window_id X.set_wm_state window.window_id, 1 end + X.focus $root end end diff --git a/src/shell/power.sh b/src/shell/power.sh index bf4f707..c819b02 100755 --- a/src/shell/power.sh +++ b/src/shell/power.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash set -euo pipefail -opts=" +opts=" Lock 󰒲 Suspend - Stop KutuWM  Shutdown -󰜉 Reboot" +󰜉 Reboot + Hibernate + Stop KutuWM" sel=$(printf "%s\n" "$opts" | dmenu -i -p "Select Power Option:" \ - -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'HurmitNerdFont-16') + -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'AgaveNerdFont-16') -# user pressed Esc [ -z "$sel" ] && exit 0 case "$sel" in *Shutdown*) confirm=$(printf "No\nYes" | dmenu -i -p "Are you sure you want to shutdown? :" \ - -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'HurmitNerdFont-16') + -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'AgaveNerdFont-16') [ "$confirm" = "Yes" ] && exec shutdown -h now ;; *Reboot*) @@ -24,11 +24,18 @@ case "$sel" in -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'HurmitNerdFont-16') [ "$confirm" = "Yes" ] && exec reboot ;; +*Hibernate*) + confirm=$(printf "No\nYes" | dmenu -i -p "Are you sure you want to hibernate? :" \ + -nf '#e0af68' -nb '#1f2335' -sb '#f7768e' -sf '#1a1b26' -fn 'AgaveNerdFont-16') + [ "$confirm" = "Yes" ] && exec systemctl hibernate + ;; *Stop\ KutuWM*) exec kutu-run.rb stop ;; *Suspend*) - ~/dotfiles/scripts/lock.sh & exec systemctl suspend ;; +*Lock*) + ~/dotfiles/scripts/lock.sh & + ;; esac diff --git a/src/shell/run.sh b/src/shell/run.sh index f351154..7aba8f6 100755 --- a/src/shell/run.sh +++ b/src/shell/run.sh @@ -2,13 +2,13 @@ choice=$( ( - printf "%s\n" godot mvox mox aseprite terraria + printf "%s\n" godot mvox mox aseprite terraria zomboid silksong factorio oxygen compgen -c ) | grep -v -E '^(if|fi|case|esac|for|done|while|until|select|function|return|continue|break|time|exec|source|alias|builtin|read|export|unset|local|set|declare|typeset|:|\.|\[|coproc|l|ll|ls|then|else|elif|do|in|\{|\}|!|\[\[|\]\]|_.*|compgen)$' | sort | dmenu -i -p "Enter command  " \ - -nf '#4abaaf' -nb '#1f2335' -sb '#7aa2f7' -sf '#102030' -fn 'HurmitNerdFont-16' + -nf '#4abaaf' -nb '#1f2335' -sb '#7aa2f7' -sf '#102030' -fn 'AgaveNerdFont-16' ) [ -z "$choice" ] && exit 0 diff --git a/src/shell/startup.sh b/src/shell/startup.sh index c49e02b..5d8d012 100755 --- a/src/shell/startup.sh +++ b/src/shell/startup.sh @@ -1,15 +1,21 @@ #!/usr/bin/env bash -picom --config ~/.config/i3/picom.conf & -greenclip daemon & +picom --config ~/dotfiles/picom/dot-config/picom/picom.conf & +clipmenud & # xss-lock --transfer-sleep-lock -- ~/dotfiles/scripts/lock.sh & dunst -config ~/.config/dunst/dunstrc & -bluetoothctl power off - -magick -size 1920x1080 xc:#000000 /tmp/f_bg.png -feh --bg-fill /tmp/f_bg.png +#magick -size 1920x1080 xc:#000000 /tmp/f_bg.png +#feh --bg-fill /tmp/f_bg.png xsetroot -cursor_name left_ptr -xrdb ~/.Xresources setxkbmap us + +export XAUTHORITY="$HOME/.Xauthority" + +systemctl --user import-environment DISPLAY XAUTHORITY +dbus-update-activation-environment DISPLAY XAUTHORITY + +systemctl --user restart xdg-desktop-portal +systemctl --user restart xdg-desktop-portal-gtk +