Make it work
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.so
|
*.so
|
||||||
*.vim
|
*.vim
|
||||||
*p.yml
|
*p.yml
|
||||||
|
.num.json
|
||||||
|
|||||||
22
kutu.rb
22
kutu.rb
@@ -16,10 +16,11 @@ end
|
|||||||
|
|
||||||
# Require modules
|
# Require modules
|
||||||
|
|
||||||
require_relative "./src/ruby/utils"
|
load File.join(__dir__, "./src/ruby/utils.rb")
|
||||||
require_relative "./src/ruby/node"
|
load File.join(__dir__, "./src/ruby/controller.rb")
|
||||||
require_relative "./src/ruby/workspace"
|
load File.join(__dir__, "./src/ruby/window.rb")
|
||||||
require_relative "./src/ruby/events"
|
load File.join(__dir__, "./src/ruby/workspace.rb")
|
||||||
|
load File.join(__dir__, "./src/ruby/events.rb")
|
||||||
|
|
||||||
|
|
||||||
# Cleanup on exit
|
# Cleanup on exit
|
||||||
@@ -33,7 +34,6 @@ end
|
|||||||
# Globals
|
# Globals
|
||||||
|
|
||||||
$monitors = {}
|
$monitors = {}
|
||||||
$workspaces = {}
|
|
||||||
$windows = {}
|
$windows = {}
|
||||||
|
|
||||||
$keybind_actions = {}
|
$keybind_actions = {}
|
||||||
@@ -47,23 +47,17 @@ $rect = {}
|
|||||||
|
|
||||||
# Initialize monitors
|
# Initialize monitors
|
||||||
|
|
||||||
refresh_monitors!
|
load_monitors!
|
||||||
|
|
||||||
|
|
||||||
# Run startup script
|
# Run startup script
|
||||||
|
|
||||||
pid = spawn File.join(__dir__, "/src/shell/startup.sh")
|
run File.join(__dir__, "./src/shell/startup.sh")
|
||||||
Process.detach pid
|
|
||||||
|
|
||||||
|
|
||||||
# Add keybinds
|
# Add keybinds
|
||||||
|
|
||||||
load File.join(__dir__, "/src/ruby/bindings.rb")
|
load File.join(__dir__, "./src/ruby/bindings.rb")
|
||||||
|
|
||||||
|
|
||||||
# Initialize workspaces
|
|
||||||
|
|
||||||
$workspaces[:main] = Workspace.new(:main)
|
|
||||||
|
|
||||||
|
|
||||||
# Main loop
|
# Main loop
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ void cleanup(void) {
|
|||||||
|
|
||||||
// Keybind function to setup a key grab if the keycode is clicked along with the
|
// Keybind function to setup a key grab if the keycode is clicked along with the
|
||||||
// MOD key
|
// MOD key
|
||||||
void add_keybind(int key) {
|
void add_keybind(int key, int mod) {
|
||||||
xcb_grab_key(conn, 0, scr->root, MOD, key, XCB_GRAB_MODE_ASYNC,
|
xcb_grab_key(conn, 0, scr->root, mod ? MOD : 0, key, XCB_GRAB_MODE_ASYNC,
|
||||||
XCB_GRAB_MODE_ASYNC);
|
XCB_GRAB_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mousebind function to setup a mouse button grab if the button is clicked
|
// Mousebind function to setup a mouse button grab if the button is clicked
|
||||||
// along with the MOD key
|
// along with the MOD key
|
||||||
void add_mousebind(int button) {
|
void add_mousebind(int button, int mod) {
|
||||||
xcb_grab_button(conn, 0, scr->root,
|
xcb_grab_button(conn, 0, scr->root,
|
||||||
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
|
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
|
||||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, scr->root, XCB_NONE,
|
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, scr->root, XCB_NONE,
|
||||||
button, MOD);
|
button, mod ? MOD : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy function to initialize the X connection, set up event masks, and
|
// Deploy function to initialize the X connection, set up event masks, and
|
||||||
@@ -271,6 +271,8 @@ void ungrab_pointer(void) {
|
|||||||
Event wait_for_event(void) {
|
Event wait_for_event(void) {
|
||||||
Event ret = {0};
|
Event ret = {0};
|
||||||
|
|
||||||
|
xcb_flush(conn);
|
||||||
|
|
||||||
xcb_generic_event_t *ev;
|
xcb_generic_event_t *ev;
|
||||||
ev = xcb_wait_for_event(conn);
|
ev = xcb_wait_for_event(conn);
|
||||||
|
|
||||||
@@ -318,14 +320,13 @@ Event wait_for_event(void) {
|
|||||||
|
|
||||||
case XCB_BUTTON_PRESS: {
|
case XCB_BUTTON_PRESS: {
|
||||||
xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev;
|
xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev;
|
||||||
if (!e->child)
|
|
||||||
break;
|
|
||||||
ret.type = 6;
|
ret.type = 6;
|
||||||
ret.window = e->child;
|
ret.window = e->child;
|
||||||
ret.is_root = e->child == scr->root;
|
ret.is_root = e->child == scr->root;
|
||||||
ret.x = e->event_x;
|
ret.x = e->event_x;
|
||||||
ret.y = e->event_y;
|
ret.y = e->event_y;
|
||||||
ret.btn = e->detail;
|
ret.btn = e->detail;
|
||||||
|
ret.state = (e->state & MOD) != 0;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case XCB_MOTION_NOTIFY: {
|
case XCB_MOTION_NOTIFY: {
|
||||||
@@ -350,7 +351,7 @@ Event wait_for_event(void) {
|
|||||||
ret.type = 9;
|
ret.type = 9;
|
||||||
ret.window = e->child;
|
ret.window = e->child;
|
||||||
ret.btn = e->detail;
|
ret.btn = e->detail;
|
||||||
ret.state = e->state;
|
ret.state = (e->state & MOD) != 0;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case XCB_KEY_RELEASE: {
|
case XCB_KEY_RELEASE: {
|
||||||
@@ -388,7 +389,6 @@ Event wait_for_event(void) {
|
|||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_flush(conn);
|
|
||||||
free(ev);
|
free(ev);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ typedef struct Event {
|
|||||||
int deploy(void);
|
int deploy(void);
|
||||||
void cleanup(void);
|
void cleanup(void);
|
||||||
|
|
||||||
void add_keybind(int key);
|
void add_keybind(int key, int mod);
|
||||||
void add_mousebind(int button);
|
void add_mousebind(int button, int mod);
|
||||||
|
|
||||||
xcb_window_t get_focus(void);
|
xcb_window_t get_focus(void);
|
||||||
xcb_window_t get_root(void);
|
xcb_window_t get_root(void);
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ module X
|
|||||||
end
|
end
|
||||||
|
|
||||||
attach_function :deploy, [], :int
|
attach_function :deploy, [], :int
|
||||||
attach_function :add_keybind, [:int], :void
|
attach_function :add_keybind, [:int, :int], :void
|
||||||
attach_function :add_mousebind, [:int], :void
|
attach_function :add_mousebind, [:int, :int], :void
|
||||||
attach_function :cleanup, [], :void
|
attach_function :cleanup, [], :void
|
||||||
attach_function :focus, [:xcb_window_t], :void
|
attach_function :focus, [:xcb_window_t], :void
|
||||||
attach_function :get_focus, [], :xcb_window_t
|
attach_function :get_focus, [], :xcb_window_t
|
||||||
|
|||||||
@@ -1,23 +1,162 @@
|
|||||||
|
keybind(23) do |_event|
|
||||||
|
run "firefox"
|
||||||
|
end
|
||||||
|
|
||||||
keybind(24) do |event|
|
keybind(24) do |event|
|
||||||
X.kill event[:window]
|
X.kill event[:window]
|
||||||
end
|
end
|
||||||
|
|
||||||
keybind(25) do |_event|
|
keybind(25) do |_event|
|
||||||
pid = spawn "kitty"
|
run "kitty"
|
||||||
Process.detach pid
|
|
||||||
end
|
end
|
||||||
|
|
||||||
keybind(26) do |_event|
|
keybind(26) do |_event|
|
||||||
$workspaces.each_value(&:close_all)
|
# run "~/dotfiles/scripts/power.sh"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
keybind(38) do |_event|
|
||||||
|
run "maim -c 0.3,0.5,1.0,0.8 -s | tee /tmp/screenshot_temp.png | xclip -selection clipboard -t image/png && if [ -s '/tmp/screenshot_temp.png' ]; then mv /tmp/screenshot_temp.png ~/screenshots/$(date +%Y-%m-%d_%H:%M:%S).png; fi"
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(54) do |_event|
|
||||||
|
run "kitty -e fish -c \"y\""
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(53) do |_event|
|
||||||
|
run "kitty -e fish -c \"editor\""
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(40) do |_event|
|
||||||
|
run "~/dotfiles/scripts/run.sh"
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(33) do |_event|
|
||||||
|
run "~/.config/polybar/launch.sh"
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(56) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
create_workspace monitor
|
||||||
|
persistence_path = File.join(__dir__, ".num.json")
|
||||||
|
persistence = File.exist?(persistence_path) ?
|
||||||
|
JSON.parse(File.read(persistence_path), symbolize_names: true) :
|
||||||
|
{}
|
||||||
|
persistence[$monitors.key(monitor)] = monitor[:workspaces].length
|
||||||
|
File.write(persistence_path, JSON.pretty_generate(persistence))
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(57) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
delete_workspace monitor[:workspaces].length - 1, monitor
|
||||||
|
persistence_path = File.join(__dir__, ".num.json")
|
||||||
|
persistence = File.exist?(persistence_path) ?
|
||||||
|
JSON.parse(File.read(persistence_path), symbolize_names: true) :
|
||||||
|
{}
|
||||||
|
persistence[$monitors.key(monitor)] = monitor[:workspaces].length
|
||||||
|
File.write(persistence_path, JSON.pretty_generate(persistence))
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(112, 0) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length
|
||||||
|
select_workspace next_ws, monitor
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(112) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
pointer = X.get_pointer
|
||||||
|
|
||||||
|
window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w|
|
||||||
|
pointer[:x] >= w.x &&
|
||||||
|
pointer[:x] < w.x + w.width &&
|
||||||
|
pointer[:y] >= w.y &&
|
||||||
|
pointer[:y] < w.y + w.height
|
||||||
|
end
|
||||||
|
|
||||||
|
next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length
|
||||||
|
monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(117, 0) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length
|
||||||
|
select_workspace next_ws, monitor
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(117) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
pointer = X.get_pointer
|
||||||
|
|
||||||
|
window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w|
|
||||||
|
pointer[:x] >= w.x &&
|
||||||
|
pointer[:x] < w.x + w.width &&
|
||||||
|
pointer[:y] >= w.y &&
|
||||||
|
pointer[:y] < w.y + w.height
|
||||||
|
end
|
||||||
|
|
||||||
|
next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length
|
||||||
|
monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(27) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
return unless monitor
|
||||||
|
monitor[:workspaces][monitor[:selected_workspace]].switch_direction
|
||||||
|
end
|
||||||
|
|
||||||
keybind(55) do |_event|
|
keybind(55) do |_event|
|
||||||
pid = spawn "rofi -modi 'clipboard:greenclip print' -show clipboard -run-command '{cmd}'"
|
run "rofi -modi 'clipboard:greenclip print' -show clipboard -run-command '{cmd}'"
|
||||||
Process.detach pid
|
end
|
||||||
|
|
||||||
|
keybind(39) do |event|
|
||||||
|
window = $windows[event[:window]]
|
||||||
|
window.toggle_floating if window
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
mousebind 1
|
mousebind 1
|
||||||
|
|
||||||
mousebind 3
|
mousebind 3
|
||||||
|
|
||||||
|
mousebind(8, 0) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length
|
||||||
|
select_workspace next_ws, monitor
|
||||||
|
end
|
||||||
|
|
||||||
|
mousebind 8 do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
pointer = X.get_pointer
|
||||||
|
|
||||||
|
window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w|
|
||||||
|
pointer[:x] >= w.x &&
|
||||||
|
pointer[:x] < w.x + w.width &&
|
||||||
|
pointer[:y] >= w.y &&
|
||||||
|
pointer[:y] < w.y + w.height
|
||||||
|
end
|
||||||
|
|
||||||
|
next_ws = (monitor[:selected_workspace] + 1) % monitor[:workspaces].length
|
||||||
|
monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window
|
||||||
|
end
|
||||||
|
|
||||||
|
mousebind(9, 0) do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length
|
||||||
|
select_workspace next_ws, monitor
|
||||||
|
end
|
||||||
|
|
||||||
|
mousebind 9 do |_event|
|
||||||
|
monitor = current_monitor
|
||||||
|
pointer = X.get_pointer
|
||||||
|
|
||||||
|
window = monitor[:workspaces][monitor[:selected_workspace]].windows.find do |w|
|
||||||
|
pointer[:x] >= w.x &&
|
||||||
|
pointer[:x] < w.x + w.width &&
|
||||||
|
pointer[:y] >= w.y &&
|
||||||
|
pointer[:y] < w.y + w.height
|
||||||
|
end
|
||||||
|
|
||||||
|
next_ws = (monitor[:selected_workspace] - 1) % monitor[:workspaces].length
|
||||||
|
monitor[:workspaces][next_ws].drop monitor[:workspaces][next_ws].tiled_windows.length, window if window
|
||||||
|
end
|
||||||
|
|||||||
20
src/ruby/controller.rb
Normal file
20
src/ruby/controller.rb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
def create_workspace(monitor)
|
||||||
|
monitor[:workspaces] << Workspace.new($monitors.key(monitor))
|
||||||
|
monitor[:selected_workspace] = monitor[:workspaces].length - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_workspace(n, monitor)
|
||||||
|
return if monitor[:workspaces].length <= 1
|
||||||
|
monitor[:workspaces][n].windows.each { |w| monitor[:workspaces][(n - 1) % monitor[:workspaces].length].drop 0, w }
|
||||||
|
monitor[:workspaces].delete_at n
|
||||||
|
if monitor[:selected_workspace] >= n
|
||||||
|
monitor[:selected_workspace] -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_workspace(n, monitor)
|
||||||
|
monitor[:workspaces].each { |w| w.hide if w != monitor[:workspaces][n] }
|
||||||
|
monitor[:selected_workspace] = n
|
||||||
|
monitor[:workspaces][n].show
|
||||||
|
monitor[:workspaces][n].compute_tiled!
|
||||||
|
end
|
||||||
@@ -9,7 +9,7 @@ EVENT_TYPES = {
|
|||||||
8 => :mouse_release,
|
8 => :mouse_release,
|
||||||
9 => :key_press,
|
9 => :key_press,
|
||||||
10 => :key_release,
|
10 => :key_release,
|
||||||
11 => :closed,
|
11 => :closed_temp,
|
||||||
12 => :configure_request,
|
12 => :configure_request,
|
||||||
13 => :resize_request
|
13 => :resize_request
|
||||||
}.freeze
|
}.freeze
|
||||||
@@ -26,33 +26,25 @@ def handle_event(event)
|
|||||||
|
|
||||||
when :closed
|
when :closed
|
||||||
$windows[event[:window]]&.delete
|
$windows[event[:window]]&.delete
|
||||||
$workspaces[:main].tiled_root_block.compute_geometry!
|
|
||||||
|
|
||||||
|
|
||||||
when :enter
|
when :enter
|
||||||
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 }
|
|
||||||
X.focus event[:window]
|
|
||||||
|
|
||||||
|
|
||||||
when :showed
|
|
||||||
return unless event[:override_redirect].zero?
|
|
||||||
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 }
|
|
||||||
X.focus event[:window]
|
X.focus event[:window]
|
||||||
|
|
||||||
|
|
||||||
when :show_request
|
when :show_request
|
||||||
$workspaces[:main].add event[:window] if $windows[event[:window]].nil?
|
monitor = current_monitor
|
||||||
$workspaces[:main].tiled_root_block.compute_geometry!
|
if $windows[event[:window]].nil?
|
||||||
X.show event[:window]
|
monitor[:workspaces][monitor[:selected_workspace]].create event[:window]
|
||||||
|
X.show event[:window]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
when :mouse_press
|
when :mouse_press
|
||||||
|
$mousebind_actions[[event[:btn], event[:state]]]&.call(event)
|
||||||
return if event[:is_root] != 0
|
return if event[:is_root] != 0
|
||||||
return if $windows[event[:window]].nil?
|
return if $windows[event[:window]].nil?
|
||||||
|
return if event[:state] != 1
|
||||||
$mouse_data[:btn] = event[:btn]
|
$mouse_data[:btn] = event[:btn]
|
||||||
$mouse_data[:window] = $windows[event[:window]]
|
$mouse_data[:window] = $windows[event[:window]]
|
||||||
X.grab_pointer $root
|
X.grab_pointer $root
|
||||||
@@ -63,7 +55,6 @@ def handle_event(event)
|
|||||||
else
|
else
|
||||||
$mouse_data[:mode] = :tiled
|
$mouse_data[:mode] = :tiled
|
||||||
end
|
end
|
||||||
$mousebind_actions[event[:btn]]&.call(event)
|
|
||||||
|
|
||||||
when :mouse_drag
|
when :mouse_drag
|
||||||
mouse_pos = X.get_pointer
|
mouse_pos = X.get_pointer
|
||||||
@@ -73,13 +64,14 @@ def handle_event(event)
|
|||||||
if $mouse_data[:btn] == 1
|
if $mouse_data[:btn] == 1
|
||||||
$mouse_data[:window].move $mouse_data[:geometry][:x] + dx, $mouse_data[:geometry][:y] + dy
|
$mouse_data[:window].move $mouse_data[:geometry][:x] + dx, $mouse_data[:geometry][:y] + dy
|
||||||
elsif $mouse_data[:btn] == 3
|
elsif $mouse_data[:btn] == 3
|
||||||
X.resize_window $mouse_data[:window].window_id,
|
$mouse_data[:window]&.resize [$mouse_data[:geometry][:width] + dx, 50].max,
|
||||||
[$mouse_data[:geometry][:width] + dx, 50].max,
|
[$mouse_data[:geometry][:height] + dy, 50].max,
|
||||||
[$mouse_data[:geometry][:height] + dy, 50].max
|
true
|
||||||
end
|
end
|
||||||
elsif $mouse_data[:mode] == :tiled
|
elsif $mouse_data[:mode] == :tiled
|
||||||
if $mouse_data[:btn] == 1
|
if $mouse_data[:btn] == 1
|
||||||
find_targets = compute_drop_targets! $mouse_data[:window]
|
monitor = current_monitor
|
||||||
|
find_targets = monitor[:workspaces][monitor[:selected_workspace]].insertion_rects
|
||||||
target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y])
|
target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y])
|
||||||
if target
|
if target
|
||||||
if $rect[:target] != target
|
if $rect[:target] != target
|
||||||
@@ -98,17 +90,21 @@ def handle_event(event)
|
|||||||
|
|
||||||
when :mouse_release
|
when :mouse_release
|
||||||
if $mouse_data[:mode] == :tiled
|
if $mouse_data[:mode] == :tiled
|
||||||
X.destroy $rect[:id] if $rect[:id]
|
if $mouse_data[:btn] == 1
|
||||||
$rect = {}
|
if $rect[:id]
|
||||||
|
X.destroy $rect[:id]
|
||||||
|
monitor = current_monitor
|
||||||
|
monitor[:workspaces][monitor[:selected_workspace]].drop $rect[:target][:drop], $mouse_data[:window]
|
||||||
|
end
|
||||||
|
$rect = {}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
X.ungrab_pointer
|
X.ungrab_pointer
|
||||||
X.send_to_top $mouse_data[:window].window_id if $mouse_data[:window]
|
|
||||||
X.focus $mouse_data[:window].window_id if $mouse_data[:window]
|
|
||||||
$mouse_data = {}
|
$mouse_data = {}
|
||||||
|
|
||||||
|
|
||||||
when :key_press
|
when :key_press
|
||||||
$keybind_actions[event[:btn]]&.call(event)
|
$keybind_actions[[event[:btn], event[:state]]]&.call(event)
|
||||||
|
|
||||||
when :key_release
|
when :key_release
|
||||||
# TODO
|
# TODO
|
||||||
@@ -116,12 +112,8 @@ def handle_event(event)
|
|||||||
|
|
||||||
when :configure_request
|
when :configure_request
|
||||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||||
X.send_to_top event[:window]
|
|
||||||
X.focus event[:window]
|
|
||||||
|
|
||||||
when :resize_request
|
when :resize_request
|
||||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||||
X.send_to_top event[:window]
|
|
||||||
X.focus event[:window]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
class Node
|
|
||||||
attr_accessor :size
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@size = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Require submodules
|
|
||||||
|
|
||||||
require_relative "./window"
|
|
||||||
require_relative "./window_block"
|
|
||||||
@@ -1,28 +1,49 @@
|
|||||||
def keybind(key, &block)
|
require 'json'
|
||||||
X.add_keybind key
|
|
||||||
$keybind_actions[key] = block if block
|
def keybind(key, mod = 1, &block)
|
||||||
|
X.add_keybind key, mod
|
||||||
|
$keybind_actions[[key, mod]] = block if block
|
||||||
end
|
end
|
||||||
|
|
||||||
def mousebind(btn, &block)
|
def mousebind(btn, mod = 1, &block)
|
||||||
X.add_mousebind btn
|
X.add_mousebind btn, mod
|
||||||
$mousebind_actions[btn] = block if block
|
$mousebind_actions[[btn, mod]] = block if block
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def run(command)
|
||||||
|
pid = spawn command
|
||||||
|
Process.detach pid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def refresh_monitors!
|
def load_monitors!
|
||||||
$monitors.clear
|
$monitors.clear
|
||||||
xrandr_output = `xrandr --query`
|
xrandr_output = `xrandr --query`
|
||||||
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
||||||
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
||||||
|
persistence_path = File.join(__dir__, ".num.json")
|
||||||
|
persistence = File.exist?(persistence_path) ?
|
||||||
|
JSON.parse(File.read(persistence_path), symbolize_names: true) :
|
||||||
|
{}
|
||||||
connected.first(2).each_with_index do |line, index|
|
connected.first(2).each_with_index do |line, index|
|
||||||
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
||||||
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
||||||
key = index.zero? ? :primary : :secondary
|
key = index.zero? ? :primary : :secondary
|
||||||
$monitors[key] = { x: x, y: y, width: w, height: h }
|
$monitors[key] = { x: x, y: y, width: w, height: h, workspaces: Array.new(persistence[key] || 1) { Workspace.new(key) }, selected_workspace: 0 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def current_monitor(pointer = X.get_pointer)
|
||||||
|
$monitors.find do |_, r|
|
||||||
|
pointer[:x] >= r[:x] &&
|
||||||
|
pointer[:x] < r[:x] + r[:width] &&
|
||||||
|
pointer[:y] >= r[:y] &&
|
||||||
|
pointer[:y] < r[:y] + r[:height]
|
||||||
|
end&.last
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def fixed_size_or_aspect?(hints)
|
def fixed_size_or_aspect?(hints)
|
||||||
@@ -38,39 +59,6 @@ def fixed_size_or_aspect?(hints)
|
|||||||
same_size || fixed_aspect
|
same_size || fixed_aspect
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def compute_drop_targets!(window)
|
|
||||||
targets = []
|
|
||||||
|
|
||||||
$monitors.each_value do |monitor|
|
|
||||||
# if monitor[:windows].nil? || monitor[:windows].empty?
|
|
||||||
# targets << { type: :monitor_full, x: monitor[:x], y: monitor[:y], width: monitor[:width], height: monitor[:height], monitor: monitor }
|
|
||||||
# else
|
|
||||||
margin = 60
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
$windows.each_value do |w|
|
|
||||||
next if w.floating
|
|
||||||
next if w == window
|
|
||||||
|
|
||||||
margin_x = (w.width / 4).round
|
|
||||||
margin_y = (w.height / 4).round
|
|
||||||
|
|
||||||
targets << { type: :window_top, x: w.x, y: w.y, width: w.width, height: margin_y, window: w }
|
|
||||||
targets << { type: :window_bottom, x: w.x, y: w.y + w.height - margin_y, width: w.width, height: margin_y, window: w }
|
|
||||||
targets << { type: :window_left, x: w.x, y: w.y, width: margin_x, height: w.height, window: w }
|
|
||||||
targets << { type: :window_right, x: w.x + w.width - margin_x, y: w.y, width: margin_x, height: w.height, window: w }
|
|
||||||
end
|
|
||||||
|
|
||||||
targets
|
|
||||||
end
|
|
||||||
|
|
||||||
def drop_target(targets, cx, cy)
|
def drop_target(targets, cx, cy)
|
||||||
targets.find do |t|
|
targets.find do |t|
|
||||||
cx >= t[:x] &&
|
cx >= t[:x] &&
|
||||||
|
|||||||
@@ -1,34 +1,47 @@
|
|||||||
class Window < Node
|
class Window
|
||||||
attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace
|
attr_accessor :window_id, :floating, :workspace, :def_floating, :size, :x, :y, :width, :height
|
||||||
|
|
||||||
def initialize(window_id, workspace)
|
def initialize(window_id, workspace)
|
||||||
|
@size = 0
|
||||||
@workspace = workspace
|
@workspace = workspace
|
||||||
@window_id = window_id
|
@window_id = window_id
|
||||||
@name = X.get_wm_name(window_id)
|
wm_n_hints = X.get_wm_n_hints(window_id)
|
||||||
@wm_n_hints = X.get_wm_n_hints(window_id)
|
|
||||||
@floating = false
|
@floating = false
|
||||||
if @wm_n_hints
|
if wm_n_hints
|
||||||
if fixed_size_or_aspect?(@wm_n_hints)
|
if fixed_size_or_aspect?(wm_n_hints)
|
||||||
@floating = true
|
@floating = true
|
||||||
@width = @wm_n_hints[:max_width]
|
@def_floating = true
|
||||||
if @wm_n_hints[:flags] & 128 != 0
|
@width = wm_n_hints[:max_width]
|
||||||
@height = @wm_n_hints[:max_width] * (@wm_n_hints[:min_aspect_num] / @wm_n_hints[:min_aspect_den])
|
if wm_n_hints[:flags] & 128 != 0
|
||||||
|
@height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den])
|
||||||
else
|
else
|
||||||
@height = @wm_n_hints[:max_height]
|
@height = wm_n_hints[:max_height]
|
||||||
end
|
end
|
||||||
@x = @workspace.width / 2 - @width / 2
|
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||||
@y = @workspace.height / 2 - @height / 2
|
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||||
apply_geometry!
|
apply_geometry!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@wm_hints = X.get_wm_hints(window_id)
|
wm_hints = X.get_wm_hints(window_id)
|
||||||
if @wm_hints
|
if wm_hints
|
||||||
if @wm_hints[:flags] & 1 != 0 && @wm_hints[:initial_state] == 3
|
if wm_hints[:flags] & 1 != 0 && wm_hints[:initial_state] == 3
|
||||||
X.set_wm_state window_id, 1
|
X.set_wm_state window_id, 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@transient_for = X.get_wm_transient_for(window_id)
|
transient_for = X.get_wm_transient_for(window_id)
|
||||||
@floating = true unless @transient_for.zero?
|
unless transient_for.zero?
|
||||||
|
@floating = true
|
||||||
|
@def_floating = true
|
||||||
|
@width = wm_n_hints[:max_width]
|
||||||
|
if wm_n_hints[:flags] & 128 != 0
|
||||||
|
@height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den])
|
||||||
|
else
|
||||||
|
@height = wm_n_hints[:max_height]
|
||||||
|
end
|
||||||
|
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||||
|
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||||
|
apply_geometry!
|
||||||
|
end
|
||||||
super()
|
super()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -40,21 +53,32 @@ class Window < Node
|
|||||||
block.call(self)
|
block.call(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def toggle_floating
|
||||||
|
if !@def_floating
|
||||||
|
@floating = !@floating
|
||||||
|
@workspace.check_floating self
|
||||||
|
apply_geometry!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def move(x, y)
|
def move(x, y)
|
||||||
return unless @floating
|
return unless @floating
|
||||||
@x, @y = x, y
|
@x, @y = x, y
|
||||||
apply_geometry!
|
apply_geometry!
|
||||||
end
|
end
|
||||||
|
|
||||||
def resize(width, height)
|
def resize(width, height, force = false)
|
||||||
return unless @floating
|
return unless @floating
|
||||||
@x = @workspace.width / 2 - width / 2
|
|
||||||
@y = @workspace.height / 2 - height / 2
|
|
||||||
@width, @height = width, height
|
@width, @height = width, height
|
||||||
|
if !force
|
||||||
|
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||||
|
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||||
|
end
|
||||||
apply_geometry!
|
apply_geometry!
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_geometry!
|
def apply_geometry!
|
||||||
|
X.send_to_top @window_id if @floating
|
||||||
X.move_window @window_id, @x, @y
|
X.move_window @window_id, @x, @y
|
||||||
X.resize_window @window_id, @width, @height
|
X.resize_window @window_id, @width, @height
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
class WindowBlock < Node
|
|
||||||
attr_accessor :children, :direction, :size, :x, :y, :width, :height
|
|
||||||
|
|
||||||
def initialize(direction = :horizontal)
|
|
||||||
@children = []
|
|
||||||
@direction = direction
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_node(node)
|
|
||||||
@children << node
|
|
||||||
end
|
|
||||||
|
|
||||||
def 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_percent = @children.map { |c| c.size.to_i > 0 ? c.size.to_i : 0 }.sum
|
|
||||||
flex_count = @children.count { |c| c.size.to_i <= 0 }
|
|
||||||
total_space = horizontal ? @width : @height
|
|
||||||
total_percent = [total_percent, 100].min
|
|
||||||
remaining_percent = 100 - total_percent
|
|
||||||
flex_percent = flex_count > 0 ? remaining_percent / flex_count : 0
|
|
||||||
pos = horizontal ? @x : @y
|
|
||||||
|
|
||||||
@children.each do |child|
|
|
||||||
percent = child.size.to_i > 0 ? child.size.to_i : flex_percent
|
|
||||||
child_size = (total_space * percent) / 100.0
|
|
||||||
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
|
|
||||||
def initialize(workspace)
|
|
||||||
super(:horizontal)
|
|
||||||
@x = workspace.x
|
|
||||||
@y = workspace.y
|
|
||||||
@width = workspace.width
|
|
||||||
@height = workspace.height
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,45 +1,146 @@
|
|||||||
class Workspace
|
class Workspace
|
||||||
attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block
|
attr_reader :monitor_id
|
||||||
|
attr_accessor :tiled_windows, :untiled_windows
|
||||||
|
|
||||||
def initialize(name, monitor = :primary)
|
def initialize(monitor_id = :primary, direction = :horizontal)
|
||||||
@monitor = monitor
|
@monitor_id = monitor_id
|
||||||
@x = $monitors[monitor][:x]
|
@direction = direction
|
||||||
@y = $monitors[monitor][:y]
|
@tiled_windows = []
|
||||||
@width = $monitors[monitor][:width]
|
|
||||||
@height = $monitors[monitor][:height]
|
|
||||||
@tiled_root_block = RootWindowBlock.new(self)
|
|
||||||
@untiled_windows = []
|
@untiled_windows = []
|
||||||
@name = name
|
end
|
||||||
|
|
||||||
|
def drop(idx_dst, window)
|
||||||
|
idx_src = window.workspace.tiled_windows.index(window)
|
||||||
|
window.workspace.tiled_windows.delete_at idx_src
|
||||||
|
self.tiled_windows.insert idx_dst, window
|
||||||
|
window.workspace.compute_tiled! if window.workspace != self
|
||||||
|
window.workspace = self
|
||||||
|
compute_tiled!
|
||||||
|
select_workspace $monitors[@monitor_id][:selected_workspace], $monitors[@monitor_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def insertion_rects
|
||||||
|
rects = []
|
||||||
|
return rects if @tiled_windows.empty?
|
||||||
|
compute_tiled!
|
||||||
|
|
||||||
|
horizontal = (@direction == :horizontal)
|
||||||
|
z = 100
|
||||||
|
half = z / 2
|
||||||
|
|
||||||
|
wins = @tiled_windows
|
||||||
|
|
||||||
|
first = wins.first
|
||||||
|
if horizontal
|
||||||
|
rects << { x: first.x, y: first.y, width: z, height: first.height, drop: 0 }
|
||||||
|
else
|
||||||
|
rects << { x: first.x, y: first.y, width: first.width, height: z, drop: 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
wins.each_cons(2).with_index do |(a, b), i|
|
||||||
|
if horizontal
|
||||||
|
mid = (a.x + a.width + b.x) / 2.0
|
||||||
|
rects << {
|
||||||
|
x: mid - half,
|
||||||
|
y: a.y,
|
||||||
|
width: z,
|
||||||
|
height: a.height,
|
||||||
|
drop: i
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mid = (a.y + a.height + b.y) / 2.0
|
||||||
|
rects << {
|
||||||
|
x: a.x,
|
||||||
|
y: mid - half,
|
||||||
|
width: a.width,
|
||||||
|
height: z,
|
||||||
|
drop: i
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
last = wins.last
|
||||||
|
if horizontal
|
||||||
|
rects << { x: last.x + last.width - z, y: last.y, width: z, height: last.height, drop: wins.count - 1 }
|
||||||
|
else
|
||||||
|
rects << { x: last.x, y: last.y + last.height - z, width: z, height: last.width, drop: wins.count - 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
rects
|
||||||
end
|
end
|
||||||
|
|
||||||
def windows
|
def windows
|
||||||
windows = []
|
@tiled_windows + @untiled_windows
|
||||||
@untiled_windows.each { |w| windows << w }
|
|
||||||
@tiled_root_block.each_leaf { |w| windows << w }
|
|
||||||
windows
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add(window_id)
|
def check_floating(window)
|
||||||
|
if window.floating
|
||||||
|
@untiled_windows << window
|
||||||
|
@tiled_windows.delete window
|
||||||
|
else
|
||||||
|
@tiled_windows << window
|
||||||
|
@untiled_windows.delete window
|
||||||
|
end
|
||||||
|
compute_tiled!
|
||||||
|
end
|
||||||
|
|
||||||
|
def switch_direction
|
||||||
|
@direction = @direction == :horizontal ? :vertical : :horizontal
|
||||||
|
windows.each { |window| window.size = 0 }
|
||||||
|
compute_tiled!
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(window_id)
|
||||||
window = Window.new(window_id, self)
|
window = Window.new(window_id, self)
|
||||||
|
$windows[window_id] = window
|
||||||
if window.floating
|
if window.floating
|
||||||
@untiled_windows << window
|
@untiled_windows << window
|
||||||
else
|
else
|
||||||
@tiled_root_block.add_node window
|
@tiled_windows << window
|
||||||
|
compute_tiled!
|
||||||
end
|
end
|
||||||
$windows[window_id] = window
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(window)
|
def remove(window)
|
||||||
@untiled_windows.delete window
|
@untiled_windows.delete window
|
||||||
@tiled_root_block.each_node { |node| node.children.delete window }
|
@tiled_windows.delete window
|
||||||
$windows.delete window.window_id
|
$windows.delete window.window_id
|
||||||
|
compute_tiled!
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute_tiled!
|
||||||
|
return if @tiled_windows.empty?
|
||||||
|
|
||||||
|
monitor = $monitors[@monitor_id]
|
||||||
|
horizontal = @direction == :horizontal
|
||||||
|
abs_total = @tiled_windows.sum { |c| c.size.to_i }
|
||||||
|
flex_count = @tiled_windows.count { |c| c.size.to_i <= 0 }
|
||||||
|
total_space = horizontal ? monitor[:width] : monitor[:height]
|
||||||
|
remaining = total_space - abs_total
|
||||||
|
remaining = 0 if remaining < 0
|
||||||
|
flex_each = flex_count > 0 ? (remaining.to_f / flex_count) : 0
|
||||||
|
pos = horizontal ? monitor[:x] : monitor[:y]
|
||||||
|
|
||||||
|
@tiled_windows.each do |window|
|
||||||
|
px = window.size.to_i > 0 ? window.size.to_i : flex_each
|
||||||
|
if horizontal
|
||||||
|
window.x = pos
|
||||||
|
window.y = monitor[:y]
|
||||||
|
window.width = px
|
||||||
|
window.height = monitor[:height]
|
||||||
|
else
|
||||||
|
window.x = monitor[:x]
|
||||||
|
window.y = pos
|
||||||
|
window.width = monitor[:width]
|
||||||
|
window.height = px
|
||||||
|
end
|
||||||
|
window.apply_geometry!
|
||||||
|
pos += px
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def close_all
|
def close_all
|
||||||
self.windows.each do |window|
|
self.windows.each { |window| remove window }
|
||||||
X.kill window.window_id
|
|
||||||
remove window
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide
|
def hide
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
picom --config ~/.config/i3/picom.conf
|
picom --config ~/.config/i3/picom.conf &
|
||||||
|
greenclip daemon &
|
||||||
|
xss-lock --transfer-sleep-lock -- ~/dotfiles/scripts/lock.sh &
|
||||||
|
dunst -config ~/.config/dunst/dunstrc &
|
||||||
|
|
||||||
|
bluetoothctl power off
|
||||||
|
|
||||||
|
magick -size 1920x1080 xc:#000000 /tmp/f_bg.png
|
||||||
|
feh --bg-fill /tmp/f_bg.png
|
||||||
|
xsetroot -cursor_name left_ptr
|
||||||
|
|
||||||
|
xrdb ~/.Xresources
|
||||||
|
setxkbmap us
|
||||||
|
|
||||||
|
~/.config/polybar/launch.sh special &>>"$LOGFILE" &
|
||||||
|
|||||||
Reference in New Issue
Block a user