Improve workspace system into a hashtable

This commit is contained in:
2026-09-10 18:25:53 +01:00
parent 6544ae5f23
commit 945ff0a6d3
12 changed files with 353 additions and 221 deletions
-2
View File
@@ -15,8 +15,6 @@ let register_callbacks state =
register_exit state;
Window_op.register_kill state;
Window_op.register_toggle_float state;
Ws_op.register_new state;
Ws_op.register_remove state;
Ws_op.register_jump state;
Ws_op.register_ws_count state;
Keybinds.register_bind state;
+4 -1
View File
@@ -73,7 +73,10 @@ let dispatch_keybind mrb hashtbl key modifier value1 value2 =
| None -> ()
| Some block ->
let arg1 = Mruby.Bindings.mrb_int_value mrb value1 in
let arg2 = Mruby.Bindings.mrb_int_value mrb value2 in
let arg2 =
Mruby.Bindings.mrb_intern_cstr mrb value2
|> Mruby.Bindings.mrb_symbol_value
in
let argv = CArray.of_list Mruby.Types.Mrb_value.typ [ arg1; arg2 ] in
ignore
(Mruby.Bindings.mrb_funcall_argv mrb block
+35 -28
View File
@@ -31,35 +31,42 @@ let register_toggle_float (state : Kutu.State.wm_state) =
let win_id = allocate int64_t 0L in
ignore (mrb_get_args mrb "i" win_id);
let window = Unsigned.UInt32.of_int64 !@win_id in
let ws = List.nth state.workspaces state.current in
let windows = Kutu.Layout.calculate state.screen ws.ws_root in
(match Kutu.Layout.find_fwindow window windows with
| Some { fw_rect; _ } ->
begin match Kutu.Layout.find_fwindow window ws.ws_fwindows with
| Some _ -> print_endline "Error: window already floating"
begin match Hashtbl.find_opt state.workspaces state.current with
| Some ws -> (
let windows = Kutu.Layout.calculate state.screen ws.ws_root in
match Kutu.Layout.find_fwindow window windows with
| Some { fw_rect; _ } ->
begin match Kutu.Layout.find_fwindow window ws.ws_fwindows with
| Some _ -> print_endline "Error: window already floating"
| None ->
let new_root, _ = Kutu.Layout.remove window ws.ws_root in
ws.ws_root <- new_root;
ws.ws_fwindows <-
{ fw_rect; fw_window = window } :: ws.ws_fwindows;
Xcb.Window.move_to_top state.conn window;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
end
| None ->
let new_root, _ = Kutu.Layout.remove window ws.ws_root in
ws.ws_root <- new_root;
ws.ws_fwindows <- { fw_rect; fw_window = window } :: ws.ws_fwindows;
Xcb.Window.move_to_top state.conn window;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
end
| None ->
begin match Kutu.Layout.find_fwindow window ws.ws_fwindows with
| Some _ ->
ws.ws_fwindows <- Kutu.Layout.remove_fwindow window ws.ws_fwindows;
Xcb.Window.move_to_bottom state.conn window;
ws.ws_root <-
Kutu.Layout.insert Kutu.Layout.Horizontal window ws.ws_root;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
| None -> print_endline "Error: window is neither tiled nor floating"
end);
begin match Kutu.Layout.find_fwindow window ws.ws_fwindows with
| Some _ ->
ws.ws_fwindows <-
Kutu.Layout.remove_fwindow window ws.ws_fwindows;
Xcb.Window.move_to_bottom state.conn window;
ws.ws_root <-
Some
(Kutu.Layout.insert Kutu.Layout.Horizontal window ws.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
| None ->
print_endline "Error: window is neither tiled nor floating"
end)
| None -> ()
end;
self
in
Mruby.Bindings.mrb_define_method state.mrb
+29 -86
View File
@@ -1,98 +1,41 @@
open Ctypes
open Foreign
let register_new (state : Kutu.State.wm_state) =
let new_ws (mrb : Mruby.Types.Mrb_state.t structure ptr)
(self : Mruby.Types.Mrb_value.t structure) =
let old_ws = List.nth state.workspaces state.current in
state.workspaces <-
List.append state.workspaces [ { ws_root = Nothing; ws_fwindows = [] } ];
state.current <- List.length state.workspaces - 1;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
(Kutu.Layout.calculate state.screen old_ws.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
old_ws.ws_fwindows;
self
in
Mruby.Bindings.mrb_define_method state.mrb
(Mruby.Types.Mrb_state.get_object_class state.mrb)
"new_ws" new_ws (Unsigned.UInt32.of_int 0)
let rec remove_at index = function
| [] -> []
| _ :: rest when index = 0 -> rest
| x :: rest -> x :: remove_at (index - 1) rest
let register_remove (state : Kutu.State.wm_state) =
let mrb_get_args =
foreign "mrb_get_args"
(ptr Mruby.Types.Mrb_state.typ
@-> string @-> ptr int64_t @-> returning int)
in
let remove_ws (mrb : Mruby.Types.Mrb_state.t structure ptr)
(self : Mruby.Types.Mrb_value.t structure) =
let ws_number = allocate int64_t 0L in
ignore (mrb_get_args mrb "i" ws_number);
let ws_number = Signed.Int64.to_int !@ws_number in
let total = List.length state.workspaces in
if total > 1 && ws_number < total && ws_number >= 0 then (
let dest_idx = if ws_number > 0 then ws_number - 1 else 1 in
let removed = List.nth state.workspaces ws_number in
let dest = List.nth state.workspaces dest_idx in
dest.ws_fwindows <- dest.ws_fwindows @ removed.ws_fwindows;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
dest.ws_root <- Kutu.Layout.insert Horizontal window dest.ws_root;
Xcb.Window.unmap state.conn window)
(Kutu.Layout.calculate state.screen removed.ws_root);
state.workspaces <- remove_at ws_number state.workspaces;
if ws_number = state.current then
state.current <- (if ws_number > 0 then ws_number - 1 else 0)
else if ws_number > state.current then state.current <- state.current - 1;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.map state.conn window;
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen dest.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.map state.conn window)
dest.ws_fwindows);
self
in
Mruby.Bindings.mrb_define_method state.mrb
(Mruby.Types.Mrb_state.get_object_class state.mrb)
"remove_ws" remove_ws
(Mruby.Bindings.mrb_args_req 1)
let register_jump (state : Kutu.State.wm_state) =
let mrb_get_args =
foreign "mrb_get_args"
(ptr Mruby.Types.Mrb_state.typ
@-> string @-> ptr int64_t @-> returning int)
@-> string
@-> ptr Mruby.Types.Mrb_sym.typ
@-> returning int)
in
let jump_ws (mrb : Mruby.Types.Mrb_state.t structure ptr)
(self : Mruby.Types.Mrb_value.t structure) =
let ws_number = allocate int64_t 0L in
ignore (mrb_get_args mrb "i" ws_number);
let ws_number = Signed.Int64.to_int !@ws_number in
let total = List.length state.workspaces in
if ws_number < total && ws_number >= 0 && ws_number <> state.current then (
let old_ws = List.nth state.workspaces state.current in
let new_ws = List.nth state.workspaces ws_number in
state.current <- ws_number;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
(Kutu.Layout.calculate state.screen old_ws.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
old_ws.ws_fwindows;
let name = allocate Mruby.Types.Mrb_sym.typ (Unsigned.UInt32.of_int 0) in
ignore (mrb_get_args mrb "n" name);
let name = Mruby.Bindings.mrb_sym_name mrb !@name in
let new_ws =
match Hashtbl.find_opt state.workspaces name with
| Some ws -> ws
| None ->
let ws = { Kutu.Layout.ws_root = None; ws_fwindows = [] } in
Hashtbl.add state.workspaces name ws;
ws
in
if state.current <> name then (
begin match Hashtbl.find_opt state.workspaces state.current with
| Some old_ws ->
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
(Kutu.Layout.calculate state.screen old_ws.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect } ->
Xcb.Window.unmap state.conn window)
old_ws.ws_fwindows
| None -> ()
end;
state.current <- name;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.map state.conn window;
@@ -113,7 +56,7 @@ let register_ws_count (state : Kutu.State.wm_state) =
let ws_count (mrb : Mruby.Types.Mrb_state.t structure ptr)
(_ : Mruby.Types.Mrb_value.t structure) =
Mruby.Bindings.mrb_int_value mrb
(List.length state.workspaces |> Unsigned.UInt32.of_int)
(Hashtbl.length state.workspaces |> Unsigned.UInt32.of_int)
in
Mruby.Bindings.mrb_define_method state.mrb
(Mruby.Types.Mrb_state.get_object_class state.mrb)
+59 -42
View File
@@ -1,7 +1,6 @@
type direction = Horizontal | Vertical
type layout =
| Nothing
| Leaf of { layout_window : Xcb.Types.Window.t }
| Split of {
layout_direction : direction;
@@ -13,10 +12,13 @@ type layout =
type fwindow = { fw_rect : Decl.Types.rect; fw_window : Xcb.Types.Window.t }
type workspace = {
mutable ws_root : layout;
mutable ws_root : layout option;
mutable ws_fwindows : fwindow list;
}
let empty_workspace () = { ws_root = None; ws_fwindows = [] }
let is_empty_workspace ws = ws.ws_root = None && ws.ws_fwindows = []
let find_fwindow window fwindows =
List.find_opt (fun fw -> fw.fw_window = window) fwindows
@@ -26,39 +28,51 @@ let remove_fwindow window fwindows =
let flip_direction = function Horizontal -> Vertical | Vertical -> Horizontal
let rec insert direction window = function
| Nothing -> Leaf { layout_window = window }
| Leaf { layout_window = existing } ->
Split
{
layout_direction = direction;
layout_ratio = 0.5;
layout_l = Leaf { layout_window = existing };
layout_r = Leaf { layout_window = window };
}
| Split { layout_direction; layout_ratio; layout_l; layout_r } ->
Split
{
layout_direction;
layout_ratio;
layout_l;
layout_r = insert (flip_direction layout_direction) window layout_r;
}
| None -> Leaf { layout_window = window }
| Some l -> (
match l with
| Leaf { layout_window = existing } ->
Split
{
layout_direction = direction;
layout_ratio = 0.5;
layout_l = Leaf { layout_window = existing };
layout_r = Leaf { layout_window = window };
}
| Split { layout_direction; layout_ratio; layout_l; layout_r } ->
Split
{
layout_direction;
layout_ratio;
layout_l;
layout_r =
insert (flip_direction layout_direction) window (Some layout_r);
})
let rec remove window = function
| Nothing -> (Nothing, false)
| Leaf { layout_window = existing } ->
if existing = window then (Nothing, true)
else (Leaf { layout_window = existing }, false)
| Split { layout_direction; layout_ratio; layout_l; layout_r } -> (
let l', l_removed = remove window layout_l in
let r', r_removed = remove window layout_r in
let removed = l_removed || r_removed in
match (l', r') with
| Nothing, r -> (r, removed)
| l, Nothing -> (l, removed)
| l, r ->
( Split { layout_direction; layout_ratio; layout_l = l; layout_r = r },
removed ))
| None -> (None, false)
| Some l -> (
match l with
| Leaf { layout_window = existing } ->
if existing = window then (None, true)
else (Some (Leaf { layout_window = existing }), false)
| Split { layout_direction; layout_ratio; layout_l; layout_r } -> (
let l', l_removed = remove window (Some layout_l) in
let r', r_removed = remove window (Some layout_r) in
let removed = l_removed || r_removed in
match (l', r') with
| None, r -> (r, removed)
| l, None -> (l, removed)
| Some l, Some r ->
( Some
(Split
{
layout_direction;
layout_ratio;
layout_l = l;
layout_r = r;
}),
removed )))
let split_rect (rect : Decl.Types.rect) direction ratio =
match direction with
@@ -104,12 +118,15 @@ let split_rect (rect : Decl.Types.rect) direction ratio =
(top, bottom)
let rec calculate rect = function
| Nothing -> []
| Leaf { layout_window } -> [ { fw_rect = rect; fw_window = layout_window } ]
| Split { layout_direction; layout_ratio; layout_l; layout_r } ->
let left_rect, right_rect =
split_rect rect layout_direction layout_ratio
in
let left_windows = calculate left_rect layout_l in
let right_windows = calculate right_rect layout_r in
left_windows @ right_windows
| None -> []
| Some l -> (
match l with
| Leaf { layout_window } ->
[ { fw_rect = rect; fw_window = layout_window } ]
| Split { layout_direction; layout_ratio; layout_l; layout_r } ->
let left_rect, right_rect =
split_rect rect layout_direction layout_ratio
in
let left_windows = calculate left_rect (Some layout_l) in
let right_windows = calculate right_rect (Some layout_r) in
left_windows @ right_windows)
+6 -5
View File
@@ -4,8 +4,9 @@ type wm_state = {
mrb : Mruby.Types.Mrb_state.mrb_ptr;
root : Xcb.Types.Window.t;
mutable focus : Xcb.Types.Window.t;
mutable workspaces : Layout.workspace list;
mutable current : int;
mutable current : string;
workspaces : (string, Layout.workspace) Hashtbl.t;
window_workspace : (Xcb.Types.Window.t, string) Hashtbl.t;
screen : Decl.Types.rect;
blocks : (int64, Mruby.Types.Mrb_value.t Ctypes.structure) Hashtbl.t;
}
@@ -21,9 +22,9 @@ let new_state =
mrb;
root;
focus = root;
workspaces =
[ { Layout.ws_root = Layout.Nothing; Layout.ws_fwindows = [] } ];
current = 0;
current = "default";
workspaces = Hashtbl.create 16;
window_workspace = Hashtbl.create 16;
screen = Xcb.Screen_iterator.screen_rect conn;
blocks = Hashtbl.create 16;
}
+55 -55
View File
@@ -1,4 +1,10 @@
let () =
let startup_script =
if Array.length Sys.argv <> 2 then (
Printf.eprintf "usage: kutu <startup.rb>\n";
exit 1)
else Sys.argv.(1)
in
let state = Kutu.State.new_state in
print_endline "Started Kutu WM";
@@ -18,59 +24,39 @@ let () =
"e" => 26,
"r" => 27,
"f" => 41,
"n" => 57,
"b" => 56,
"m" => 58,
"v" => 55,
"]" => 35,
"[" => 34,
"1" => 10,
"2" => 11,
"3" => 12,
"4" => 13,
"5" => 14,
"6" => 15,
"7" => 16,
"8" => 17,
"9" => 18,
"0" => 19
}
def bind(key, mod = :alt, &block)
key = KEY_MAP[key]
key_ = key.is_a?(String) ? KEY_MAP[key] : key
mod = MOD_MAP[mod]
raise "unknown key" unless key
raise "unknown key #{key.to_s}" unless key_
raise "unknown modifier" unless mod
_bind key, mod, &block
_bind key_, mod, &block
end
def unbind(key, mod = :alt)
key = KEY_MAP[key]
key_ = key.is_a?(String) ? KEY_MAP[key] : key
mod = MOD_MAP[mod]
raise "unknown key" unless key
raise "unknown key #{key.to_s}" unless key_
raise "unknown modifier" unless mod
_unbind key, mod
end
bind "tab" do
system "firefox >/dev/null 2>&1 &"
end
bind ?q do |x|
kill x
end
bind ?w do
system "kitty >/dev/null 2>&1 &"
end
bind ?e do
exit
end
bind ?r do
unbind ?w
end
bind ?f do |x|
toggle_float x
end
bind ?v do |_, x|
remove_ws x
end
bind ?b do |_, x|
jump_ws ((x - 1 + ws_count) % ws_count)
end
bind ?n do |_, x|
jump_ws ((x + 1) % ws_count)
end
bind ?m do
new_ws
_unbind key_, mod
end
|};
Mruby.Core.mrb_load_file state.mrb startup_script;
Mruby.Core.error_check state.mrb;
if Xcb.Connection.flush state.conn <= 0 then exit 1;
@@ -85,8 +71,7 @@ let () =
let key = Xcb.Types.Events.Key.detail req in
let modifier = Xcb.Types.Events.Key.state req in
Bindings.Keybinds.dispatch_keybind state.mrb state.blocks key
modifier state.focus
(Unsigned.UInt32.of_int state.current)
modifier state.focus state.current
| MapRequest ->
let req = Xcb.Types.Events.Map_request.from ev in
let window = Xcb.Types.Events.Map_request.window req in
@@ -94,13 +79,21 @@ let () =
Xcb.Window.focus state.conn window;
Xcb.Window.move_to_bottom state.conn window;
state.focus <- window;
let ws = List.nth state.workspaces state.current in
let ws =
match Hashtbl.find_opt state.workspaces state.current with
| Some ws -> ws
| None ->
let ws = { Kutu.Layout.ws_root = None; ws_fwindows = [] } in
Hashtbl.add state.workspaces state.current ws;
ws
in
ws.ws_root <-
Kutu.Layout.insert Kutu.Layout.Horizontal window ws.ws_root;
Some (Kutu.Layout.insert Kutu.Layout.Horizontal window ws.ws_root);
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
(Kutu.Layout.calculate state.screen ws.ws_root);
Hashtbl.replace state.window_workspace window state.current
| Create ->
let req = Xcb.Types.Events.Create.from ev in
let window = Xcb.Types.Events.Create.window req in
@@ -117,17 +110,24 @@ let () =
| Destroy ->
let req = Xcb.Types.Events.Destroy.from ev in
let window = Xcb.Types.Events.Destroy.window req in
let ws = List.nth state.workspaces state.current in
let new_root, removed = Kutu.Layout.remove window ws.ws_root in
if removed then ws.ws_root <- new_root
else
ws.ws_fwindows <- Kutu.Layout.remove_fwindow window ws.ws_fwindows;
Xcb.Window.focus state.conn state.root;
state.focus <- state.root;
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
begin match Hashtbl.find_opt state.window_workspace window with
| None -> ()
| Some ws_name ->
let ws = Hashtbl.find state.workspaces ws_name in
let new_root, tiled_removed =
Kutu.Layout.remove window ws.ws_root
in
if tiled_removed then ws.ws_root <- new_root
else
ws.ws_fwindows <-
Kutu.Layout.remove_fwindow window ws.ws_fwindows;
Hashtbl.remove state.window_workspace window;
if ws_name = state.current then
List.iter
(fun { Kutu.Layout.fw_window = window; fw_rect = rect } ->
Xcb.Window.reshape state.conn window rect)
(Kutu.Layout.calculate state.screen ws.ws_root)
end
| _ -> ());
Xcb.Utils.free ev);
Mruby.Core.error_check state.mrb;
+8
View File
@@ -34,8 +34,16 @@ let mrb_intern_cstr =
let call_sym mrb = mrb_intern_cstr mrb "call"
let mrb_symbol_value =
foreign "kutu_mrb_symbol_value"
(Types.Mrb_sym.typ @-> returning Types.Mrb_value.typ)
let mrb_funcall_argv =
foreign "mrb_funcall_argv"
(ptr Types.Mrb_state.typ @-> Types.Mrb_value.typ @-> Types.Mrb_sym.typ
@-> int64_t @-> ptr Types.Mrb_value.typ
@-> returning Types.Mrb_value.typ)
let mrb_sym_name =
foreign "mrb_sym_name"
(ptr Types.Mrb_state.typ @-> Types.Mrb_sym.typ @-> returning string)
+4
View File
@@ -12,6 +12,10 @@ let mrb_load_string =
foreign "mrb_load_string"
(ptr Types.Mrb_state.typ @-> string @-> returning void)
let mrb_load_file =
foreign "kutu_mrb_load_file"
(ptr Types.Mrb_state.typ @-> string @-> returning void)
let error_check mrb_ptr =
if Types.Mrb_state.has_error mrb_ptr then begin
mrb_print_error mrb_ptr;
+21
View File
@@ -1,9 +1,30 @@
#include <stdint.h>
#include <stdio.h>
#include <mruby.h>
#include <mruby/value.h>
#include <mruby/compile.h>
mrb_value
kutu_mrb_int_value(mrb_state *mrb, uint64_t value)
{
return mrb_int_value(mrb, (mrb_int)value);
}
mrb_value
kutu_mrb_symbol_value(mrb_sym i)
{
return mrb_symbol_value(i);
}
void
kutu_mrb_load_file(mrb_state *mrb, const char *filename)
{
FILE *file = fopen(filename, "rb");
if (file == NULL) {
mrb_raisef(mrb, E_RUNTIME_ERROR,
"could not open file: %s", filename);
return;
}
mrb_load_file(mrb, file);
fclose(file);
}