#!/usr/bin/env ruby require "ffi" module X extend FFI::Library ffi_lib "/home/syed/main/kutu/X-kutu.so" typedef :uint32, :window_id typedef :pointer, :goemetry_array 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 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 :xrandr_get_monitors, [], :goemetry_array attach_function :free_geometry, [:goemetry_array], :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 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 raise "Failed to deploy X" exit 1 end at_exit { X.cleanup } $monitors = X.get_monitors $monitors.each do |monitor| puts "Monitor: #{monitor[:x]} #{monitor[:y]} #{monitor[:width]} #{monitor[:height]}" end $workspaces = {} $windows = {} class Window attr_reader :window_id def initialize(window_id) @window_id = window_id end end class Workspace attr_reader :windows, :name, :screen def initialize(name) @windows = {} @name = name @screen = :main end def add(window_id) @windows[window_id] = Window.new(window_id) $windows[window_id] = self end def remove(window_id) @windows.delete window_id $windows.delete window_id end def close_all @windows.each_key do |window| X.kill window self.remove window end end def hide @windows.each_key do |window| X.hide window end end def show @windows.each_key do |window| X.show window end end end EVENT_TYPES = { 0 => :unused, 1 => :create, 2 => :close, 3 => :enter, 4 => :show, 5 => :mouse_press, 6 => :mouse_drag, 7 => :mouse_release, 8 => :key_press, 9 => :key_release, 10 => :configured }.freeze $workspaces[:main] = Workspace.new(:main) X.add_keybind 24 X.add_keybind 25 X.add_keybind 26 X.add_mousebind 1 X.add_mousebind 3 $mouse_state = -1 $mouse_window = -1 loop do event = X.wait_for_event case EVENT_TYPES[event[:type]] when :create if event[:override_redirect] == 0 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] when :enter X.focus event[:window] X.send_to_top event[:window] when :show X.show event[:window] X.focus event[:window] when :mouse_press next if event[:is_root] != 0 X.send_to_top event[:window] $mouse_state = event[:btn] $mouse_window = event[:window] $mouse_pos_start = X.get_pointer $geom_start = X.get_geometry event[:window] when :mouse_drag screen_bounds = $monitors[0] 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 X.move_window $mouse_window, new_x, new_y elsif $mouse_state == 3 X.resize_window $mouse_window, [$geom_start[:width] + dx, 50].max, [$geom_start[:height] + dy, 50].max end when :mouse_release if [1, 3].include?($mouse_state) X.focus $mouse_window X.send_to_top $mouse_window $mouse_state = -1 $mouse_window = -1 $mouse_pos_start = { x: -1, y: -1 } $geom_start = nil end when :key_press case event[:btn] when 24 X.kill event[:window] when 25 pid = spawn("kitty") Process.detach pid when 26 $workspaces.each_value do |workspace| workspace.close_all end exit 1 end when :key_release # TODO when :configured # TODO end end