commit f08113b53232a288c617323a2573f4fc0098be33 Author: Syed Daanish Date: Mon Sep 29 07:17:37 2025 +0100 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..faadf6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.so +*.vim diff --git a/X-kutu.c b/X-kutu.c new file mode 100644 index 0000000..f7bb02e --- /dev/null +++ b/X-kutu.c @@ -0,0 +1,303 @@ +#define CLEANMASK(m) ((m & ~0x80)) + +#define SUPER XCB_MOD_MASK_4 +#define ALT XCB_MOD_MASK_1 +#define CTRL XCB_MOD_MASK_CONTROL +#define SHIFT XCB_MOD_MASK_SHIFT +#define MOD SUPER + +#include +#include +#include +#include +#include + +xcb_connection_t *conn; +xcb_screen_t *scr; +xcb_window_t focuswin; + +typedef struct Geometry { + int16_t x; + int16_t y; + uint16_t width; + uint16_t height; +} Geometry; + +void cleanup(void) { + if (conn != NULL) + xcb_disconnect(conn); +} + +void add_keybind(int key) { + xcb_grab_key(conn, 0, scr->root, MOD, key, XCB_GRAB_MODE_ASYNC, + XCB_GRAB_MODE_ASYNC); +} + +void add_mousebind(int button) { + 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); +} + +int deploy(void) { + uint32_t values[2]; + int mask; + + if (xcb_connection_has_error(conn = xcb_connect(NULL, NULL))) + return -1; + + scr = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + focuswin = scr->root; + + mask = XCB_CW_EVENT_MASK; + values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; + xcb_change_window_attributes_checked(conn, scr->root, mask, values); + + xcb_flush(conn); + + return 0; +} + +void free_geometry(Geometry *g) { free(g); } + +Geometry *xrandr_get_monitors(void) { + xcb_randr_get_screen_resources_current_cookie_t res_cookie = + xcb_randr_get_screen_resources_current(conn, scr->root); + xcb_randr_get_screen_resources_current_reply_t *res = + xcb_randr_get_screen_resources_current_reply(conn, res_cookie, NULL); + if (!res) + return NULL; + int num_outputs = xcb_randr_get_screen_resources_current_outputs_length(res); + xcb_randr_output_t *outputs = + xcb_randr_get_screen_resources_current_outputs(res); + Geometry *all = calloc(2, sizeof(Geometry)); + int count = 0; + for (int i = 0; i < num_outputs && count < 2; i++) { + xcb_randr_get_output_info_cookie_t ocookie = + xcb_randr_get_output_info(conn, outputs[i], XCB_CURRENT_TIME); + xcb_randr_get_output_info_reply_t *oinfo = + xcb_randr_get_output_info_reply(conn, ocookie, NULL); + if (!oinfo) + continue; + if (oinfo->connection == XCB_RANDR_CONNECTION_CONNECTED && + oinfo->crtc != XCB_NONE) { + xcb_randr_get_crtc_info_cookie_t ccookie = + xcb_randr_get_crtc_info(conn, oinfo->crtc, XCB_CURRENT_TIME); + xcb_randr_get_crtc_info_reply_t *cinfo = + xcb_randr_get_crtc_info_reply(conn, ccookie, NULL); + if (cinfo) { + all[count].x = cinfo->x; + all[count].y = cinfo->y; + all[count].width = cinfo->width; + all[count].height = cinfo->height; + free(cinfo); + count++; + } + } + free(oinfo); + } + free(res); + return all; +} + +void focus(xcb_window_t win) { + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, + XCB_CURRENT_TIME); + if (win != focuswin) + focuswin = win; +} + +xcb_window_t get_focus(void) { return focuswin; } + +void subscribe(xcb_window_t win) { + uint32_t values[2]; + + values[0] = XCB_EVENT_MASK_ENTER_WINDOW; + values[1] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; + xcb_change_window_attributes(conn, win, XCB_CW_EVENT_MASK, values); +} + +void kill(xcb_window_t window) { + xcb_kill_client(conn, window); + xcb_flush(conn); +} + +void show(xcb_window_t window) { + xcb_map_window(conn, window); + xcb_flush(conn); +} + +void hide(xcb_window_t window) { + xcb_unmap_window(conn, window); + xcb_flush(conn); +} + +void send_to_top(xcb_window_t win) { + uint32_t values[1] = {XCB_STACK_MODE_ABOVE}; + xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values); + xcb_flush(conn); +} + +Geometry get_geometry(xcb_window_t win) { + xcb_get_geometry_reply_t *geom = + xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL); + Geometry g = {geom->x, geom->y, geom->width, geom->height}; + free(geom); + return g; +} + +Geometry 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}; + free(geom); + return g; +} + +Geometry get_screen(void) { + return (Geometry){0, 0, scr->width_in_pixels, scr->height_in_pixels}; +} + +void warp_pointer(xcb_window_t win, int x, int y) { + xcb_warp_pointer(conn, XCB_NONE, win, 0, 0, 0, 0, x, y); + xcb_flush(conn); +} + +void move_window(xcb_window_t win, int x, int y) { + uint32_t values[2] = {x, y}; + xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, + values); + xcb_flush(conn); +} + +void resize_window(xcb_window_t win, int width, int height) { + uint32_t values[2] = {width, height}; + xcb_configure_window( + conn, win, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); + xcb_flush(conn); +} + +typedef struct Event { + int32_t type; + uint32_t window; + int8_t override_redirect; + uint32_t btn; + int32_t x; + int32_t y; + uint32_t height; + uint32_t width; + uint32_t state; + int8_t is_root; +} Event; + +Event wait_for_event(void) { + Event ret = {0}; + + xcb_generic_event_t *ev; + ev = xcb_wait_for_event(conn); + + if (!ev) + errx(1, "xcb connection broken"); + + switch (CLEANMASK(ev->response_type)) { + + case XCB_CREATE_NOTIFY: { + xcb_create_notify_event_t *e; + e = (xcb_create_notify_event_t *)ev; + ret.type = 1; + ret.window = e->window; + ret.override_redirect = e->override_redirect; + } break; + + case XCB_DESTROY_NOTIFY: { + xcb_destroy_notify_event_t *e; + e = (xcb_destroy_notify_event_t *)ev; + ret.type = 2; + ret.window = e->window; + } break; + + case XCB_ENTER_NOTIFY: { + xcb_enter_notify_event_t *e; + e = (xcb_enter_notify_event_t *)ev; + ret.type = 3; + ret.window = e->event; + } break; + + case XCB_MAP_NOTIFY: { + xcb_map_notify_event_t *e; + e = (xcb_map_notify_event_t *)ev; + ret.type = 4; + ret.window = e->window; + } break; + + case XCB_BUTTON_PRESS: { + xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev; + if (!e->child) + break; + 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 = 5; + 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; + } break; + + case XCB_MOTION_NOTIFY: { + xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *)ev; + ret.type = 6; + ret.window = e->child; + ret.x = e->event_x; + ret.y = e->event_y; + ret.state = e->state; + } break; + + case XCB_BUTTON_RELEASE: { + xcb_button_release_event_t *e = (xcb_button_release_event_t *)ev; + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + ret.type = 7; + ret.x = e->event_x; + ret.y = e->event_y; + ret.btn = e->detail; + } break; + + case XCB_KEY_PRESS: { + xcb_key_press_event_t *e; + e = (xcb_key_press_event_t *)ev; + ret.type = 8; + ret.window = e->child; + ret.btn = e->detail; + ret.state = e->state; + } break; + + case XCB_KEY_RELEASE: { + xcb_key_release_event_t *e; + e = (xcb_key_release_event_t *)ev; + ret.type = 9; + ret.window = e->child; + ret.btn = e->detail; + ret.state = e->state; + } break; + + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *e; + e = (xcb_configure_notify_event_t *)ev; + ret.type = 10; + ret.window = e->window; + ret.width = e->width; + ret.height = e->height; + ret.x = e->x; + ret.y = e->y; + } break; + } + + xcb_flush(conn); + free(ev); + return ret; +} diff --git a/kutu.rb b/kutu.rb new file mode 100755 index 0000000..57a4a86 --- /dev/null +++ b/kutu.rb @@ -0,0 +1,224 @@ +#!/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