uh 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +5 -0
- data/Guardfile +7 -0
- data/Rakefile +11 -0
- data/TODO +17 -0
- data/ext/uh/color.c +12 -0
- data/ext/uh/display.c +234 -0
- data/ext/uh/event.c +161 -0
- data/ext/uh/extconf.rb +13 -0
- data/ext/uh/font.c +14 -0
- data/ext/uh/pixmap.c +92 -0
- data/ext/uh/screen.c +12 -0
- data/ext/uh/uh.c +139 -0
- data/ext/uh/uh.h +106 -0
- data/ext/uh/window.c +222 -0
- data/lib/uh.rb +28 -0
- data/lib/uh/display.rb +15 -0
- data/lib/uh/drawable.rb +7 -0
- data/lib/uh/events.rb +32 -0
- data/lib/uh/font.rb +7 -0
- data/lib/uh/geo.rb +42 -0
- data/lib/uh/pixmap.rb +5 -0
- data/lib/uh/screen.rb +7 -0
- data/lib/uh/version.rb +3 -0
- data/lib/uh/window.rb +35 -0
- data/lib/uh/wm.rb +199 -0
- data/lib/uh/wm/action_handler.rb +59 -0
- data/lib/uh/wm/client.rb +76 -0
- data/lib/uh/wm/manager.rb +99 -0
- data/lib/uh/wm/workers/base_worker.rb +20 -0
- data/lib/uh/wm/workers/blocking_worker.rb +11 -0
- data/lib/uh/wm/workers/multiplexing_worker.rb +27 -0
- data/test/test_helper.rb +13 -0
- data/test/uh/test_geo.rb +48 -0
- data/uh.gemspec +21 -0
- metadata +136 -0
data/lib/uh.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'uh.so'
|
2
|
+
require 'uh/display'
|
3
|
+
require 'uh/drawable'
|
4
|
+
require 'uh/events'
|
5
|
+
require 'uh/font'
|
6
|
+
require 'uh/geo'
|
7
|
+
require 'uh/pixmap'
|
8
|
+
require 'uh/screen'
|
9
|
+
require 'uh/version'
|
10
|
+
require 'uh/window'
|
11
|
+
|
12
|
+
module Uh
|
13
|
+
Error = Class.new(StandardError)
|
14
|
+
RuntimeError = Class.new(RuntimeError)
|
15
|
+
ArgumentError = Class.new(Error)
|
16
|
+
OtherWMRunningError = Class.new(RuntimeError)
|
17
|
+
|
18
|
+
KEY_MODIFIERS = {
|
19
|
+
shift: 1 << 0,
|
20
|
+
lock: 1 << 1,
|
21
|
+
ctrl: 1 << 2,
|
22
|
+
mod1: 1 << 3,
|
23
|
+
mod2: 1 << 4,
|
24
|
+
mod3: 1 << 5,
|
25
|
+
mod4: 1 << 6,
|
26
|
+
mod5: 1 << 7
|
27
|
+
}.freeze
|
28
|
+
end
|
data/lib/uh/display.rb
ADDED
data/lib/uh/drawable.rb
ADDED
data/lib/uh/events.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Uh
|
2
|
+
module Events
|
3
|
+
# Input Event Masks. Used as event-mask window attribute and as arguments
|
4
|
+
# to Grab requests. Not to be confused with event names.
|
5
|
+
NO_EVENT_MASK = 0
|
6
|
+
KEY_PRESS_MASK = 1 << 0
|
7
|
+
KEY_RELEASE_MASK = 1 << 1
|
8
|
+
BUTTON_PRESS_MASK = 1 << 2
|
9
|
+
BUTTON_RELEASE_MASK = 1 << 3
|
10
|
+
ENTER_WINDOW_MASK = 1 << 4
|
11
|
+
LEAVE_WINDOW_MASK = 1 << 5
|
12
|
+
POINTER_MOTION_MASK = 1 << 6
|
13
|
+
POINTER_MOTION_HINT_MASK = 1 << 7
|
14
|
+
BUTTON1_MOTION_MASK = 1 << 8
|
15
|
+
BUTTON2_MOTION_MASK = 1 << 9
|
16
|
+
BUTTON3_MOTION_MASK = 1 << 10
|
17
|
+
BUTTON4_MOTION_MASK = 1 << 11
|
18
|
+
BUTTON5_MOTION_MASK = 1 << 12
|
19
|
+
BUTTON_MOTION_MASK = 1 << 13
|
20
|
+
KEYMAP_STATE_MASK = 1 << 14
|
21
|
+
EXPOSURE_MASK = 1 << 15
|
22
|
+
VISIBILITY_CHANGE_MASK = 1 << 16
|
23
|
+
STRUCTURE_NOTIFY_MASK = 1 << 17
|
24
|
+
RESIZE_REDIRECT_MASK = 1 << 18
|
25
|
+
SUBSTRUCTURE_NOTIFY_MASK = 1 << 19
|
26
|
+
SUBSTRUCTURE_REDIRECT_MASK = 1 << 20
|
27
|
+
FOCUS_CHANGE_MASK = 1 << 21
|
28
|
+
PROPERTY_CHANGE_MASK = 1 << 22
|
29
|
+
COLORMAP_CHANGE_MASK = 1 << 23
|
30
|
+
OWNER_GRAB_BUTTON_MASK = 1 << 24
|
31
|
+
end
|
32
|
+
end
|
data/lib/uh/font.rb
ADDED
data/lib/uh/geo.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Uh
|
2
|
+
class Geo < Struct.new(:x, :y, :width, :height)
|
3
|
+
class << self
|
4
|
+
def format_xgeometry(x, y, width, height)
|
5
|
+
'%sx%s+%s+%s' % [
|
6
|
+
width ? width.to_s : ??,
|
7
|
+
height ? height.to_s : ??,
|
8
|
+
x ? x.to_s : ??,
|
9
|
+
y ? y.to_s : ??
|
10
|
+
]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super
|
16
|
+
%i[width height].each do |dimension|
|
17
|
+
check_value dimension, send(dimension)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
self.class.format_xgeometry *values
|
23
|
+
end
|
24
|
+
|
25
|
+
def width=(value)
|
26
|
+
check_value :width, value
|
27
|
+
super value
|
28
|
+
end
|
29
|
+
|
30
|
+
def height=(value)
|
31
|
+
check_value :height, value
|
32
|
+
super value
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def check_value(name, value)
|
39
|
+
fail ArgumentError, "invalid #{name.to_s}: #{value}" unless value > 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/uh/pixmap.rb
ADDED
data/lib/uh/screen.rb
ADDED
data/lib/uh/version.rb
ADDED
data/lib/uh/window.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Uh
|
2
|
+
class Window
|
3
|
+
def to_s
|
4
|
+
id.to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def ==(other)
|
8
|
+
id == other.id
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(geo)
|
12
|
+
_configure geo.x, geo.y, geo.width, geo.height
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure_event(geo)
|
17
|
+
_configure_event geo.x, geo.y, geo.width, geo.height
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_sub(geo)
|
22
|
+
_create_sub geo.x, geo.y, geo.width, geo.height
|
23
|
+
end
|
24
|
+
|
25
|
+
def moveresize(geo)
|
26
|
+
_moveresize geo.x, geo.y, geo.width, geo.height
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def show
|
31
|
+
map
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/uh/wm.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'logger'
|
3
|
+
require 'uh/wm/action_handler'
|
4
|
+
require 'uh/wm/client'
|
5
|
+
require 'uh/wm/manager'
|
6
|
+
require 'uh/wm/workers/base_worker'
|
7
|
+
require 'uh/wm/workers/blocking_worker'
|
8
|
+
require 'uh/wm/workers/multiplexing_worker'
|
9
|
+
|
10
|
+
module Uh
|
11
|
+
class WM
|
12
|
+
include Events
|
13
|
+
|
14
|
+
LOGGER_FORMAT_STR = "%s.%03i %s: %s\n".freeze
|
15
|
+
LOGGER_FORMATER = proc do |severity, datetime, progname, message|
|
16
|
+
LOGGER_FORMAT_STR % [
|
17
|
+
datetime.strftime('%FT%T'),
|
18
|
+
datetime.usec / 1000,
|
19
|
+
severity[0..0],
|
20
|
+
message
|
21
|
+
]
|
22
|
+
end
|
23
|
+
LOGGER_LEVEL = Logger::INFO
|
24
|
+
LOGGER_DEBUG_ENV = 'UH_DEBUG'.freeze
|
25
|
+
|
26
|
+
WORKERS = {
|
27
|
+
blocking: Workers::BlockingWorker,
|
28
|
+
multiplexing: Workers::MultiplexingWorker
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
DEFAULT_MODIFIER = :mod1
|
32
|
+
INPUT_MASK = SUBSTRUCTURE_REDIRECT_MASK
|
33
|
+
ROOT_MASK = PROPERTY_CHANGE_MASK |
|
34
|
+
SUBSTRUCTURE_REDIRECT_MASK |
|
35
|
+
SUBSTRUCTURE_NOTIFY_MASK |
|
36
|
+
STRUCTURE_NOTIFY_MASK
|
37
|
+
|
38
|
+
extend Forwardable
|
39
|
+
def_delegators :@manager, :on_configure, :on_manage, :on_unmanage, :on_change
|
40
|
+
def_delegator :@logger, :info, :log
|
41
|
+
def_delegator :@logger, :error, :log_error
|
42
|
+
|
43
|
+
def initialize(layout, &block)
|
44
|
+
@layout = layout
|
45
|
+
@display = Display.new
|
46
|
+
@logger = Logger.new($stdout).tap do |o|
|
47
|
+
o.level = ENV.key?(LOGGER_DEBUG_ENV) ? Logger::DEBUG : LOGGER_LEVEL
|
48
|
+
o.formatter = LOGGER_FORMATER
|
49
|
+
end
|
50
|
+
@manager = Manager.new(@logger)
|
51
|
+
@action_handler = ActionHandler.new(self, @manager, layout)
|
52
|
+
@keys = {}
|
53
|
+
|
54
|
+
@manager.on_manage do |client|
|
55
|
+
@display.listen_events client.window, PROPERTY_CHANGE_MASK
|
56
|
+
end
|
57
|
+
|
58
|
+
return unless block_given?
|
59
|
+
if block.arity == 1 then yield self else instance_eval &block end
|
60
|
+
end
|
61
|
+
|
62
|
+
def modifier(mod = nil)
|
63
|
+
return (@modifier or DEFAULT_MODIFIER) unless mod
|
64
|
+
@modifier = mod
|
65
|
+
end
|
66
|
+
|
67
|
+
def key(key, mod = nil, &block)
|
68
|
+
mod_mask = KEY_MODIFIERS[modifier]
|
69
|
+
mod_mask |= KEY_MODIFIERS[mod] if mod
|
70
|
+
@keys[[key, mod_mask]] = block
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_init(&block)
|
74
|
+
@on_init = block
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_expose(&block)
|
78
|
+
@on_expose = block
|
79
|
+
end
|
80
|
+
|
81
|
+
def worker(*args, **options)
|
82
|
+
if args.any?
|
83
|
+
@worker = WORKERS[args.first].new(@display, @logger, options)
|
84
|
+
else
|
85
|
+
@worker ||= Workers::BlockingWorker.new(@display, @logger)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def request_quit!
|
90
|
+
@quit_requested = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def run
|
94
|
+
connect
|
95
|
+
@on_init.call @display
|
96
|
+
grab_keys
|
97
|
+
@display.root.mask = ROOT_MASK
|
98
|
+
worker.setup.each_event do |e|
|
99
|
+
process_event e
|
100
|
+
break if quit_requested?
|
101
|
+
end
|
102
|
+
disconnect
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def modifier_mask(mod)
|
109
|
+
KEY_MODIFIERS[mod]
|
110
|
+
end
|
111
|
+
|
112
|
+
def quit_requested?
|
113
|
+
!!@quit_requested
|
114
|
+
end
|
115
|
+
|
116
|
+
def connect
|
117
|
+
@display.open
|
118
|
+
Display.on_error proc { fail OtherWMRunningError }
|
119
|
+
@display.listen_events INPUT_MASK
|
120
|
+
@display.sync false
|
121
|
+
Display.on_error proc { |*args| handle_error(*args) }
|
122
|
+
@display.sync false
|
123
|
+
end
|
124
|
+
|
125
|
+
def disconnect
|
126
|
+
@display.close
|
127
|
+
end
|
128
|
+
|
129
|
+
def grab_keys
|
130
|
+
@keys.each do |k, v|
|
131
|
+
key, mod = *k
|
132
|
+
key = key.to_s.gsub /\AXK_/, ''
|
133
|
+
@display.grab_key key, mod
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def process_event(event)
|
138
|
+
return unless respond_to? handler = event_handler_method(event), true
|
139
|
+
log_event event
|
140
|
+
send handler, event
|
141
|
+
end
|
142
|
+
|
143
|
+
def event_handler_method(event)
|
144
|
+
('handle_%s' % event.type).to_sym
|
145
|
+
end
|
146
|
+
|
147
|
+
def log_event(event)
|
148
|
+
fmt = '> Event %s'
|
149
|
+
complement = case event.type
|
150
|
+
when :destroy_notify, :expose, :key_press, :map_request, :property_notify,
|
151
|
+
:unmap_notify
|
152
|
+
"window: #{event.window}"
|
153
|
+
when :configure_request
|
154
|
+
'%s, above: #%d, detail: #%d, value_mask: #%d' % [
|
155
|
+
Geo.format_xgeometry(event.x, event.y, event.width, event.height),
|
156
|
+
event.above_window_id,
|
157
|
+
event.detail,
|
158
|
+
event.value_mask
|
159
|
+
]
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
log 'XEvent %s' % [event.type, complement].compact.join(' ')
|
165
|
+
end
|
166
|
+
|
167
|
+
def handle_configure_request(event)
|
168
|
+
@manager.configure event.window
|
169
|
+
end
|
170
|
+
|
171
|
+
def handle_destroy_notify(event)
|
172
|
+
@manager.destroy event.window
|
173
|
+
end
|
174
|
+
|
175
|
+
def handle_expose(event)
|
176
|
+
@on_expose.call event.window if @on_expose
|
177
|
+
end
|
178
|
+
|
179
|
+
def handle_key_press(event)
|
180
|
+
@action_handler.call @keys[["XK_#{event.key}".to_sym, event.modifier_mask]]
|
181
|
+
end
|
182
|
+
|
183
|
+
def handle_map_request(event)
|
184
|
+
@manager.map event.window
|
185
|
+
end
|
186
|
+
|
187
|
+
def handle_property_notify(event)
|
188
|
+
@manager.update_properties event.window
|
189
|
+
end
|
190
|
+
|
191
|
+
def handle_unmap_notify(event)
|
192
|
+
@manager.unmap event.window
|
193
|
+
end
|
194
|
+
|
195
|
+
def handle_error(req, resource_id, msg)
|
196
|
+
@logger.error "XERROR: #{resource_id} #{req} #{msg}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Uh
|
2
|
+
class WM
|
3
|
+
class ActionHandler
|
4
|
+
def initialize(wm, manager, layout)
|
5
|
+
@wm, @manager, @layout = wm, manager, layout
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(action)
|
9
|
+
instance_exec &action
|
10
|
+
end
|
11
|
+
|
12
|
+
def quit
|
13
|
+
@wm.log 'Exiting...'
|
14
|
+
@wm.request_quit!
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(command)
|
18
|
+
@wm.log "Spawn `#{command}`"
|
19
|
+
pid = spawn command, pgroup: true
|
20
|
+
Process.detach pid
|
21
|
+
rescue Errno::ENOENT => e
|
22
|
+
@wm.log_error "Spawn: #{e}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def log_layout
|
26
|
+
@wm.log "Layout:\n#{@layout.to_s.lines.map { |e| " #{e}" }.join.chomp}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def log_clients
|
30
|
+
@wm.log "Clients:\n#{@manager.to_s.lines.map { |e| " #{e}" }.join.chomp}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def log_separator
|
34
|
+
@wm.log '- ' * 24
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(m, *args, &block)
|
38
|
+
if respond_to? m
|
39
|
+
meth = layout_method m
|
40
|
+
@wm.log "#{@layout.class.name}##{meth} #{args.inspect}"
|
41
|
+
@layout.send(meth, *args)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to_missing?(m, *)
|
48
|
+
m.to_s =~ /\Alayout_/ || super
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def layout_method(m)
|
55
|
+
m.to_s.gsub(/\Alayout_/, 'handle_').to_sym
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|