diff --git a/X-kutu.c b/X-kutu.c index 5b71a5b..79a2724 100644 --- a/X-kutu.c +++ b/X-kutu.c @@ -14,8 +14,7 @@ #include #include -// XCB headers -#include +// XCB header #include // Global variables @@ -97,13 +96,6 @@ int deploy(void) { // Free a geometry structure void free_geometry(Geometry *g) { free(g); } -// Get monitor geometries using XRandR, returns an array of Geometry structures -// The caller is responsible for freeing the returned array -Geometry *xrandr_get_monitors(void) { - // TODO: Loop through monitors and return their geometries - // return all; -} - // Set input focus to a window void focus(xcb_window_t win) { xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..725ab59 --- /dev/null +++ b/compile.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +DIR="$(cd -- "$(dirname -- "$0")" && pwd)" + +if ! XCB=$(pkg-config --cflags --libs xcb 2>/dev/null); then + echo "Error: lib-xcb not found. Please install lib-xcb." >&2 + exit 1 +fi + +if ! xrandr --version >/dev/null; then + echo "Error: xrandr not found. Please install xrandr." >&2 + exit 1 +fi + +gcc -shared -fPIC -Wall -Wextra -o "$DIR/X-kutu.so" "$DIR/X-kutu.c" $XCB + +if [ ! -f "$DIR/X-kutu.so" ]; then + echo "Error: compilation failed." >&2 + exit 1 +else + echo "Success: $DIR/X-kutu.so is compiled." +fi diff --git a/kutu.rb b/kutu.rb index a0710c4..88b31f5 100755 --- a/kutu.rb +++ b/kutu.rb @@ -4,10 +4,9 @@ require "ffi" module X extend FFI::Library - ffi_lib "/home/syed/main/kutu/X-kutu.so" + ffi_lib File.join(__dir__, "X-kutu.so") typedef :uint32, :window_id - typedef :pointer, :goemetry_array class Geometry < FFI::Struct layout :x, :int16, @@ -40,8 +39,7 @@ module X attach_function :show, [:window_id], :void attach_function :hide, [:window_id], :void attach_function :send_to_top, [:window_id], :void - attach_function :xrandr_get_monitors, [], :goemetry_array - attach_function :free_geometry, [:goemetry_array], :void + attach_function :free_geometry, [:pointer], :void attach_function :get_geometry, [:window_id], Geometry.by_value attach_function :get_pointer, [], Geometry.by_value attach_function :get_screen, [], Geometry.by_value @@ -49,20 +47,6 @@ module X attach_function :move_window, [:window_id, :int, :int], :void attach_function :resize_window, [:window_id, :int, :int], :void attach_function :wait_for_event, [], Event.by_value - - def self.get_monitors - ptr = xrandr_get_monitors - return [] if ptr.null? - monitors = [] - 2.times do |i| - geom_ptr = ptr + i * Geometry.size - geom = Geometry.new(geom_ptr) - break if geom[:width] == 0 || geom[:height] == 0 - monitors << geom - end - free_geometry(ptr) - monitors - end end if X.deploy < 0 @@ -71,7 +55,17 @@ end at_exit { X.cleanup } -$monitors = X.get_monitors +$monitors = {} + +xrandr_output = `xrandr --query` +connected = xrandr_output.each_line.select { |line| line.include?(" connected") } +connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 } +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 } +end $workspaces = {} $windows = {} @@ -98,24 +92,30 @@ class Node end class Window < Node - attr_accessor :window_id + attr_accessor :window_id, :x, :y, :width, :height def initialize(window_id) @window_id = window_id - super + super() end def each_leaf(&block) block.call(self) end + + def apply_geometry + X.move_window @window_id, @x, @y + X.resize_window @window_id, @width, @height + end end class WindowBlock < Node - attr_accessor :children, :direction, :size + attr_accessor :children, :direction, :size, :x, :y, :width, :height - def initialize() + def initialize(direction = :horizontal) @children = [] - super + @direction = direction + super() end def add_node(node) @@ -123,33 +123,68 @@ class WindowBlock < Node end def each_leaf(&block) - node.children.each do |child| - child.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_fixed = @children.map { |c| c.size || 0 }.sum + flex_count = @children.count { |c| c.size.to_i <= 0 } + total_space = horizontal ? @width : @height + remaining_space = total_space - total_fixed + remaining_space = 0 if remaining_space < 0 + flex_size = flex_count > 0 ? remaining_space / flex_count : 0 + pos = horizontal ? @x : @y + + @children.each do |child| + child_size = child.size.to_i > 0 ? child.size.to_i : flex_size + 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 - attr_accessor :x, :y, :width, :height - def initialize(workspace) - super + super(:horizontal) @x = workspace.x @y = workspace.y @width = workspace.width @height = workspace.height - @direction = :vertical end end class Workspace - attr_reader :name, :monitor, :x, :y, :width, :height + attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block - def initialize(name, monitor = 0) + 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) @untiled_windows = [] @name = name - @monitor = monitor end def windows @@ -160,32 +195,29 @@ class Workspace end def add(window_id) - @windows[window_id] = Window.new(window_id) + @tiled_root_block.add_node Window.new(window_id) $windows[window_id] = self end def remove(window_id) - @windows.delete window_id + @untiled_windows.delete window_id + @tiled_root_block.each_node { |node| node.children.delete window_id } $windows.delete window_id end def close_all - @windows.each_key do |window| - X.kill window - remove window + self.windows.each do |window| + X.kill window.window_id + remove window.window_id end end def hide - @windows.each_key do |window| - X.hide window - end + self.windows.each { |window| X.hide window.window_id } end def show - @windows.each_key do |window| - X.show window - end + self.windows.each { |window| X.show window.window_id } end end @@ -219,8 +251,12 @@ keybind(26) do |_event| exit 1 end -mousebind(1) -mousebind(3) +keybind(27) do |_event| + pp $workspaces[:main].windows +end + +mousebind 1 +mousebind 3 $mouse_state = -1 $mouse_window = -1 @@ -232,17 +268,18 @@ loop do if event[:override_redirect].zero? X.subscribe event[:window] X.focus event[:window] - $workspaces[:main].add event[:window] end when :close X.kill event[:window] - $windows[event[:window]].remove event[:window] + $windows[event[:window]]&.remove event[:window] when :enter X.focus event[:window] X.send_to_top event[:window] when :show X.show event[:window] X.focus event[:window] + $workspaces[:main].add event[:window] if $windows[event[:window]].nil? + $workspaces[:main].tiled_root_block.compute_geometry! when :mouse_press next if event[:is_root] != 0 X.send_to_top event[:window] @@ -252,7 +289,7 @@ loop do $geom_start = X.get_geometry event[:window] $mousebind_actions[event[:btn]]&.call(event) when :mouse_drag - screen_bounds = X.get_screen # TODO: use monitor + screen_bounds = $monitors[:primary] mouse_pos = X.get_pointer dx = mouse_pos[:x] - $mouse_pos_start[:x] dy = mouse_pos[:y] - $mouse_pos_start[:y]