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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +15 -0
  5. data/Gemfile +5 -0
  6. data/Guardfile +12 -0
  7. data/LICENSE +30 -0
  8. data/README.md +68 -0
  9. data/Rakefile +40 -0
  10. data/bin/uhwm +5 -0
  11. data/config/cucumber.yaml +1 -0
  12. data/features/actions/execute.feature +9 -0
  13. data/features/actions/layout_delegation.feature +31 -0
  14. data/features/actions/quit.feature +9 -0
  15. data/features/cli/debug.feature +5 -0
  16. data/features/cli/layout.feature +15 -0
  17. data/features/cli/require.feature +5 -0
  18. data/features/cli/run_control.feature +9 -0
  19. data/features/cli/usage.feature +11 -0
  20. data/features/cli/verbose.feature +5 -0
  21. data/features/cli/version.feature +6 -0
  22. data/features/cli/worker.feature +9 -0
  23. data/features/layout/manage.feature +12 -0
  24. data/features/layout/protocol.feature +24 -0
  25. data/features/layout/unmanage.feature +10 -0
  26. data/features/manager/check_other_wm.feature +8 -0
  27. data/features/manager/input_events.feature +8 -0
  28. data/features/manager/manage.feature +14 -0
  29. data/features/manager/unmanage.feature +13 -0
  30. data/features/manager/x_errors.feature +17 -0
  31. data/features/run_control/evaluation.feature +18 -0
  32. data/features/run_control/key.feature +33 -0
  33. data/features/run_control/modifier.feature +10 -0
  34. data/features/run_control/worker.feature +9 -0
  35. data/features/session/connection.feature +5 -0
  36. data/features/session/termination.feature +13 -0
  37. data/features/steps/filesystem_steps.rb +3 -0
  38. data/features/steps/output_steps.rb +44 -0
  39. data/features/steps/run_control_steps.rb +3 -0
  40. data/features/steps/run_steps.rb +41 -0
  41. data/features/steps/x_steps.rb +53 -0
  42. data/features/support/env.rb +33 -0
  43. data/lib/uh/wm.rb +8 -0
  44. data/lib/uh/wm/actions_handler.rb +46 -0
  45. data/lib/uh/wm/cli.rb +20 -13
  46. data/lib/uh/wm/client.rb +64 -0
  47. data/lib/uh/wm/dispatcher.rb +3 -1
  48. data/lib/uh/wm/env.rb +15 -9
  49. data/lib/uh/wm/env_logging.rb +8 -0
  50. data/lib/uh/wm/logger_formatter.rb +16 -0
  51. data/lib/uh/wm/manager.rb +96 -14
  52. data/lib/uh/wm/run_control.rb +8 -3
  53. data/lib/uh/wm/runner.rb +82 -14
  54. data/lib/uh/wm/testing/acceptance_helpers.rb +140 -18
  55. data/lib/uh/wm/version.rb +1 -1
  56. data/lib/uh/wm/workers.rb +21 -0
  57. data/lib/uh/wm/workers/base.rb +27 -0
  58. data/lib/uh/wm/workers/blocking.rb +11 -0
  59. data/lib/uh/wm/workers/mux.rb +18 -0
  60. data/spec/spec_helper.rb +26 -0
  61. data/spec/support/exit_helpers.rb +6 -0
  62. data/spec/support/filesystem_helpers.rb +11 -0
  63. data/spec/uh/wm/actions_handler_spec.rb +30 -0
  64. data/spec/uh/wm/cli_spec.rb +214 -0
  65. data/spec/uh/wm/client_spec.rb +133 -0
  66. data/spec/uh/wm/dispatcher_spec.rb +76 -0
  67. data/spec/uh/wm/env_spec.rb +145 -0
  68. data/spec/uh/wm/manager_spec.rb +355 -0
  69. data/spec/uh/wm/run_control_spec.rb +102 -0
  70. data/spec/uh/wm/runner_spec.rb +186 -0
  71. data/uh-wm.gemspec +25 -0
  72. metadata +112 -9
@@ -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
@@ -16,7 +16,9 @@ module Uh
16
16
  end
17
17
 
18
18
  def emit *key, args: []
19
- @hooks[translate_key key].each { |e| e.call *args }
19
+ value = nil
20
+ @hooks[translate_key key].each { |e| value = e.call *args }
21
+ value
20
22
  end
21
23
 
22
24
 
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 = :mod1
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 :logger, :info, :log
18
- def_delegator :logger, :debug, :log_debug
19
- def_delegator :@output, :print
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 : LOGGER_LEVEL
55
+ verbose? ? LOGGER_LEVEL_VERBOSE :
56
+ LOGGER_LEVEL
57
+ o.formatter = LoggerFormatter.new
52
58
  end
53
59
  end
54
60
 
@@ -0,0 +1,8 @@
1
+ module Uh
2
+ module WM
3
+ module EnvLogging
4
+ extend Forwardable
5
+ def_delegators :@env, :log, :log_error, :log_debug
6
+ end
7
+ end
8
+ end
@@ -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 = Events::SUBSTRUCTURE_REDIRECT_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
- Display.on_error { fail OtherWMRunningError }
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
- handle @display.next_event while @display.pending?
85
+ handle_next_event while @display.pending?
33
86
  end
34
87
 
35
88
  def handle event
36
- case event.type
37
- when :key_press
38
- key_selector = event.modifier_mask & KEY_MODIFIERS[:shift] == 1 ?
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
- @dispatcher.emit :error, args: args
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
@@ -28,14 +28,19 @@ module Uh
28
28
  @env.modifier = mod
29
29
  end
30
30
 
31
- def key keysym, &block
32
- @env.keybinds[translate_keysym keysym] = block
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
- register_runner_hooks
45
- register_manager_hooks
46
- register_layout_event_hooks
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
- manager.handle_pending_events until block.call
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(:connecting) do |display|
70
- @env.log_debug "Connecting to X server on `#{display}'"
94
+ @events.on :connecting do |display|
95
+ log_debug "Connecting to X server on `#{display}'"
71
96
  end
72
- @events.on(:connected) do |display|
73
- @env.log "Connected to X server on `#{display}'"
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 register_layout_event_hooks
78
- @events.on(:connected) do |display|
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 register_key_bindings_hooks
124
+ def register_keybinds_hooks
84
125
  @env.keybinds.each do |keysym, code|
85
- @events.on :key, *keysym do
86
- @actions.evaluate code
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