uh 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|