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