diff --git a/X-kutu.c b/X-kutu.c index 8939bda..b518e73 100644 --- a/X-kutu.c +++ b/X-kutu.c @@ -239,6 +239,61 @@ void set_wm_state(xcb_window_t win, int state) { xcb_flush(conn); } +xcb_window_t draw_rectangle(int x, int y, int width, int height, + uint32_t color) { + // Get the screen + const xcb_setup_t *setup = xcb_get_setup(conn); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + xcb_screen_t *screen = iter.data; + + // Create an override-redirect window + xcb_window_t win = xcb_generate_id(conn); + uint32_t mask = + XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + uint32_t values[3]; + values[0] = screen->black_pixel; // initial background pixel + values[1] = 1; // override_redirect = true + values[2] = XCB_EVENT_MASK_EXPOSURE; // we want exposure events + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, // depth + win, // window ID + screen->root, // parent + x, y, // x, y + width, height, // width, height + 0, // border width + XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, + values); + + // Map the window (make it visible) + xcb_map_window(conn, win); + + // Fill it with the solid color using a Graphics Context + xcb_gcontext_t gc = xcb_generate_id(conn); + uint32_t gc_values[] = {color, XCB_LINE_STYLE_SOLID}; + xcb_create_gc(conn, gc, win, XCB_GC_FOREGROUND | XCB_GC_LINE_STYLE, + gc_values); + + xcb_rectangle_t rect = {0, 0, width, height}; // relative to window + xcb_poly_fill_rectangle(conn, win, gc, 1, &rect); + + // Flush the connection to send commands to the server + xcb_flush(conn); + + return win; +} + +void grab_pointer(xcb_window_t win) { + xcb_grab_pointer(conn, 0, win, + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_BUTTON_MOTION | + XCB_EVENT_MASK_POINTER_MOTION_HINT, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, win, XCB_NONE, + XCB_CURRENT_TIME); +} + +void ungrab_pointer(void) { xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); } + // Wait for an event and return it as an Event structure // This function is blocking // The event is sent by value, so no need to free anything @@ -294,13 +349,6 @@ Event wait_for_event(void) { xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev; if (!e->child) break; - // Grab pointer for dragging - xcb_grab_pointer(conn, 0, e->child, - XCB_EVENT_MASK_BUTTON_RELEASE | - XCB_EVENT_MASK_BUTTON_MOTION | - XCB_EVENT_MASK_POINTER_MOTION_HINT, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, e->child, - XCB_NONE, XCB_CURRENT_TIME); ret.type = 6; ret.window = e->child; ret.is_root = e->child == scr->root; @@ -312,7 +360,6 @@ Event wait_for_event(void) { case XCB_MOTION_NOTIFY: { xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *)ev; ret.type = 7; - ret.window = e->child; ret.x = e->event_x; ret.y = e->event_y; ret.state = e->state; @@ -320,8 +367,6 @@ Event wait_for_event(void) { case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *e = (xcb_button_release_event_t *)ev; - // Ungrab pointer after dragging - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); ret.type = 8; ret.x = e->event_x; ret.y = e->event_y; diff --git a/compile.sh b/compile.sh index 0b8a73b..41db450 100755 --- a/compile.sh +++ b/compile.sh @@ -13,11 +13,11 @@ if ! xrandr --version >/dev/null 2>&1; then exit 1 fi -gcc -shared -fPIC -Wall -Wextra -o "$DIR/X-kutu.so" "$DIR/X-kutu.c" $XCB +gcc -shared -fPIC -Wall -Wextra -o "$DIR/lib/X-kutu.so" "$DIR/X-kutu.c" $XCB -if [ ! -f "$DIR/X-kutu.so" ]; then +if [ ! -f "$DIR/lib/X-kutu.so" ]; then echo "Error: compilation failed." >&2 exit 1 else - echo "Success: $DIR/X-kutu.so is compiled." + echo "Success: $DIR/lib/X-kutu.so is compiled." fi diff --git a/kutu.rb b/kutu.rb index 80e753b..5e17c16 100755 --- a/kutu.rb +++ b/kutu.rb @@ -1,93 +1,6 @@ #!/usr/bin/env ruby -require "ffi" - -module X - extend FFI::Library - ffi_lib File.join(__dir__, "X-kutu.so") - - typedef :uint32, :window_id - typedef :uint32, :xcb_window_t - typedef :uint32, :xcb_pixmap_t - - class Geometry < FFI::Struct - layout :x, :int16, - :y, :int16, - :width, :uint16, - :height, :uint16 - end - - class Event < FFI::Struct - layout :type, :int32, - :window, :window_id, - :override_redirect, :int8, - :btn, :uint32, - :x, :int32, - :y, :int32, - :height, :uint32, - :width, :uint32, - :state, :uint32, - :is_root, :int8 - end - - class SizeHints < FFI::Struct - layout :flags, :uint32, - :x, :int32, - :y, :int32, - :width, :int32, - :height, :int32, - :min_width, :int32, - :min_height, :int32, - :max_width, :int32, - :max_height, :int32, - :width_inc, :int32, - :height_inc, :int32, - :min_aspect_num, :int32, - :min_aspect_den, :int32, - :max_aspect_num, :int32, - :max_aspect_den, :int32, - :base_width, :int32, - :base_height, :int32, - :win_gravity, :uint32 - end - - class WMHints < FFI::Struct - layout :flags, :int32, - :input, :uint32, - :initial_state,:int32, - :icon_pixmap, :xcb_pixmap_t, - :icon_window, :xcb_window_t, - :icon_x, :int32, - :icon_y, :int32, - :icon_mask, :xcb_pixmap_t, - :window_group, :xcb_window_t - end - - attach_function :deploy, [], :int - attach_function :add_keybind, [:int], :void - attach_function :add_mousebind, [:int], :void - attach_function :cleanup, [], :void - attach_function :focus, [:window_id], :void - attach_function :get_focus, [], :window_id - attach_function :subscribe, [:window_id], :void - attach_function :kill, [:window_id], :void - attach_function :show, [:window_id], :void - attach_function :hide, [:window_id], :void - attach_function :send_to_top, [:window_id], :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 - attach_function :warp_pointer, [:window_id, :int, :int], :void - 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 - attach_function :get_wm_name, [:window_id], :string - attach_function :get_wm_n_hints, [:window_id], SizeHints.by_value - attach_function :get_wm_hints, [:window_id], WMHints.by_value - attach_function :get_wm_transient_for, [:window_id], :uint8 - attach_function :set_wm_state, [:window_id, :int], :void -end +require_relative "./lib/X-kutu" if X.deploy < 0 raise "Failed to deploy X" @@ -96,8 +9,15 @@ end at_exit { X.cleanup } $monitors = {} +$workspaces = {} +$windows = {} -def refresh_monitors +$keybind_actions = {} +$mousebind_actions = {} + +$mouse_data = {} + +def refresh_monitors! $monitors.clear xrandr_output = `xrandr --query` connected = xrandr_output.each_line.select { |line| line.include?(" connected") } @@ -123,13 +43,7 @@ def fixed_size_or_aspect?(hints) same_size || fixed_aspect end -refresh_monitors - -$workspaces = {} -$windows = {} - -$keybind_actions = {} -$mousebind_actions = {} +refresh_monitors! def keybind(key, &block) X.add_keybind key @@ -329,7 +243,6 @@ class Workspace end EVENT_TYPES = { - 0 => :unused, 1 => :create, 2 => :closed, 3 => :enter, @@ -368,8 +281,37 @@ end mousebind 1 mousebind 3 -$mouse_state = -1 -$mouse_window = -1 +def compute_drop_targets! + targets = [] + + $monitors.each_value do |monitor| + margin = 40 + 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 + + $windows.each do |w| + # next if w.hidden? + margin = 60 + targets << { type: :window_top, x: w.x, y: w.y, width: w.width, height: margin, window: w } + targets << { type: :window_bottom, x: w.x, y: w.y + w.height - margin, width: w.width, height: margin, window: w } + targets << { type: :window_left, x: w.x, y: w.y, width: margin, height: w.height, window: w } + targets << { type: :window_right, x: w.x + w.width - margin, y: w.y, width: margin, height: w.height, window: w } + end + + targets +end + +def drop_target(targets, cx, cy) + targets.find do |t| + cx >= t[:x] && + cy >= t[:y] && + cx < t[:x] + t[:width] && + cy < t[:y] + t[:height] + end +end loop do event = X.wait_for_event @@ -377,7 +319,6 @@ loop do when :create next unless event[:override_redirect].zero? X.subscribe event[:window] - X.focus event[:window] when :closed pp "Deleting window #{event[:window]}" $windows[event[:window]]&.delete @@ -385,52 +326,65 @@ loop do when :enter X.focus event[:window] 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 } when :showed next unless event[:override_redirect].zero? X.focus event[:window] 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 } when :show_request X.show 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] X.focus event[:window] - $mouse_state = event[:btn] - $mouse_window = $windows[event[:window]] - if $mouse_window.nil? - $mouse_state = -1 - $mouse_window = -1 - next + $mouse_data[:btn] = event[:btn] + $mouse_data[:window] = $windows[event[:window]] + X.grab_pointer event[:window] + if $mouse_data[:window].floating + $mouse_data[:mode] = :floating + $mouse_data[:pointer] = X.get_pointer + $mouse_data[:geometry] = X.get_geometry event[:window] + else + $mouse_data[:mode] = :tiled end - $mouse_pos_start = X.get_pointer - $geom_start = X.get_geometry event[:window] $mousebind_actions[event[:btn]]&.call(event) when :mouse_drag - 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] - if $mouse_state == 1 - new_x = [[$geom_start[:x] + dx, screen_bounds[:x]].max, - screen_bounds[:x] + screen_bounds[:width] - $geom_start[:width]].min - new_y = [[$geom_start[:y] + dy, screen_bounds[:y]].max, - screen_bounds[:y] + screen_bounds[:height] - $geom_start[:height]].min - $mouse_window.move new_x, new_y - elsif $mouse_state == 3 - $mouse_window.resize [$geom_start[:width] + dx, 50].max, - [$geom_start[:height] + dy, 50].max + if $mouse_data[:mode] == :floating + dx = mouse_pos[:x] - $mouse_data[:pointer][:x] + dy = mouse_pos[:y] - $mouse_data[:pointer][:y] + 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 + end + elsif $mouse_data[:mode] == :tiled + if $mouse_data[:btn] == 1 + find_targets = compute_drop_targets! + target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y]) + X.draw_rectangle target[:x], target[:y], target[:width], target[:height], 0x00ff00 + elsif $mouse_data[:btn] == 3 + # TODO: tile resize . dynamic + end end when :mouse_release - if [1, 3].include?($mouse_state) - X.focus $mouse_window.window_id - X.send_to_top $mouse_window.window_id - $mouse_state = -1 - $mouse_window = -1 - $mouse_pos_start = { x: -1, y: -1 } - $geom_start = nil + if $mouse_data[:mode] == :floating + if [1, 3].include?($mouse_data[:btn]) + X.ungrab_pointer + X.focus $mouse_data[:window].window_id + X.send_to_top $mouse_data[:window].window_id + end + elsif $mouse_data[:mode] == :tiled + # TODO end + X.focus $mouse_data[:window].window_id if $mouse_data[:window] + $mouse_data = {} when :key_press $keybind_actions[event[:btn]]&.call(event) when :key_release diff --git a/lib/X-kutu.rb b/lib/X-kutu.rb new file mode 100644 index 0000000..3418456 --- /dev/null +++ b/lib/X-kutu.rb @@ -0,0 +1,90 @@ +require "ffi" + +module X + extend FFI::Library + ffi_lib File.join(__dir__, "X-kutu.so") + + typedef :uint32, :xcb_window_t + typedef :uint32, :xcb_pixmap_t + + class Geometry < FFI::Struct + layout :x, :int16, + :y, :int16, + :width, :uint16, + :height, :uint16 + end + + class Event < FFI::Struct + layout :type, :int32, + :window, :xcb_window_t, + :override_redirect, :int8, + :btn, :uint32, + :x, :int32, + :y, :int32, + :height, :uint32, + :width, :uint32, + :state, :uint32, + :is_root, :int8 + end + + class SizeHints < FFI::Struct + layout :flags, :uint32, + :x, :int32, + :y, :int32, + :width, :int32, + :height, :int32, + :min_width, :int32, + :min_height, :int32, + :max_width, :int32, + :max_height, :int32, + :width_inc, :int32, + :height_inc, :int32, + :min_aspect_num, :int32, + :min_aspect_den, :int32, + :max_aspect_num, :int32, + :max_aspect_den, :int32, + :base_width, :int32, + :base_height, :int32, + :win_gravity, :uint32 + end + + class WMHints < FFI::Struct + layout :flags, :int32, + :input, :uint32, + :initial_state,:int32, + :icon_pixmap, :xcb_pixmap_t, + :icon_window, :xcb_window_t, + :icon_x, :int32, + :icon_y, :int32, + :icon_mask, :xcb_pixmap_t, + :window_group, :xcb_window_t + end + + attach_function :deploy, [], :int + attach_function :add_keybind, [:int], :void + attach_function :add_mousebind, [:int], :void + attach_function :cleanup, [], :void + attach_function :focus, [:xcb_window_t], :void + attach_function :get_focus, [], :xcb_window_t + attach_function :subscribe, [:xcb_window_t], :void + attach_function :kill, [:xcb_window_t], :void + attach_function :show, [:xcb_window_t], :void + attach_function :hide, [:xcb_window_t], :void + 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_screen, [], Geometry.by_value + attach_function :warp_pointer, [:xcb_window_t, :int, :int], :void + attach_function :move_window, [:xcb_window_t, :int, :int], :void + attach_function :resize_window, [:xcb_window_t, :int, :int], :void + attach_function :wait_for_event, [], Event.by_value + attach_function :get_wm_name, [:xcb_window_t], :string + attach_function :get_wm_n_hints, [:xcb_window_t], SizeHints.by_value + attach_function :get_wm_hints, [:xcb_window_t], WMHints.by_value + attach_function :get_wm_transient_for, [:xcb_window_t], :uint8 + attach_function :set_wm_state, [:xcb_window_t, :int], :void + attach_function :draw_rectangle, [:int, :int, :int, :int, :uint32], :xcb_window_t + attach_function :grab_pointer, [:xcb_window_t], :void + attach_function :ungrab_pointer, [], :void +end