Modularize
This commit is contained in:
17
X-kutu.c
17
X-kutu.c
@@ -1,4 +1,3 @@
|
|||||||
#include <xcb/xproto.h>
|
|
||||||
#define CLEANMASK(m) ((m & ~0x80))
|
#define CLEANMASK(m) ((m & ~0x80))
|
||||||
|
|
||||||
// Definitions for modifier keys
|
// Definitions for modifier keys
|
||||||
@@ -127,6 +126,12 @@ void kill(xcb_window_t window) {
|
|||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy a window
|
||||||
|
void destroy(xcb_window_t win) {
|
||||||
|
xcb_destroy_window(conn, win);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
// Show a window
|
// Show a window
|
||||||
void show(xcb_window_t window) {
|
void show(xcb_window_t window) {
|
||||||
xcb_map_window(conn, window);
|
xcb_map_window(conn, window);
|
||||||
@@ -283,16 +288,22 @@ xcb_window_t draw_rectangle(int x, int y, int width, int height,
|
|||||||
return win;
|
return win;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xcb_window_t get_root(void) { return scr->root; }
|
||||||
|
|
||||||
void grab_pointer(xcb_window_t win) {
|
void grab_pointer(xcb_window_t win) {
|
||||||
xcb_grab_pointer(conn, 0, win,
|
xcb_grab_pointer(conn, 0, win,
|
||||||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||||||
XCB_EVENT_MASK_BUTTON_MOTION |
|
XCB_EVENT_MASK_BUTTON_MOTION |
|
||||||
XCB_EVENT_MASK_POINTER_MOTION_HINT,
|
XCB_EVENT_MASK_POINTER_MOTION_HINT,
|
||||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, win, XCB_NONE,
|
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE,
|
||||||
XCB_CURRENT_TIME);
|
XCB_CURRENT_TIME);
|
||||||
|
xcb_flush(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ungrab_pointer(void) { xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); }
|
void ungrab_pointer(void) {
|
||||||
|
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for an event and return it as an Event structure
|
// Wait for an event and return it as an Event structure
|
||||||
// This function is blocking
|
// This function is blocking
|
||||||
|
|||||||
0
build/.keep
Normal file
0
build/.keep
Normal file
@@ -13,11 +13,13 @@ if ! xrandr --version >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
gcc -shared -fPIC -Wall -Wextra -o "$DIR/lib/X-kutu.so" "$DIR/X-kutu.c" $XCB
|
mkdir -p "$DIR/build"
|
||||||
|
|
||||||
if [ ! -f "$DIR/lib/X-kutu.so" ]; then
|
gcc -shared -fPIC -Wall -Wextra -o "$DIR/build/X-kutu.so" "$DIR/X-kutu.c" $XCB
|
||||||
|
|
||||||
|
if [ ! -f "$DIR/build/X-kutu.so" ]; then
|
||||||
echo "Error: compilation failed." >&2
|
echo "Error: compilation failed." >&2
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "Success: $DIR/lib/X-kutu.so is compiled."
|
echo "Success: $DIR/build/X-kutu.so is compiled."
|
||||||
fi
|
fi
|
||||||
|
|||||||
395
kutu.rb
395
kutu.rb
@@ -1,13 +1,31 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
require_relative "./lib/X-kutu"
|
require_relative "./src/X-kutu"
|
||||||
|
|
||||||
|
# Initialize X
|
||||||
|
|
||||||
if X.deploy < 0
|
if X.deploy < 0
|
||||||
raise "Failed to deploy X"
|
raise "Failed to deploy X"
|
||||||
|
else
|
||||||
|
puts "Started kutu WM for X11"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Require modules
|
||||||
|
|
||||||
|
require_relative "./src/utils"
|
||||||
|
require_relative "./src/node"
|
||||||
|
require_relative "./src/workspace"
|
||||||
|
require_relative "./src/events"
|
||||||
|
|
||||||
|
|
||||||
|
# Cleanup on exit
|
||||||
|
|
||||||
at_exit { X.cleanup }
|
at_exit { X.cleanup }
|
||||||
|
|
||||||
|
|
||||||
|
# Globals
|
||||||
|
|
||||||
$monitors = {}
|
$monitors = {}
|
||||||
$workspaces = {}
|
$workspaces = {}
|
||||||
$windows = {}
|
$windows = {}
|
||||||
@@ -17,385 +35,28 @@ $mousebind_actions = {}
|
|||||||
|
|
||||||
$mouse_data = {}
|
$mouse_data = {}
|
||||||
|
|
||||||
def refresh_monitors!
|
$root = X.get_root
|
||||||
$monitors.clear
|
$rect = {}
|
||||||
xrandr_output = `xrandr --query`
|
|
||||||
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
|
||||||
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
|
||||||
connected.first(2).each_with_index do |line, index|
|
|
||||||
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
|
||||||
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
|
||||||
key = index.zero? ? :primary : :secondary
|
|
||||||
$monitors[key] = { x: x, y: y, width: w, height: h }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fixed_size_or_aspect?(hints)
|
|
||||||
return false unless hints
|
# Initialize monitors
|
||||||
flags = hints[:flags] || 0
|
|
||||||
same_size = (flags & (16 | 256) != 0) && (flags & 32 != 0) &&
|
|
||||||
hints[:max_width] == (hints[:min_width] || hints[:base_width]) &&
|
|
||||||
hints[:max_height] == (hints[:min_height] || hints[:base_height])
|
|
||||||
fixed_aspect = (flags & 128 != 0) &&
|
|
||||||
hints[:min_aspect_num] == hints[:max_aspect_num] &&
|
|
||||||
hints[:min_aspect_den] == hints[:max_aspect_den] &&
|
|
||||||
hints[:min_aspect_num] != 0
|
|
||||||
same_size || fixed_aspect
|
|
||||||
end
|
|
||||||
|
|
||||||
refresh_monitors!
|
refresh_monitors!
|
||||||
|
|
||||||
def keybind(key, &block)
|
|
||||||
X.add_keybind key
|
|
||||||
$keybind_actions[key] = block
|
|
||||||
end
|
|
||||||
|
|
||||||
def mousebind(btn, &block)
|
# Add keybinds
|
||||||
X.add_mousebind btn
|
|
||||||
$mousebind_actions[btn] = block if block
|
|
||||||
end
|
|
||||||
|
|
||||||
class Node
|
load "./src/bindings.rb"
|
||||||
attr_accessor :size
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@size = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Window < Node
|
# Initialize workspaces
|
||||||
attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace
|
|
||||||
|
|
||||||
def initialize(window_id, workspace)
|
|
||||||
@workspace = workspace
|
|
||||||
@window_id = window_id
|
|
||||||
@name = X.get_wm_name(window_id)
|
|
||||||
@wm_n_hints = X.get_wm_n_hints(window_id)
|
|
||||||
@floating = false
|
|
||||||
if @wm_n_hints
|
|
||||||
if fixed_size_or_aspect?(@wm_n_hints)
|
|
||||||
@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 = @workspace.width / 2 - @width / 2
|
|
||||||
@y = @workspace.height / 2 - @height / 2
|
|
||||||
apply_geometry!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@wm_hints = X.get_wm_hints(window_id)
|
|
||||||
if @wm_hints
|
|
||||||
if @wm_hints[:flags] & 1 != 0 && @wm_hints[:initial_state] == 3
|
|
||||||
X.set_wm_state window_id, 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@transient_for = X.get_wm_transient_for(window_id)
|
|
||||||
@floating = true unless @transient_for.zero?
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete
|
|
||||||
@workspace.remove self
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_leaf(&block)
|
|
||||||
block.call(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def move(x, y)
|
|
||||||
return unless @floating
|
|
||||||
@x, @y = x, y
|
|
||||||
apply_geometry!
|
|
||||||
end
|
|
||||||
|
|
||||||
def resize(width, height)
|
|
||||||
return unless @floating
|
|
||||||
@x = @workspace.width / 2 - width / 2
|
|
||||||
@y = @workspace.height / 2 - height / 2
|
|
||||||
@width, @height = width, height
|
|
||||||
apply_geometry!
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_geometry!
|
|
||||||
X.move_window @window_id, @x, @y
|
|
||||||
X.resize_window @window_id, @width, @height
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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_fixed = @children.map { |c| c.size || 0 }.sum
|
|
||||||
flex_count = @children.count { |c| c.size.to_i <= 0 }
|
|
||||||
total_space = horizontal ? @width : @height
|
|
||||||
remaining_space = total_space - total_fixed
|
|
||||||
remaining_space = 0 if remaining_space < 0
|
|
||||||
flex_size = flex_count > 0 ? remaining_space / flex_count : 0
|
|
||||||
pos = horizontal ? @x : @y
|
|
||||||
|
|
||||||
@children.each do |child|
|
|
||||||
child_size = child.size.to_i > 0 ? child.size.to_i : flex_size
|
|
||||||
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
|
|
||||||
|
|
||||||
class Workspace
|
|
||||||
attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block
|
|
||||||
|
|
||||||
def initialize(name, monitor = :primary)
|
|
||||||
@monitor = monitor
|
|
||||||
@x = $monitors[monitor][:x]
|
|
||||||
@y = $monitors[monitor][:y]
|
|
||||||
@width = $monitors[monitor][:width]
|
|
||||||
@height = $monitors[monitor][:height]
|
|
||||||
@tiled_root_block = RootWindowBlock.new(self)
|
|
||||||
@untiled_windows = []
|
|
||||||
@name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
def windows
|
|
||||||
windows = []
|
|
||||||
@untiled_windows.each { |w| windows << w }
|
|
||||||
@tiled_root_block.each_leaf { |w| windows << w }
|
|
||||||
windows
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(window_id)
|
|
||||||
window = Window.new(window_id, self)
|
|
||||||
if window.floating
|
|
||||||
@untiled_windows << window
|
|
||||||
else
|
|
||||||
@tiled_root_block.add_node window
|
|
||||||
end
|
|
||||||
$windows[window_id] = window
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove(window)
|
|
||||||
@untiled_windows.delete window
|
|
||||||
@tiled_root_block.each_node { |node| node.children.delete window }
|
|
||||||
$windows.delete window.window_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def close_all
|
|
||||||
self.windows.each do |window|
|
|
||||||
X.kill window.window_id
|
|
||||||
remove window.window_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hide
|
|
||||||
self.windows.each { |window| X.hide window.window_id }
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
self.windows.each { |window| X.show window.window_id }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
EVENT_TYPES = {
|
|
||||||
1 => :create,
|
|
||||||
2 => :closed,
|
|
||||||
3 => :enter,
|
|
||||||
4 => :showed,
|
|
||||||
5 => :show_request,
|
|
||||||
6 => :mouse_press,
|
|
||||||
7 => :mouse_drag,
|
|
||||||
8 => :mouse_release,
|
|
||||||
9 => :key_press,
|
|
||||||
10 => :key_release,
|
|
||||||
11 => :closed,
|
|
||||||
12 => :configure_request,
|
|
||||||
13 => :resize_request
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
$workspaces[:main] = Workspace.new(:main)
|
$workspaces[:main] = Workspace.new(:main)
|
||||||
|
|
||||||
keybind(24) do |event|
|
|
||||||
X.kill event[:window]
|
|
||||||
end
|
|
||||||
|
|
||||||
keybind(25) do |_event|
|
# Main loop
|
||||||
pid = spawn("kitty")
|
|
||||||
Process.detach pid
|
|
||||||
end
|
|
||||||
|
|
||||||
keybind(26) do |_event|
|
|
||||||
$workspaces.each_value(&:close_all)
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
|
|
||||||
keybind(27) do |_event|
|
|
||||||
pp $workspaces[:main].windows
|
|
||||||
end
|
|
||||||
|
|
||||||
mousebind 1
|
|
||||||
mousebind 3
|
|
||||||
|
|
||||||
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
|
loop do
|
||||||
event = X.wait_for_event
|
event = X.wait_for_event
|
||||||
case EVENT_TYPES[event[:type]]
|
handle_event event
|
||||||
when :create
|
|
||||||
next unless event[:override_redirect].zero?
|
|
||||||
X.subscribe event[:window]
|
|
||||||
when :closed
|
|
||||||
pp "Deleting window #{event[:window]}"
|
|
||||||
$windows[event[:window]]&.delete
|
|
||||||
$workspaces[:main].tiled_root_block.compute_geometry!
|
|
||||||
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.focus event[:window]
|
|
||||||
$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
|
|
||||||
$mousebind_actions[event[:btn]]&.call(event)
|
|
||||||
when :mouse_drag
|
|
||||||
mouse_pos = X.get_pointer
|
|
||||||
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 $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
|
|
||||||
# TODO
|
|
||||||
when :configure_request
|
|
||||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
|
||||||
X.send_to_top event[:window]
|
|
||||||
X.focus event[:window]
|
|
||||||
when :resize_request
|
|
||||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
|
||||||
X.send_to_top event[:window]
|
|
||||||
X.focus event[:window]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ require "ffi"
|
|||||||
|
|
||||||
module X
|
module X
|
||||||
extend FFI::Library
|
extend FFI::Library
|
||||||
ffi_lib File.join(__dir__, "X-kutu.so")
|
ffi_lib File.join(__dir__, "../build/X-kutu.so")
|
||||||
|
|
||||||
typedef :uint32, :xcb_window_t
|
typedef :uint32, :xcb_window_t
|
||||||
typedef :uint32, :xcb_pixmap_t
|
typedef :uint32, :xcb_pixmap_t
|
||||||
@@ -68,6 +68,7 @@ module X
|
|||||||
attach_function :get_focus, [], :xcb_window_t
|
attach_function :get_focus, [], :xcb_window_t
|
||||||
attach_function :subscribe, [:xcb_window_t], :void
|
attach_function :subscribe, [:xcb_window_t], :void
|
||||||
attach_function :kill, [:xcb_window_t], :void
|
attach_function :kill, [:xcb_window_t], :void
|
||||||
|
attach_function :destroy, [:xcb_window_t], :void
|
||||||
attach_function :show, [:xcb_window_t], :void
|
attach_function :show, [:xcb_window_t], :void
|
||||||
attach_function :hide, [:xcb_window_t], :void
|
attach_function :hide, [:xcb_window_t], :void
|
||||||
attach_function :send_to_top, [:xcb_window_t], :void
|
attach_function :send_to_top, [:xcb_window_t], :void
|
||||||
@@ -87,4 +88,5 @@ module X
|
|||||||
attach_function :draw_rectangle, [:int, :int, :int, :int, :uint32], :xcb_window_t
|
attach_function :draw_rectangle, [:int, :int, :int, :int, :uint32], :xcb_window_t
|
||||||
attach_function :grab_pointer, [:xcb_window_t], :void
|
attach_function :grab_pointer, [:xcb_window_t], :void
|
||||||
attach_function :ungrab_pointer, [], :void
|
attach_function :ungrab_pointer, [], :void
|
||||||
|
attach_function :get_root, [], :xcb_window_t
|
||||||
end
|
end
|
||||||
18
src/bindings.rb
Normal file
18
src/bindings.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
keybind(24) do |event|
|
||||||
|
X.kill event[:window]
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(25) do |_event|
|
||||||
|
pid = spawn("kitty")
|
||||||
|
Process.detach pid
|
||||||
|
end
|
||||||
|
|
||||||
|
keybind(26) do |_event|
|
||||||
|
$workspaces.each_value(&:close_all)
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mousebind 1
|
||||||
|
|
||||||
|
mousebind 3
|
||||||
127
src/events.rb
Normal file
127
src/events.rb
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
EVENT_TYPES = {
|
||||||
|
1 => :create,
|
||||||
|
2 => :closed,
|
||||||
|
3 => :enter,
|
||||||
|
4 => :showed,
|
||||||
|
5 => :show_request,
|
||||||
|
6 => :mouse_press,
|
||||||
|
7 => :mouse_drag,
|
||||||
|
8 => :mouse_release,
|
||||||
|
9 => :key_press,
|
||||||
|
10 => :key_release,
|
||||||
|
11 => :closed,
|
||||||
|
12 => :configure_request,
|
||||||
|
13 => :resize_request
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
|
||||||
|
def handle_event(event)
|
||||||
|
case EVENT_TYPES[event[:type]]
|
||||||
|
|
||||||
|
|
||||||
|
when :create
|
||||||
|
return unless event[:override_redirect].zero?
|
||||||
|
X.subscribe event[:window]
|
||||||
|
|
||||||
|
|
||||||
|
when :closed
|
||||||
|
$windows[event[:window]]&.delete
|
||||||
|
$workspaces[:main].tiled_root_block.compute_geometry!
|
||||||
|
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
when :show_request
|
||||||
|
$workspaces[:main].add event[:window] if $windows[event[:window]].nil?
|
||||||
|
$workspaces[:main].tiled_root_block.compute_geometry!
|
||||||
|
X.show event[:window]
|
||||||
|
|
||||||
|
|
||||||
|
when :mouse_press
|
||||||
|
return if event[:is_root] != 0
|
||||||
|
return if $windows[event[:window]].nil?
|
||||||
|
$mouse_data[:btn] = event[:btn]
|
||||||
|
$mouse_data[:window] = $windows[event[:window]]
|
||||||
|
X.grab_pointer $root
|
||||||
|
$mouse_data[:pointer] = X.get_pointer
|
||||||
|
if $mouse_data[:window].floating
|
||||||
|
$mouse_data[:mode] = :floating
|
||||||
|
$mouse_data[:geometry] = X.get_geometry event[:window]
|
||||||
|
else
|
||||||
|
$mouse_data[:mode] = :tiled
|
||||||
|
end
|
||||||
|
$mousebind_actions[event[:btn]]&.call(event)
|
||||||
|
|
||||||
|
when :mouse_drag
|
||||||
|
mouse_pos = X.get_pointer
|
||||||
|
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! $mouse_data[:window]
|
||||||
|
target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y])
|
||||||
|
if target
|
||||||
|
if $rect[:target] != target
|
||||||
|
X.destroy $rect[:id] if $rect[:id]
|
||||||
|
$rect[:id] = X.draw_rectangle target[:x], target[:y], target[:width], target[:height], 0x500070
|
||||||
|
$rect[:target] = target
|
||||||
|
end
|
||||||
|
else
|
||||||
|
X.destroy $rect[:id] if $rect[:id]
|
||||||
|
$rect = {}
|
||||||
|
end
|
||||||
|
elsif $mouse_data[:btn] == 3
|
||||||
|
# TODO: tile resize . dynamic
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
when :mouse_release
|
||||||
|
if $mouse_data[:mode] == :tiled
|
||||||
|
X.destroy $rect[:id] if $rect[:id]
|
||||||
|
$rect = {}
|
||||||
|
end
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
|
||||||
|
when :key_press
|
||||||
|
$keybind_actions[event[:btn]]&.call(event)
|
||||||
|
|
||||||
|
when :key_release
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
|
||||||
|
when :configure_request
|
||||||
|
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||||
|
X.send_to_top event[:window]
|
||||||
|
X.focus event[:window]
|
||||||
|
|
||||||
|
when :resize_request
|
||||||
|
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||||
|
X.send_to_top event[:window]
|
||||||
|
X.focus event[:window]
|
||||||
|
end
|
||||||
|
end
|
||||||
13
src/node.rb
Normal file
13
src/node.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class Node
|
||||||
|
attr_accessor :size
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@size = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Require submodules
|
||||||
|
|
||||||
|
require_relative "./window"
|
||||||
|
require_relative "./window_block"
|
||||||
81
src/utils.rb
Normal file
81
src/utils.rb
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
def keybind(key, &block)
|
||||||
|
X.add_keybind key
|
||||||
|
$keybind_actions[key] = block if block
|
||||||
|
end
|
||||||
|
|
||||||
|
def mousebind(btn, &block)
|
||||||
|
X.add_mousebind btn
|
||||||
|
$mousebind_actions[btn] = block if block
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_monitors!
|
||||||
|
$monitors.clear
|
||||||
|
xrandr_output = `xrandr --query`
|
||||||
|
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
||||||
|
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
||||||
|
connected.first(2).each_with_index do |line, index|
|
||||||
|
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
||||||
|
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
||||||
|
key = index.zero? ? :primary : :secondary
|
||||||
|
$monitors[key] = { x: x, y: y, width: w, height: h }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fixed_size_or_aspect?(hints)
|
||||||
|
return false unless hints
|
||||||
|
flags = hints[:flags] || 0
|
||||||
|
same_size = (flags & (16 | 256) != 0) && (flags & 32 != 0) &&
|
||||||
|
hints[:max_width] == (hints[:min_width] || hints[:base_width]) &&
|
||||||
|
hints[:max_height] == (hints[:min_height] || hints[:base_height])
|
||||||
|
fixed_aspect = (flags & 128 != 0) &&
|
||||||
|
hints[:min_aspect_num] == hints[:max_aspect_num] &&
|
||||||
|
hints[:min_aspect_den] == hints[:max_aspect_den] &&
|
||||||
|
hints[:min_aspect_num] != 0
|
||||||
|
same_size || fixed_aspect
|
||||||
|
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)
|
||||||
|
targets.find do |t|
|
||||||
|
cx >= t[:x] &&
|
||||||
|
cy >= t[:y] &&
|
||||||
|
cx < t[:x] + t[:width] &&
|
||||||
|
cy < t[:y] + t[:height]
|
||||||
|
end
|
||||||
|
end
|
||||||
61
src/window.rb
Normal file
61
src/window.rb
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
class Window < Node
|
||||||
|
attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace
|
||||||
|
|
||||||
|
def initialize(window_id, workspace)
|
||||||
|
@workspace = workspace
|
||||||
|
@window_id = window_id
|
||||||
|
@name = X.get_wm_name(window_id)
|
||||||
|
@wm_n_hints = X.get_wm_n_hints(window_id)
|
||||||
|
@floating = false
|
||||||
|
if @wm_n_hints
|
||||||
|
if fixed_size_or_aspect?(@wm_n_hints)
|
||||||
|
@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 = @workspace.width / 2 - @width / 2
|
||||||
|
@y = @workspace.height / 2 - @height / 2
|
||||||
|
apply_geometry!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@wm_hints = X.get_wm_hints(window_id)
|
||||||
|
if @wm_hints
|
||||||
|
if @wm_hints[:flags] & 1 != 0 && @wm_hints[:initial_state] == 3
|
||||||
|
X.set_wm_state window_id, 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@transient_for = X.get_wm_transient_for(window_id)
|
||||||
|
@floating = true unless @transient_for.zero?
|
||||||
|
super()
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
@workspace.remove self
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_leaf(&block)
|
||||||
|
block.call(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def move(x, y)
|
||||||
|
return unless @floating
|
||||||
|
@x, @y = x, y
|
||||||
|
apply_geometry!
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize(width, height)
|
||||||
|
return unless @floating
|
||||||
|
@x = @workspace.width / 2 - width / 2
|
||||||
|
@y = @workspace.height / 2 - height / 2
|
||||||
|
@width, @height = width, height
|
||||||
|
apply_geometry!
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_geometry!
|
||||||
|
X.move_window @window_id, @x, @y
|
||||||
|
X.resize_window @window_id, @width, @height
|
||||||
|
end
|
||||||
|
end
|
||||||
64
src/window_block.rb
Normal file
64
src/window_block.rb
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
52
src/workspace.rb
Normal file
52
src/workspace.rb
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
class Workspace
|
||||||
|
attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block
|
||||||
|
|
||||||
|
def initialize(name, monitor = :primary)
|
||||||
|
@monitor = monitor
|
||||||
|
@x = $monitors[monitor][:x]
|
||||||
|
@y = $monitors[monitor][:y]
|
||||||
|
@width = $monitors[monitor][:width]
|
||||||
|
@height = $monitors[monitor][:height]
|
||||||
|
@tiled_root_block = RootWindowBlock.new(self)
|
||||||
|
@untiled_windows = []
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
def windows
|
||||||
|
windows = []
|
||||||
|
@untiled_windows.each { |w| windows << w }
|
||||||
|
@tiled_root_block.each_leaf { |w| windows << w }
|
||||||
|
windows
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(window_id)
|
||||||
|
window = Window.new(window_id, self)
|
||||||
|
if window.floating
|
||||||
|
@untiled_windows << window
|
||||||
|
else
|
||||||
|
@tiled_root_block.add_node window
|
||||||
|
end
|
||||||
|
$windows[window_id] = window
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove(window)
|
||||||
|
@untiled_windows.delete window
|
||||||
|
@tiled_root_block.each_node { |node| node.children.delete window }
|
||||||
|
$windows.delete window.window_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_all
|
||||||
|
self.windows.each do |window|
|
||||||
|
X.kill window.window_id
|
||||||
|
remove window
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def hide
|
||||||
|
self.windows.each { |window| X.hide window.window_id }
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
self.windows.each { |window| X.show window.window_id }
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user