uh-wm 0.0.2.pre → 0.0.2
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 +4 -4
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +15 -0
- data/Gemfile +5 -0
- data/Guardfile +12 -0
- data/LICENSE +30 -0
- data/README.md +68 -0
- data/Rakefile +40 -0
- data/bin/uhwm +5 -0
- data/config/cucumber.yaml +1 -0
- data/features/actions/execute.feature +9 -0
- data/features/actions/layout_delegation.feature +31 -0
- data/features/actions/quit.feature +9 -0
- data/features/cli/debug.feature +5 -0
- data/features/cli/layout.feature +15 -0
- data/features/cli/require.feature +5 -0
- data/features/cli/run_control.feature +9 -0
- data/features/cli/usage.feature +11 -0
- data/features/cli/verbose.feature +5 -0
- data/features/cli/version.feature +6 -0
- data/features/cli/worker.feature +9 -0
- data/features/layout/manage.feature +12 -0
- data/features/layout/protocol.feature +24 -0
- data/features/layout/unmanage.feature +10 -0
- data/features/manager/check_other_wm.feature +8 -0
- data/features/manager/input_events.feature +8 -0
- data/features/manager/manage.feature +14 -0
- data/features/manager/unmanage.feature +13 -0
- data/features/manager/x_errors.feature +17 -0
- data/features/run_control/evaluation.feature +18 -0
- data/features/run_control/key.feature +33 -0
- data/features/run_control/modifier.feature +10 -0
- data/features/run_control/worker.feature +9 -0
- data/features/session/connection.feature +5 -0
- data/features/session/termination.feature +13 -0
- data/features/steps/filesystem_steps.rb +3 -0
- data/features/steps/output_steps.rb +44 -0
- data/features/steps/run_control_steps.rb +3 -0
- data/features/steps/run_steps.rb +41 -0
- data/features/steps/x_steps.rb +53 -0
- data/features/support/env.rb +33 -0
- data/lib/uh/wm.rb +8 -0
- data/lib/uh/wm/actions_handler.rb +46 -0
- data/lib/uh/wm/cli.rb +20 -13
- data/lib/uh/wm/client.rb +64 -0
- data/lib/uh/wm/dispatcher.rb +3 -1
- data/lib/uh/wm/env.rb +15 -9
- data/lib/uh/wm/env_logging.rb +8 -0
- data/lib/uh/wm/logger_formatter.rb +16 -0
- data/lib/uh/wm/manager.rb +96 -14
- data/lib/uh/wm/run_control.rb +8 -3
- data/lib/uh/wm/runner.rb +82 -14
- data/lib/uh/wm/testing/acceptance_helpers.rb +140 -18
- data/lib/uh/wm/version.rb +1 -1
- data/lib/uh/wm/workers.rb +21 -0
- data/lib/uh/wm/workers/base.rb +27 -0
- data/lib/uh/wm/workers/blocking.rb +11 -0
- data/lib/uh/wm/workers/mux.rb +18 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/exit_helpers.rb +6 -0
- data/spec/support/filesystem_helpers.rb +11 -0
- data/spec/uh/wm/actions_handler_spec.rb +30 -0
- data/spec/uh/wm/cli_spec.rb +214 -0
- data/spec/uh/wm/client_spec.rb +133 -0
- data/spec/uh/wm/dispatcher_spec.rb +76 -0
- data/spec/uh/wm/env_spec.rb +145 -0
- data/spec/uh/wm/manager_spec.rb +355 -0
- data/spec/uh/wm/run_control_spec.rb +102 -0
- data/spec/uh/wm/runner_spec.rb +186 -0
- data/uh-wm.gemspec +25 -0
- metadata +112 -9
data/lib/uh/wm/client.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Uh
|
2
|
+
module WM
|
3
|
+
class Client
|
4
|
+
attr_reader :window
|
5
|
+
attr_accessor :geo, :unmap_count
|
6
|
+
|
7
|
+
def initialize window, geo = nil
|
8
|
+
@window = window
|
9
|
+
@geo = geo
|
10
|
+
@visible = false
|
11
|
+
@unmap_count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"<#{name}> (#{wclass}) #{@geo} win: #{@window}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def visible?
|
19
|
+
@visible
|
20
|
+
end
|
21
|
+
|
22
|
+
def hidden?
|
23
|
+
not visible?
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
@wname ||= @window.name
|
28
|
+
end
|
29
|
+
|
30
|
+
def wclass
|
31
|
+
@wclass ||= @window.wclass
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure
|
35
|
+
@window.configure @geo
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def moveresize
|
40
|
+
@window.moveresize @geo
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def show
|
45
|
+
@window.map
|
46
|
+
@visible = true
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def hide
|
51
|
+
@window.unmap
|
52
|
+
@visible = false
|
53
|
+
@unmap_count += 1
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def focus
|
58
|
+
@window.raise
|
59
|
+
@window.focus
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/uh/wm/dispatcher.rb
CHANGED
data/lib/uh/wm/env.rb
CHANGED
@@ -3,29 +3,33 @@ module Uh
|
|
3
3
|
class Env
|
4
4
|
RC_PATH = '~/.uhwmrc.rb'.freeze
|
5
5
|
|
6
|
-
MODIFIER
|
7
|
-
KEYBINDS
|
8
|
-
q: proc { quit }
|
6
|
+
MODIFIER = :mod1
|
7
|
+
KEYBINDS = {
|
8
|
+
[:q, :shift] => proc { quit }
|
9
9
|
}.freeze
|
10
|
+
WORKER = :block
|
10
11
|
|
11
12
|
LOGGER_LEVEL = Logger::WARN
|
12
13
|
LOGGER_LEVEL_VERBOSE = Logger::INFO
|
13
14
|
LOGGER_LEVEL_DEBUG = Logger::DEBUG
|
14
|
-
LOGGER_LEVEL_STRINGS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN]
|
15
|
+
LOGGER_LEVEL_STRINGS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
|
15
16
|
|
16
17
|
extend Forwardable
|
17
|
-
def_delegator
|
18
|
-
def_delegator
|
19
|
-
def_delegator
|
18
|
+
def_delegator :logger, :info, :log
|
19
|
+
def_delegator :logger, :error, :log_error
|
20
|
+
def_delegator :logger, :debug, :log_debug
|
21
|
+
def_delegators :@output, :print, :puts
|
20
22
|
|
21
23
|
attr_reader :output, :keybinds
|
22
|
-
attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier
|
24
|
+
attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier,
|
25
|
+
:worker
|
23
26
|
|
24
27
|
def initialize output
|
25
28
|
@output = output
|
26
29
|
@rc_path = RC_PATH
|
27
30
|
@modifier = MODIFIER
|
28
31
|
@keybinds = KEYBINDS.dup
|
32
|
+
@worker = :block
|
29
33
|
end
|
30
34
|
|
31
35
|
def verbose?
|
@@ -48,7 +52,9 @@ module Uh
|
|
48
52
|
def logger
|
49
53
|
@logger ||= Logger.new(@output).tap do |o|
|
50
54
|
o.level = debug? ? LOGGER_LEVEL_DEBUG :
|
51
|
-
verbose? ? LOGGER_LEVEL_VERBOSE :
|
55
|
+
verbose? ? LOGGER_LEVEL_VERBOSE :
|
56
|
+
LOGGER_LEVEL
|
57
|
+
o.formatter = LoggerFormatter.new
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Uh
|
2
|
+
module WM
|
3
|
+
class LoggerFormatter
|
4
|
+
FORMAT_STR = "%s.%03i %s: %s\n".freeze
|
5
|
+
|
6
|
+
def call severity, datetime, progname, message
|
7
|
+
FORMAT_STR % [
|
8
|
+
datetime.strftime('%FT%T'),
|
9
|
+
datetime.usec / 1000,
|
10
|
+
severity[0..0],
|
11
|
+
message
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/uh/wm/manager.rb
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
module Uh
|
2
2
|
module WM
|
3
3
|
class Manager
|
4
|
-
INPUT_MASK
|
4
|
+
INPUT_MASK = Events::SUBSTRUCTURE_REDIRECT_MASK
|
5
|
+
ROOT_MASK = Events::PROPERTY_CHANGE_MASK |
|
6
|
+
Events::SUBSTRUCTURE_REDIRECT_MASK |
|
7
|
+
Events::SUBSTRUCTURE_NOTIFY_MASK |
|
8
|
+
Events::STRUCTURE_NOTIFY_MASK
|
9
|
+
DEFAULT_GEO = Geo.new(0, 0, 320, 240).freeze
|
5
10
|
|
6
|
-
attr_reader :modifier, :display
|
11
|
+
attr_reader :modifier, :display, :clients
|
7
12
|
|
8
13
|
def initialize events, modifier, display = Display.new
|
9
14
|
@events = events
|
10
15
|
@modifier = modifier
|
11
16
|
@display = display
|
17
|
+
@clients = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_io
|
21
|
+
IO.new(@display.fileno)
|
12
22
|
end
|
13
23
|
|
14
24
|
def connect
|
15
25
|
@events.emit :connecting, args: @display
|
16
26
|
@display.open
|
17
|
-
|
18
|
-
@display.listen_events INPUT_MASK
|
19
|
-
@display.sync false
|
27
|
+
check_other_wm!
|
20
28
|
Display.on_error { |*args| handle_error *args }
|
21
29
|
@display.sync false
|
22
30
|
@events.emit :connected, args: @display
|
31
|
+
@display.root.mask = ROOT_MASK
|
32
|
+
end
|
33
|
+
|
34
|
+
def disconnect
|
35
|
+
@display.close
|
36
|
+
@events.emit :disconnected
|
37
|
+
end
|
38
|
+
|
39
|
+
def flush
|
40
|
+
@display.flush
|
23
41
|
end
|
24
42
|
|
25
43
|
def grab_key keysym, mod = nil
|
@@ -28,25 +46,89 @@ module Uh
|
|
28
46
|
@display.grab_key keysym.to_s, mod_mask
|
29
47
|
end
|
30
48
|
|
49
|
+
def configure window
|
50
|
+
if client = client_for(window)
|
51
|
+
client.configure
|
52
|
+
else
|
53
|
+
geo = @events.emit :configure, args: window
|
54
|
+
window.configure_event geo ? geo : DEFAULT_GEO
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def map window
|
59
|
+
return if window.override_redirect? || client_for(window)
|
60
|
+
@clients << client = Client.new(window)
|
61
|
+
@events.emit :manage, args: client
|
62
|
+
end
|
63
|
+
|
64
|
+
def unmap window
|
65
|
+
return unless client = client_for(window)
|
66
|
+
if client.unmap_count > 0
|
67
|
+
client.unmap_count -= 1
|
68
|
+
else
|
69
|
+
@clients.delete client
|
70
|
+
@events.emit :unmanage, args: client
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def destroy window
|
75
|
+
return unless client = client_for(window)
|
76
|
+
@clients.delete client
|
77
|
+
@events.emit :unmanage, args: client
|
78
|
+
end
|
79
|
+
|
80
|
+
def handle_next_event
|
81
|
+
handle @display.next_event
|
82
|
+
end
|
83
|
+
|
31
84
|
def handle_pending_events
|
32
|
-
|
85
|
+
handle_next_event while @display.pending?
|
33
86
|
end
|
34
87
|
|
35
88
|
def handle event
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
[event.key.to_sym, :shift] :
|
40
|
-
event.key.to_sym
|
41
|
-
@events.emit :key, *key_selector
|
42
|
-
end
|
89
|
+
@events.emit :xevent, args: event
|
90
|
+
return unless respond_to? handler = "handle_#{event.type}".to_sym, true
|
91
|
+
send handler, event
|
43
92
|
end
|
44
93
|
|
45
94
|
|
46
95
|
private
|
47
96
|
|
48
97
|
def handle_error *args
|
49
|
-
@
|
98
|
+
@events.emit :xerror, args: args
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_key_press event
|
102
|
+
key_selector = event.modifier_mask & KEY_MODIFIERS[:shift] == 1 ?
|
103
|
+
[event.key.to_sym, :shift] :
|
104
|
+
event.key.to_sym
|
105
|
+
@events.emit :key, *key_selector
|
106
|
+
end
|
107
|
+
|
108
|
+
def handle_configure_request event
|
109
|
+
configure event.window
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_destroy_notify event
|
113
|
+
destroy event.window
|
114
|
+
end
|
115
|
+
|
116
|
+
def handle_map_request event
|
117
|
+
map event.window
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_unmap_notify event
|
121
|
+
unmap event.window
|
122
|
+
end
|
123
|
+
|
124
|
+
def client_for window
|
125
|
+
@clients.find { |e| e.window == window }
|
126
|
+
end
|
127
|
+
|
128
|
+
def check_other_wm!
|
129
|
+
Display.on_error { fail OtherWMRunningError }
|
130
|
+
@display.listen_events INPUT_MASK
|
131
|
+
@display.sync false
|
50
132
|
end
|
51
133
|
end
|
52
134
|
end
|
data/lib/uh/wm/run_control.rb
CHANGED
@@ -28,14 +28,19 @@ module Uh
|
|
28
28
|
@env.modifier = mod
|
29
29
|
end
|
30
30
|
|
31
|
-
def key
|
32
|
-
@env.keybinds[translate_keysym
|
31
|
+
def key *keysyms, &block
|
32
|
+
@env.keybinds[translate_keysym *keysyms] = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def worker type, **options
|
36
|
+
@env.worker = [type, options]
|
33
37
|
end
|
34
38
|
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
|
-
def translate_keysym keysym
|
42
|
+
def translate_keysym keysym, modifier = nil
|
43
|
+
return [translate_keysym(keysym)[0].to_sym, modifier] if modifier
|
39
44
|
translate_key = keysym.to_s.downcase.to_sym
|
40
45
|
translated_keysym = KEYSYM_TRANSLATIONS.key?(translate_key) ?
|
41
46
|
KEYSYM_TRANSLATIONS[translate_key] :
|
data/lib/uh/wm/runner.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Uh
|
2
2
|
module WM
|
3
3
|
class Runner
|
4
|
+
include EnvLogging
|
5
|
+
|
4
6
|
class << self
|
5
7
|
def run env, **options
|
6
8
|
runner = new env, **options
|
@@ -8,6 +10,7 @@ module Uh
|
|
8
10
|
runner.register_event_hooks
|
9
11
|
runner.connect_manager
|
10
12
|
runner.run_until { runner.stopped? }
|
13
|
+
runner.terminate
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
@@ -41,10 +44,9 @@ module Uh
|
|
41
44
|
end
|
42
45
|
|
43
46
|
def register_event_hooks
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
register_key_bindings_hooks
|
47
|
+
%w[runner manager layout keybinds]
|
48
|
+
.map { |e| "register_#{e}_hooks".to_sym }
|
49
|
+
.each { |e| send e }
|
48
50
|
end
|
49
51
|
|
50
52
|
def connect_manager
|
@@ -54,8 +56,31 @@ module Uh
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
59
|
+
def worker
|
60
|
+
@worker ||= Workers.build(*(@env.worker)).tap do |w|
|
61
|
+
w.on_read do
|
62
|
+
@manager.handle_pending_events
|
63
|
+
end
|
64
|
+
w.on_read_next do
|
65
|
+
@manager.handle_next_event
|
66
|
+
end
|
67
|
+
w.on_timeout do |*args|
|
68
|
+
log_debug "Worker timeout: #{args.inspect}"
|
69
|
+
log_debug 'Flushing X output buffer'
|
70
|
+
@manager.flush
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
57
75
|
def run_until &block
|
58
|
-
|
76
|
+
worker.watch @manager
|
77
|
+
log "Working events with `#{worker.class}'"
|
78
|
+
worker.work_events until block.call
|
79
|
+
end
|
80
|
+
|
81
|
+
def terminate
|
82
|
+
log "Terminating..."
|
83
|
+
manager.disconnect
|
59
84
|
end
|
60
85
|
|
61
86
|
|
@@ -66,25 +91,68 @@ module Uh
|
|
66
91
|
end
|
67
92
|
|
68
93
|
def register_manager_hooks
|
69
|
-
@events.on
|
70
|
-
|
94
|
+
@events.on :connecting do |display|
|
95
|
+
log_debug "Connecting to X server on `#{display}'"
|
71
96
|
end
|
72
|
-
@events.on
|
73
|
-
|
97
|
+
@events.on :connected do |display|
|
98
|
+
log "Connected to X server on `#{display}'"
|
74
99
|
end
|
100
|
+
@events.on(:disconnected) { log "Disconnected from X server" }
|
101
|
+
@events.on(:xevent) { |event| XEventLogger.new(env).log_event event }
|
102
|
+
@events.on(:xerror) { |*error| XEventLogger.new(env).log_xerror *error }
|
75
103
|
end
|
76
104
|
|
77
|
-
def
|
78
|
-
@events.on
|
105
|
+
def register_layout_hooks
|
106
|
+
@events.on :connected do |display|
|
107
|
+
log "Registering layout `#{layout.class}'"
|
79
108
|
layout.register display
|
80
109
|
end
|
110
|
+
@events.on :configure do |window|
|
111
|
+
log "Configuring window: #{window}"
|
112
|
+
layout.suggest_geo
|
113
|
+
end
|
114
|
+
@events.on :manage do |client|
|
115
|
+
log "Managing client #{client}"
|
116
|
+
layout << client
|
117
|
+
end
|
118
|
+
@events.on :unmanage do |client|
|
119
|
+
log "Unmanaging client #{client}"
|
120
|
+
layout.remove client
|
121
|
+
end
|
81
122
|
end
|
82
123
|
|
83
|
-
def
|
124
|
+
def register_keybinds_hooks
|
84
125
|
@env.keybinds.each do |keysym, code|
|
85
|
-
@events.on
|
86
|
-
|
126
|
+
@events.on(:key, *keysym) { @actions.evaluate code }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
class XEventLogger
|
132
|
+
include EnvLogging
|
133
|
+
|
134
|
+
def initialize env
|
135
|
+
@env = env
|
136
|
+
end
|
137
|
+
|
138
|
+
def log_event xev
|
139
|
+
complement = case xev.type
|
140
|
+
when :key_press
|
141
|
+
"window: #{xev.window} key: #{xev.key} mask: #{xev.modifier_mask}"
|
142
|
+
when :map_request
|
143
|
+
"window: #{xev.window}"
|
87
144
|
end
|
145
|
+
|
146
|
+
log_debug [
|
147
|
+
'XEvent',
|
148
|
+
xev.type,
|
149
|
+
xev.send_event ? 'SENT' : nil,
|
150
|
+
complement
|
151
|
+
].compact.join ' '
|
152
|
+
end
|
153
|
+
|
154
|
+
def log_xerror req, resource_id, msg
|
155
|
+
log_error "XERROR: #{resource_id} #{req} #{msg}"
|
88
156
|
end
|
89
157
|
end
|
90
158
|
end
|