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.
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