Add better icccm support
This commit is contained in:
195
kutu.rb
195
kutu.rb
@@ -7,6 +7,8 @@ module X
|
||||
ffi_lib File.join(__dir__, "X-kutu.so")
|
||||
|
||||
typedef :uint32, :window_id
|
||||
typedef :uint32, :xcb_window_t
|
||||
typedef :uint32, :xcb_pixmap_t
|
||||
|
||||
class Geometry < FFI::Struct
|
||||
layout :x, :int16,
|
||||
@@ -28,6 +30,39 @@ module X
|
||||
:is_root, :int8
|
||||
end
|
||||
|
||||
class SizeHints < FFI::Struct
|
||||
layout :flags, :uint32,
|
||||
:x, :int32,
|
||||
:y, :int32,
|
||||
:width, :int32,
|
||||
:height, :int32,
|
||||
:min_width, :int32,
|
||||
:min_height, :int32,
|
||||
:max_width, :int32,
|
||||
:max_height, :int32,
|
||||
:width_inc, :int32,
|
||||
:height_inc, :int32,
|
||||
:min_aspect_num, :int32,
|
||||
:min_aspect_den, :int32,
|
||||
:max_aspect_num, :int32,
|
||||
:max_aspect_den, :int32,
|
||||
:base_width, :int32,
|
||||
:base_height, :int32,
|
||||
:win_gravity, :uint32
|
||||
end
|
||||
|
||||
class WMHints < FFI::Struct
|
||||
layout :flags, :int32,
|
||||
:input, :uint32,
|
||||
:initial_state,:int32,
|
||||
:icon_pixmap, :xcb_pixmap_t,
|
||||
:icon_window, :xcb_window_t,
|
||||
:icon_x, :int32,
|
||||
:icon_y, :int32,
|
||||
:icon_mask, :xcb_pixmap_t,
|
||||
:window_group, :xcb_window_t
|
||||
end
|
||||
|
||||
attach_function :deploy, [], :int
|
||||
attach_function :add_keybind, [:int], :void
|
||||
attach_function :add_mousebind, [:int], :void
|
||||
@@ -47,6 +82,11 @@ module X
|
||||
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
|
||||
attach_function :get_wm_name, [:window_id], :string
|
||||
attach_function :get_wm_n_hints, [:window_id], SizeHints.by_value
|
||||
attach_function :get_wm_hints, [:window_id], WMHints.by_value
|
||||
attach_function :get_wm_transient_for, [:window_id], :uint8
|
||||
attach_function :set_wm_state, [:window_id, :int], :void
|
||||
end
|
||||
|
||||
if X.deploy < 0
|
||||
@@ -70,15 +110,20 @@ def refresh_monitors
|
||||
end
|
||||
end
|
||||
|
||||
refresh_monitors
|
||||
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 get_wm_normal_hints(window_id)
|
||||
# xprop_output = `xprop -id 0x#{window_id.to_s(16)} WM_NORMAL_HINTS`
|
||||
# return {} unless xprop_output =~ /WM_NORMAL_HINTS\(([^)]+)\):\s*(.*)/
|
||||
# hints = {}
|
||||
# # TODO: parse the output properly
|
||||
# hints
|
||||
# end
|
||||
refresh_monitors
|
||||
|
||||
$workspaces = {}
|
||||
$windows = {}
|
||||
@@ -105,20 +150,62 @@ class Node
|
||||
end
|
||||
|
||||
class Window < Node
|
||||
attr_accessor :window_id, :x, :y, :width, :height#, :state
|
||||
attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace
|
||||
|
||||
def initialize(window_id)
|
||||
def initialize(window_id, workspace)
|
||||
@workspace = workspace
|
||||
@window_id = window_id
|
||||
# @state = :widthrawn # :iconic, :withdrawn, :normal
|
||||
# ADD: properties for https://tronche.com/gui/x/icccm/sec-4.html#s-4 sec 4.1.2.(3, 4)
|
||||
@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 apply_geometry
|
||||
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
|
||||
@@ -172,7 +259,7 @@ class WindowBlock < Node
|
||||
child.height = child_size
|
||||
end
|
||||
child.compute_geometry! if child.is_a?(WindowBlock)
|
||||
child.apply_geometry if child.is_a?(Window)
|
||||
child.apply_geometry! if child.is_a?(Window)
|
||||
pos += child_size
|
||||
end
|
||||
end
|
||||
@@ -210,14 +297,19 @@ class Workspace
|
||||
end
|
||||
|
||||
def add(window_id)
|
||||
@tiled_root_block.add_node Window.new(window_id)
|
||||
$windows[window_id] = self
|
||||
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_id)
|
||||
@untiled_windows.delete window_id
|
||||
@tiled_root_block.each_node { |node| node.children.delete window_id }
|
||||
$windows.delete window_id
|
||||
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
|
||||
@@ -239,16 +331,18 @@ end
|
||||
EVENT_TYPES = {
|
||||
0 => :unused,
|
||||
1 => :create,
|
||||
2 => :close,
|
||||
2 => :closed,
|
||||
3 => :enter,
|
||||
-1 => :show_after,
|
||||
4 => :show,
|
||||
5 => :mouse_press,
|
||||
6 => :mouse_drag,
|
||||
7 => :mouse_release,
|
||||
8 => :key_press,
|
||||
9 => :key_release,
|
||||
10 => :configured
|
||||
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)
|
||||
@@ -284,24 +378,32 @@ loop do
|
||||
next unless event[:override_redirect].zero?
|
||||
X.subscribe event[:window]
|
||||
X.focus event[:window]
|
||||
when :close
|
||||
X.kill event[:window]
|
||||
$windows[event[:window]]&.remove 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]
|
||||
when :show_after
|
||||
when :showed
|
||||
next unless event[:override_redirect].zero?
|
||||
X.focus event[:window]
|
||||
X.send_to_top event[:window]
|
||||
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 :show
|
||||
X.show event[:window]
|
||||
when :mouse_press
|
||||
next if event[:is_root] != 0
|
||||
X.send_to_top event[:window]
|
||||
X.focus event[:window]
|
||||
$mouse_state = event[:btn]
|
||||
$mouse_window = event[:window]
|
||||
$mouse_window = $windows[event[:window]]
|
||||
if $mouse_window.nil?
|
||||
$mouse_state = -1
|
||||
$mouse_window = -1
|
||||
next
|
||||
end
|
||||
$mouse_pos_start = X.get_pointer
|
||||
$geom_start = X.get_geometry event[:window]
|
||||
$mousebind_actions[event[:btn]]&.call(event)
|
||||
@@ -315,16 +417,15 @@ loop do
|
||||
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
|
||||
$mouse_window.move 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
|
||||
$mouse_window.resize [$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
|
||||
X.focus $mouse_window.window_id
|
||||
X.send_to_top $mouse_window.window_id
|
||||
$mouse_state = -1
|
||||
$mouse_window = -1
|
||||
$mouse_pos_start = { x: -1, y: -1 }
|
||||
@@ -334,7 +435,13 @@ loop do
|
||||
$keybind_actions[event[:btn]]&.call(event)
|
||||
when :key_release
|
||||
# TODO
|
||||
when :configured
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user