From 11806119df543ab82626477580d180825d4bd311 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sun, 26 Oct 2025 14:16:56 +0000 Subject: [PATCH] Make it work --- .gitignore | 1 + kutu.rb | 22 +++--- src/c/X-kutu.c | 16 ++--- src/c/X-kutu.h | 4 +- src/ruby/X-kutu.rb | 4 +- src/ruby/bindings.rb | 149 +++++++++++++++++++++++++++++++++++++-- src/ruby/controller.rb | 20 ++++++ src/ruby/events.rb | 52 ++++++-------- src/ruby/node.rb | 13 ---- src/ruby/utils.rb | 70 ++++++++---------- src/ruby/window.rb | 64 +++++++++++------ src/ruby/window_block.rb | 64 ----------------- src/ruby/workspace.rb | 143 +++++++++++++++++++++++++++++++------ src/shell/startup.sh | 16 ++++- 14 files changed, 417 insertions(+), 221 deletions(-) create mode 100644 src/ruby/controller.rb delete mode 100644 src/ruby/node.rb delete mode 100644 src/ruby/window_block.rb diff --git a/.gitignore b/.gitignore index d67c77f..777a062 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.so *.vim *p.yml +.num.json diff --git a/kutu.rb b/kutu.rb index 543ba95..73a5552 100755 --- a/kutu.rb +++ b/kutu.rb @@ -16,10 +16,11 @@ end # Require modules -require_relative "./src/ruby/utils" -require_relative "./src/ruby/node" -require_relative "./src/ruby/workspace" -require_relative "./src/ruby/events" +load File.join(__dir__, "./src/ruby/utils.rb") +load File.join(__dir__, "./src/ruby/controller.rb") +load File.join(__dir__, "./src/ruby/window.rb") +load File.join(__dir__, "./src/ruby/workspace.rb") +load File.join(__dir__, "./src/ruby/events.rb") # Cleanup on exit @@ -33,7 +34,6 @@ end # Globals $monitors = {} -$workspaces = {} $windows = {} $keybind_actions = {} @@ -47,23 +47,17 @@ $rect = {} # Initialize monitors -refresh_monitors! +load_monitors! # Run startup script -pid = spawn File.join(__dir__, "/src/shell/startup.sh") -Process.detach pid +run File.join(__dir__, "./src/shell/startup.sh") # Add keybinds -load File.join(__dir__, "/src/ruby/bindings.rb") - - -# Initialize workspaces - -$workspaces[:main] = Workspace.new(:main) +load File.join(__dir__, "./src/ruby/bindings.rb") # Main loop diff --git a/src/c/X-kutu.c b/src/c/X-kutu.c index 0fdae42..0f2209f 100644 --- a/src/c/X-kutu.c +++ b/src/c/X-kutu.c @@ -17,18 +17,18 @@ 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) { - xcb_grab_key(conn, 0, scr->root, MOD, key, XCB_GRAB_MODE_ASYNC, +void add_keybind(int key, int mod) { + xcb_grab_key(conn, 0, scr->root, mod ? MOD : 0, key, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); } // Mousebind function to setup a mouse button grab if the button is clicked // along with the MOD key -void add_mousebind(int button) { +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); + button, mod ? MOD : 0); } // Deploy function to initialize the X connection, set up event masks, and @@ -271,6 +271,8 @@ void ungrab_pointer(void) { Event wait_for_event(void) { Event ret = {0}; + xcb_flush(conn); + xcb_generic_event_t *ev; ev = xcb_wait_for_event(conn); @@ -318,14 +320,13 @@ Event wait_for_event(void) { case XCB_BUTTON_PRESS: { xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev; - if (!e->child) - break; ret.type = 6; ret.window = e->child; ret.is_root = e->child == scr->root; ret.x = e->event_x; ret.y = e->event_y; ret.btn = e->detail; + ret.state = (e->state & MOD) != 0; } break; case XCB_MOTION_NOTIFY: { @@ -350,7 +351,7 @@ Event wait_for_event(void) { ret.type = 9; ret.window = e->child; ret.btn = e->detail; - ret.state = e->state; + ret.state = (e->state & MOD) != 0; } break; case XCB_KEY_RELEASE: { @@ -388,7 +389,6 @@ Event wait_for_event(void) { } break; } - xcb_flush(conn); free(ev); return ret; } diff --git a/src/c/X-kutu.h b/src/c/X-kutu.h index b415bee..7f101c0 100644 --- a/src/c/X-kutu.h +++ b/src/c/X-kutu.h @@ -54,8 +54,8 @@ typedef struct Event { int deploy(void); void cleanup(void); -void add_keybind(int key); -void add_mousebind(int button); +void add_keybind(int key, int mod); +void add_mousebind(int button, int mod); xcb_window_t get_focus(void); xcb_window_t get_root(void); diff --git a/src/ruby/X-kutu.rb b/src/ruby/X-kutu.rb index 3c6648a..0b54b7c 100644 --- a/src/ruby/X-kutu.rb +++ b/src/ruby/X-kutu.rb @@ -61,8 +61,8 @@ module X end attach_function :deploy, [], :int - attach_function :add_keybind, [:int], :void - attach_function :add_mousebind, [:int], :void + attach_function :add_keybind, [:int, :int], :void + attach_function :add_mousebind, [:int, :int], :void attach_function :cleanup, [], :void attach_function :focus, [:xcb_window_t], :void attach_function :get_focus, [], :xcb_window_t diff --git a/src/ruby/bindings.rb b/src/ruby/bindings.rb index 9891b7b..cc1c687 100644 --- a/src/ruby/bindings.rb +++ b/src/ruby/bindings.rb @@ -1,23 +1,162 @@ +keybind(23) do |_event| + run "firefox" +end + keybind(24) do |event| X.kill event[:window] end keybind(25) do |_event| - pid = spawn "kitty" - Process.detach pid + run "kitty" end keybind(26) do |_event| - $workspaces.each_value(&:close_all) + # run "~/dotfiles/scripts/power.sh" exit 1 end +keybind(38) do |_event| + run "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 mv /tmp/screenshot_temp.png ~/screenshots/$(date +%Y-%m-%d_%H:%M:%S).png; fi" +end + +keybind(54) do |_event| + run "kitty -e fish -c \"y\"" +end + +keybind(53) do |_event| + run "kitty -e fish -c \"editor\"" +end + +keybind(40) do |_event| + run "~/dotfiles/scripts/run.sh" +end + +keybind(33) do |_event| + run "~/.config/polybar/launch.sh" +end + +keybind(56) do |_event| + monitor = current_monitor + create_workspace monitor + persistence_path = File.join(__dir__, ".num.json") + persistence = File.exist?(persistence_path) ? + JSON.parse(File.read(persistence_path), symbolize_names: true) : + {} + persistence[$monitors.key(monitor)] = monitor[:workspaces].length + File.write(persistence_path, JSON.pretty_generate(persistence)) +end + +keybind(57) do |_event| + monitor = current_monitor + delete_workspace monitor[:workspaces].length - 1, monitor + persistence_path = File.join(__dir__, ".num.json") + persistence = File.exist?(persistence_path) ? + JSON.parse(File.read(persistence_path), symbolize_names: true) : + {} + persistence[$monitors.key(monitor)] = monitor[:workspaces].length + File.write(persistence_path, JSON.pretty_generate(persistence)) +end + +keybind(112, 0) do |_event| + monitor = current_monitor + next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length + select_workspace next_ws, monitor +end + +keybind(112) do |_event| + monitor = current_monitor + pointer = X.get_pointer + + window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w| + pointer[:x] >= w.x && + pointer[:x] < w.x + w.width && + pointer[:y] >= w.y && + pointer[:y] < w.y + w.height + end + + next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length + monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window +end + +keybind(117, 0) do |_event| + monitor = current_monitor + next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length + select_workspace next_ws, monitor +end + +keybind(117) do |_event| + monitor = current_monitor + pointer = X.get_pointer + + window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w| + pointer[:x] >= w.x && + pointer[:x] < w.x + w.width && + pointer[:y] >= w.y && + pointer[:y] < w.y + w.height + end + + next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length + monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window +end + +keybind(27) do |_event| + monitor = current_monitor + return unless monitor + monitor[:workspaces][monitor[:selected_workspace]].switch_direction +end + keybind(55) do |_event| - pid = spawn "rofi -modi 'clipboard:greenclip print' -show clipboard -run-command '{cmd}'" - Process.detach pid + run "rofi -modi 'clipboard:greenclip print' -show clipboard -run-command '{cmd}'" +end + +keybind(39) do |event| + window = $windows[event[:window]] + window.toggle_floating if window end mousebind 1 mousebind 3 + +mousebind(8, 0) do |_event| + monitor = current_monitor + next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length + select_workspace next_ws, monitor +end + +mousebind 8 do |_event| + monitor = current_monitor + pointer = X.get_pointer + + window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w| + pointer[:x] >= w.x && + pointer[:x] < w.x + w.width && + pointer[:y] >= w.y && + pointer[:y] < w.y + w.height + end + + next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length + monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window +end + +mousebind(9, 0) do |_event| + monitor = current_monitor + next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length + select_workspace next_ws, monitor +end + +mousebind 9 do |_event| + monitor = current_monitor + pointer = X.get_pointer + + window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w| + pointer[:x] >= w.x && + pointer[:x] < w.x + w.width && + pointer[:y] >= w.y && + pointer[:y] < w.y + w.height + end + + next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length + monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window +end diff --git a/src/ruby/controller.rb b/src/ruby/controller.rb new file mode 100644 index 0000000..a1ef0a2 --- /dev/null +++ b/src/ruby/controller.rb @@ -0,0 +1,20 @@ +def create_workspace(monitor) + monitor[:workspaces] << Workspace.new($monitors.key(monitor)) + monitor[:selected_workspace] = monitor[:workspaces].length - 1 +end + +def delete_workspace(n, monitor) + return if monitor[:workspaces].length <= 1 + monitor[:workspaces][n].windows.each { |w| monitor[:workspaces][(n - 1) % monitor[:workspaces].length].drop 0, w } + monitor[:workspaces].delete_at n + if monitor[:selected_workspace] >= n + monitor[:selected_workspace] -= 1 + end +end + +def select_workspace(n, monitor) + monitor[:workspaces].each { |w| w.hide if w != monitor[:workspaces][n] } + monitor[:selected_workspace] = n + monitor[:workspaces][n].show + monitor[:workspaces][n].compute_tiled! +end diff --git a/src/ruby/events.rb b/src/ruby/events.rb index 65ec399..fad36a6 100644 --- a/src/ruby/events.rb +++ b/src/ruby/events.rb @@ -9,7 +9,7 @@ EVENT_TYPES = { 8 => :mouse_release, 9 => :key_press, 10 => :key_release, - 11 => :closed, + 11 => :closed_temp, 12 => :configure_request, 13 => :resize_request }.freeze @@ -26,33 +26,25 @@ def handle_event(event) when :closed $windows[event[:window]]&.delete - $workspaces[:main].tiled_root_block.compute_geometry! when :enter - X.send_to_top event[:window] - floating_windows = $windows.select { |_i, w| w.floating } - floating_windows.each { |_i, w| X.send_to_top w.window_id } - X.focus event[:window] - - - when :showed - return unless event[:override_redirect].zero? - X.send_to_top event[:window] - floating_windows = $windows.select { |_i, w| w.floating } - floating_windows.each { |_i, w| X.send_to_top w.window_id } X.focus event[:window] when :show_request - $workspaces[:main].add event[:window] if $windows[event[:window]].nil? - $workspaces[:main].tiled_root_block.compute_geometry! - X.show event[:window] + monitor = current_monitor + if $windows[event[:window]].nil? + monitor[:workspaces][monitor[:selected_workspace]].create event[:window] + X.show event[:window] + end when :mouse_press + $mousebind_actions[[event[:btn], event[:state]]]&.call(event) return if event[:is_root] != 0 return if $windows[event[:window]].nil? + return if event[:state] != 1 $mouse_data[:btn] = event[:btn] $mouse_data[:window] = $windows[event[:window]] X.grab_pointer $root @@ -63,7 +55,6 @@ def handle_event(event) else $mouse_data[:mode] = :tiled end - $mousebind_actions[event[:btn]]&.call(event) when :mouse_drag mouse_pos = X.get_pointer @@ -73,13 +64,14 @@ def handle_event(event) if $mouse_data[:btn] == 1 $mouse_data[:window].move $mouse_data[:geometry][:x] + dx, $mouse_data[:geometry][:y] + dy elsif $mouse_data[:btn] == 3 - X.resize_window $mouse_data[:window].window_id, - [$mouse_data[:geometry][:width] + dx, 50].max, - [$mouse_data[:geometry][:height] + dy, 50].max + $mouse_data[:window]&.resize [$mouse_data[:geometry][:width] + dx, 50].max, + [$mouse_data[:geometry][:height] + dy, 50].max, + true end elsif $mouse_data[:mode] == :tiled if $mouse_data[:btn] == 1 - find_targets = compute_drop_targets! $mouse_data[:window] + monitor = current_monitor + find_targets = monitor[:workspaces][monitor[:selected_workspace]].insertion_rects target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y]) if target if $rect[:target] != target @@ -98,17 +90,21 @@ def handle_event(event) when :mouse_release if $mouse_data[:mode] == :tiled - X.destroy $rect[:id] if $rect[:id] - $rect = {} + if $mouse_data[:btn] == 1 + if $rect[:id] + X.destroy $rect[:id] + monitor = current_monitor + monitor[:workspaces][monitor[:selected_workspace]].drop $rect[:target][:drop], $mouse_data[:window] + end + $rect = {} + end end X.ungrab_pointer - X.send_to_top $mouse_data[:window].window_id if $mouse_data[:window] - X.focus $mouse_data[:window].window_id if $mouse_data[:window] $mouse_data = {} when :key_press - $keybind_actions[event[:btn]]&.call(event) + $keybind_actions[[event[:btn], event[:state]]]&.call(event) when :key_release # TODO @@ -116,12 +112,8 @@ def handle_event(event) when :configure_request $windows[event[:window]]&.resize event[:width], event[:height] - X.send_to_top event[:window] - X.focus event[:window] when :resize_request $windows[event[:window]]&.resize event[:width], event[:height] - X.send_to_top event[:window] - X.focus event[:window] end end diff --git a/src/ruby/node.rb b/src/ruby/node.rb deleted file mode 100644 index 7c4b47c..0000000 --- a/src/ruby/node.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Node - attr_accessor :size - - def initialize - @size = 0 - end -end - - -# Require submodules - -require_relative "./window" -require_relative "./window_block" diff --git a/src/ruby/utils.rb b/src/ruby/utils.rb index 5bfa641..e3556cf 100644 --- a/src/ruby/utils.rb +++ b/src/ruby/utils.rb @@ -1,28 +1,49 @@ -def keybind(key, &block) - X.add_keybind key - $keybind_actions[key] = block if block +require 'json' + +def keybind(key, mod = 1, &block) + X.add_keybind key, mod + $keybind_actions[[key, mod]] = block if block end -def mousebind(btn, &block) - X.add_mousebind btn - $mousebind_actions[btn] = block if block +def mousebind(btn, mod = 1, &block) + X.add_mousebind btn, mod + $mousebind_actions[[btn, mod]] = block if block +end + + +def run(command) + pid = spawn command + Process.detach pid end -def refresh_monitors! +def load_monitors! $monitors.clear xrandr_output = `xrandr --query` connected = xrandr_output.each_line.select { |line| line.include?(" connected") } connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 } + persistence_path = File.join(__dir__, ".num.json") + persistence = File.exist?(persistence_path) ? + JSON.parse(File.read(persistence_path), symbolize_names: true) : + {} connected.first(2).each_with_index do |line, index| next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/ w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i key = index.zero? ? :primary : :secondary - $monitors[key] = { x: x, y: y, width: w, height: h } + $monitors[key] = { x: x, y: y, width: w, height: h, workspaces: Array.new(persistence[key] || 1) { Workspace.new(key) }, selected_workspace: 0 } end end +def current_monitor(pointer = X.get_pointer) + $monitors.find do |_, r| + pointer[:x] >= r[:x] && + pointer[:x] < r[:x] + r[:width] && + pointer[:y] >= r[:y] && + pointer[:y] < r[:y] + r[:height] + end&.last +end + def fixed_size_or_aspect?(hints) @@ -38,39 +59,6 @@ def fixed_size_or_aspect?(hints) same_size || fixed_aspect end - - -def compute_drop_targets!(window) - targets = [] - - $monitors.each_value do |monitor| - # if monitor[:windows].nil? || monitor[:windows].empty? - # targets << { type: :monitor_full, x: monitor[:x], y: monitor[:y], width: monitor[:width], height: monitor[:height], monitor: monitor } - # else - margin = 60 - targets << { type: :monitor_top, x: monitor[:x], y: monitor[:y], width: monitor[:width], height: margin, monitor: monitor } - targets << { type: :monitor_bottom, x: monitor[:x], y: monitor[:y] + monitor[:height] - margin, width: monitor[:width], height: margin, monitor: monitor } - targets << { type: :monitor_left, x: monitor[:x], y: monitor[:y], width: margin, height: monitor[:height], monitor: monitor } - targets << { type: :monitor_right, x: monitor[:x] + monitor[:width] - margin, y: monitor[:y], width: margin, height: monitor[:height], monitor: monitor } - # end - end - - $windows.each_value do |w| - next if w.floating - next if w == window - - margin_x = (w.width / 4).round - margin_y = (w.height / 4).round - - targets << { type: :window_top, x: w.x, y: w.y, width: w.width, height: margin_y, window: w } - targets << { type: :window_bottom, x: w.x, y: w.y + w.height - margin_y, width: w.width, height: margin_y, window: w } - targets << { type: :window_left, x: w.x, y: w.y, width: margin_x, height: w.height, window: w } - targets << { type: :window_right, x: w.x + w.width - margin_x, y: w.y, width: margin_x, height: w.height, window: w } - end - - targets -end - def drop_target(targets, cx, cy) targets.find do |t| cx >= t[:x] && diff --git a/src/ruby/window.rb b/src/ruby/window.rb index 9a8537e..abfcd7b 100644 --- a/src/ruby/window.rb +++ b/src/ruby/window.rb @@ -1,34 +1,47 @@ -class Window < Node - attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace +class Window + attr_accessor :window_id, :floating, :workspace, :def_floating, :size, :x, :y, :width, :height def initialize(window_id, workspace) + @size = 0 @workspace = workspace @window_id = window_id - @name = X.get_wm_name(window_id) - @wm_n_hints = X.get_wm_n_hints(window_id) + wm_n_hints = X.get_wm_n_hints(window_id) @floating = false - if @wm_n_hints - if fixed_size_or_aspect?(@wm_n_hints) + if wm_n_hints + if fixed_size_or_aspect?(wm_n_hints) @floating = true - @width = @wm_n_hints[:max_width] - if @wm_n_hints[:flags] & 128 != 0 - @height = @wm_n_hints[:max_width] * (@wm_n_hints[:min_aspect_num] / @wm_n_hints[:min_aspect_den]) + @def_floating = true + @width = wm_n_hints[:max_width] + if wm_n_hints[:flags] & 128 != 0 + @height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den]) else - @height = @wm_n_hints[:max_height] + @height = wm_n_hints[:max_height] end - @x = @workspace.width / 2 - @width / 2 - @y = @workspace.height / 2 - @height / 2 + @x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2 + @y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2 apply_geometry! end end - @wm_hints = X.get_wm_hints(window_id) - if @wm_hints - if @wm_hints[:flags] & 1 != 0 && @wm_hints[:initial_state] == 3 + wm_hints = X.get_wm_hints(window_id) + if wm_hints + if wm_hints[:flags] & 1 != 0 && wm_hints[:initial_state] == 3 X.set_wm_state window_id, 1 end end - @transient_for = X.get_wm_transient_for(window_id) - @floating = true unless @transient_for.zero? + transient_for = X.get_wm_transient_for(window_id) + unless transient_for.zero? + @floating = true + @def_floating = true + @width = wm_n_hints[:max_width] + if wm_n_hints[:flags] & 128 != 0 + @height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den]) + else + @height = wm_n_hints[:max_height] + end + @x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2 + @y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2 + apply_geometry! + end super() end @@ -40,21 +53,32 @@ class Window < Node block.call(self) end + def toggle_floating + if !@def_floating + @floating = !@floating + @workspace.check_floating self + apply_geometry! + end + end + def move(x, y) return unless @floating @x, @y = x, y apply_geometry! end - def resize(width, height) + def resize(width, height, force = false) return unless @floating - @x = @workspace.width / 2 - width / 2 - @y = @workspace.height / 2 - height / 2 @width, @height = width, height + if !force + @x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2 + @y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2 + end apply_geometry! end def apply_geometry! + X.send_to_top @window_id if @floating X.move_window @window_id, @x, @y X.resize_window @window_id, @width, @height end diff --git a/src/ruby/window_block.rb b/src/ruby/window_block.rb deleted file mode 100644 index a78aa33..0000000 --- a/src/ruby/window_block.rb +++ /dev/null @@ -1,64 +0,0 @@ -class WindowBlock < Node - attr_accessor :children, :direction, :size, :x, :y, :width, :height - - def initialize(direction = :horizontal) - @children = [] - @direction = direction - super() - end - - def add_node(node) - @children << node - end - - def each_leaf(&block) - @children.each { |child| child.each_leaf(&block) } - end - - def each_node(&block) - block.call(self) - @children.each { |child| child.each_node(&block) if child.is_a? WindowBlock } - end - - def compute_geometry! - return if @children.empty? - - horizontal = @direction == :horizontal - total_percent = @children.map { |c| c.size.to_i > 0 ? c.size.to_i : 0 }.sum - flex_count = @children.count { |c| c.size.to_i <= 0 } - total_space = horizontal ? @width : @height - total_percent = [total_percent, 100].min - remaining_percent = 100 - total_percent - flex_percent = flex_count > 0 ? remaining_percent / flex_count : 0 - pos = horizontal ? @x : @y - - @children.each do |child| - percent = child.size.to_i > 0 ? child.size.to_i : flex_percent - child_size = (total_space * percent) / 100.0 - if horizontal - child.x = pos - child.y = @y - child.width = child_size - child.height = @height - else - child.x = @x - child.y = pos - child.width = @width - child.height = child_size - end - child.compute_geometry! if child.is_a?(WindowBlock) - child.apply_geometry! if child.is_a?(Window) - pos += child_size - end - end -end - -class RootWindowBlock < WindowBlock - def initialize(workspace) - super(:horizontal) - @x = workspace.x - @y = workspace.y - @width = workspace.width - @height = workspace.height - end -end diff --git a/src/ruby/workspace.rb b/src/ruby/workspace.rb index 4d1a110..88a3712 100644 --- a/src/ruby/workspace.rb +++ b/src/ruby/workspace.rb @@ -1,45 +1,146 @@ class Workspace - attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block + attr_reader :monitor_id + attr_accessor :tiled_windows, :untiled_windows - def initialize(name, monitor = :primary) - @monitor = monitor - @x = $monitors[monitor][:x] - @y = $monitors[monitor][:y] - @width = $monitors[monitor][:width] - @height = $monitors[monitor][:height] - @tiled_root_block = RootWindowBlock.new(self) + def initialize(monitor_id = :primary, direction = :horizontal) + @monitor_id = monitor_id + @direction = direction + @tiled_windows = [] @untiled_windows = [] - @name = name + end + + def drop(idx_dst, window) + idx_src = window.workspace.tiled_windows.index(window) + window.workspace.tiled_windows.delete_at idx_src + self.tiled_windows.insert idx_dst, window + window.workspace.compute_tiled! if window.workspace != self + window.workspace = self + compute_tiled! + select_workspace $monitors[@monitor_id][:selected_workspace], $monitors[@monitor_id] + end + + def insertion_rects + rects = [] + return rects if @tiled_windows.empty? + compute_tiled! + + horizontal = (@direction == :horizontal) + z = 100 + half = z / 2 + + wins = @tiled_windows + + first = wins.first + if horizontal + rects << { x: first.x, y: first.y, width: z, height: first.height, drop: 0 } + else + rects << { x: first.x, y: first.y, width: first.width, height: z, drop: 0 } + end + + wins.each_cons(2).with_index do |(a, b), i| + if horizontal + mid = (a.x + a.width + b.x) / 2.0 + rects << { + x: mid - half, + y: a.y, + width: z, + height: a.height, + drop: i + } + else + mid = (a.y + a.height + b.y) / 2.0 + rects << { + x: a.x, + y: mid - half, + width: a.width, + height: z, + drop: i + } + end + end + + last = wins.last + if horizontal + rects << { x: last.x + last.width - z, y: last.y, width: z, height: last.height, drop: wins.count - 1 } + else + rects << { x: last.x, y: last.y + last.height - z, width: z, height: last.width, drop: wins.count - 1 } + end + + rects end def windows - windows = [] - @untiled_windows.each { |w| windows << w } - @tiled_root_block.each_leaf { |w| windows << w } - windows + @tiled_windows + @untiled_windows end - def add(window_id) + def check_floating(window) + if window.floating + @untiled_windows << window + @tiled_windows.delete window + else + @tiled_windows << window + @untiled_windows.delete window + end + compute_tiled! + end + + def switch_direction + @direction = @direction == :horizontal ? :vertical : :horizontal + windows.each { |window| window.size = 0 } + compute_tiled! + end + + def create(window_id) window = Window.new(window_id, self) + $windows[window_id] = window if window.floating @untiled_windows << window else - @tiled_root_block.add_node window + @tiled_windows << window + compute_tiled! end - $windows[window_id] = window end def remove(window) @untiled_windows.delete window - @tiled_root_block.each_node { |node| node.children.delete window } + @tiled_windows.delete window $windows.delete window.window_id + compute_tiled! + end + + def compute_tiled! + return if @tiled_windows.empty? + + monitor = $monitors[@monitor_id] + horizontal = @direction == :horizontal + abs_total = @tiled_windows.sum { |c| c.size.to_i } + flex_count = @tiled_windows.count { |c| c.size.to_i <= 0 } + total_space = horizontal ? monitor[:width] : monitor[:height] + remaining = total_space - abs_total + remaining = 0 if remaining < 0 + flex_each = flex_count > 0 ? (remaining.to_f / flex_count) : 0 + pos = horizontal ? monitor[:x] : monitor[:y] + + @tiled_windows.each do |window| + px = window.size.to_i > 0 ? window.size.to_i : flex_each + if horizontal + window.x = pos + window.y = monitor[:y] + window.width = px + window.height = monitor[:height] + else + window.x = monitor[:x] + window.y = pos + window.width = monitor[:width] + window.height = px + end + window.apply_geometry! + pos += px + end end def close_all - self.windows.each do |window| - X.kill window.window_id - remove window - end + self.windows.each { |window| remove window } end def hide diff --git a/src/shell/startup.sh b/src/shell/startup.sh index 0cab5a4..7522811 100755 --- a/src/shell/startup.sh +++ b/src/shell/startup.sh @@ -1,3 +1,17 @@ #!/usr/bin/env bash -picom --config ~/.config/i3/picom.conf +picom --config ~/.config/i3/picom.conf & +greenclip daemon & +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 +xsetroot -cursor_name left_ptr + +xrdb ~/.Xresources +setxkbmap us + +~/.config/polybar/launch.sh special &>>"$LOGFILE" &