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.
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
@@ -0,0 +1,15 @@
1
+ module Uh
2
+ class Display
3
+ def create_subwindow(geo)
4
+ root.create_sub geo
5
+ end
6
+
7
+ def font
8
+ @font ||= query_font
9
+ end
10
+
11
+ def pending?
12
+ pending > 0
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Uh
2
+ module Drawable
3
+ def copy(window)
4
+ _copy window.id, width, height
5
+ end
6
+ end
7
+ end
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
@@ -0,0 +1,7 @@
1
+ module Uh
2
+ class Font
3
+ def height
4
+ ascent + descent
5
+ end
6
+ end
7
+ end
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
@@ -0,0 +1,5 @@
1
+ module Uh
2
+ class Pixmap
3
+ include Drawable
4
+ end
5
+ end
data/lib/uh/screen.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Uh
2
+ class Screen
3
+ def geo
4
+ @geo ||= Geo.new(x, y, width, height)
5
+ end
6
+ end
7
+ end
data/lib/uh/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Uh
2
+ VERSION = '0.1.1'.freeze
3
+ end
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