uh 0.1.1

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