Compare commits
136 Commits
travis-not
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d9b4ac6083 | ||
|
d24f25bbbe | ||
|
2731f9b3a3 | ||
|
4bc513010e | ||
|
8a490e155f | ||
|
1cc1f98ad2 | ||
|
10ad5d9a76 | ||
|
eed9cdf0c0 | ||
|
97abd402b1 | ||
|
f26e16fe6e | ||
|
4050d52af7 | ||
|
92dedd16e7 | ||
|
85e5abac8d | ||
|
6a4a15e9db | ||
|
27448abfc6 | ||
|
a6b2e98146 | ||
|
1d5ee7cab7 | ||
|
d9ce2eb120 | ||
|
c9ab16c847 | ||
|
265bb1d626 | ||
|
c394fee2a6 | ||
|
01d9bdb3b3 | ||
|
ace86cf0e3 | ||
|
daa028e794 | ||
|
258812013b | ||
|
66413b6ccb | ||
|
1332be5f39 | ||
|
9199efaeda | ||
|
7d7db62f0d | ||
|
ed478c51b2 | ||
|
56bf7fb03a | ||
|
ba28838441 | ||
|
a649a2b450 | ||
|
e8647a938b | ||
|
1e2bf97762 | ||
|
42beda3f50 | ||
|
c8106d2ef5 | ||
|
341e6fd4d2 | ||
|
f9d905e498 | ||
|
7c2712924a | ||
|
70fe9bdb03 | ||
|
bf6f08e848 | ||
|
7c06b52664 | ||
|
85f799174a | ||
|
4af83c181a | ||
|
3a338e2bb7 | ||
|
f5bd8872c5 | ||
|
cd06f99e9f | ||
|
e68d3fd6a3 | ||
|
6cfed91653 | ||
|
eaa1d279dc | ||
|
4e4b15871e | ||
|
ddf875eab2 | ||
|
009d5dbedd | ||
|
eb9c8d16db | ||
|
44a5be6bd9 | ||
|
a421b496c0 | ||
|
accbebe97c | ||
|
95245e1178 | ||
|
0601fe0560 | ||
|
b1a601918e | ||
|
933bcdfcd8 | ||
|
03a6c323ac | ||
|
fb4af963eb | ||
|
b58a143aff | ||
|
b8e31e1981 | ||
|
a7fe5d767f | ||
|
b3a82fcf92 | ||
|
730527b836 | ||
|
47bc9c9da2 | ||
|
2cad5da620 | ||
|
3776abf405 | ||
|
8ed77a0460 | ||
|
8afb8961f0 | ||
|
ff2fc8d70d | ||
|
ae3d78497f | ||
|
cc66b6a760 | ||
|
e8dacfa4ac | ||
|
1d9571766b | ||
|
7ee70b666b | ||
|
4e2ec5f6ec | ||
|
6346eb5ee6 | ||
|
c7bd6ea28a | ||
|
ad67456c52 | ||
|
3fd8658adf | ||
|
a4b6127ec8 | ||
|
0b7c3e9128 | ||
|
2cca771ad0 | ||
|
e2c46d096c | ||
|
cc401c8733 | ||
|
04d999e1b0 | ||
|
3849471a1b | ||
|
29f2b4725d | ||
|
e97dcea87a | ||
|
31a326e7eb | ||
|
0423d6df9d | ||
|
4c9342c2f2 | ||
|
42751993a2 | ||
|
a15af6f0ab | ||
|
a6edac962b | ||
|
38a90ab0af | ||
|
a052140921 | ||
|
09d7b6ebb5 | ||
|
7722909c22 | ||
|
b156665236 | ||
|
9a4f2727d2 | ||
|
f2c97499ce | ||
|
a7941d51cc | ||
|
c71fed4108 | ||
|
f15fa116c9 | ||
|
d8994dc165 | ||
|
77226594fa | ||
|
30cb15b5d3 | ||
|
84d92798a0 | ||
|
1f7b2269c7 | ||
|
7fc695f258 | ||
|
c69a600ca4 | ||
|
7c440e28d2 | ||
|
e9baae2962 | ||
|
ae2beeac20 | ||
|
bdbeca399a | ||
|
036ca1f989 | ||
|
02861a63d4 | ||
|
c51d1525ee | ||
|
d47e7de6d0 | ||
|
5e057ebab3 | ||
|
5e18dbae95 | ||
|
6c7b01c9aa | ||
|
231e72df42 | ||
|
26bac5aea9 | ||
|
20cdec5fce | ||
|
68cec67402 | ||
|
78dffde6c9 | ||
|
82a4161cc2 | ||
|
2894783b57 | ||
|
a9466a49f0 |
@ -5,8 +5,8 @@ rvm:
|
||||
- ruby-head
|
||||
- 2.1
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq twm x11-utils xdotool
|
||||
- travis_retry sudo apt-get update -qq
|
||||
- travis_retry sudo apt-get install -qq twm x11-utils xdotool
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
|
33
README.md
33
README.md
@ -1,17 +1,40 @@
|
||||
uh-wm
|
||||
=====
|
||||
|
||||
Xlib Window Manager prototype.
|
||||
|
||||
[![Version ][badge-version-img]][badge-version-uri]
|
||||
[![Build status ][badge-build-img]][badge-build-uri]
|
||||
[![Code Climate ][badge-cclimate-img]][badge-cclimate-uri]
|
||||
|
||||
|
||||
uh-wm is a minimalistic tiling and stacking window manager for X. It
|
||||
shares some similarities with dwm and wmii, but is written in ruby so
|
||||
you can configure and extend features directly with ruby code.
|
||||
|
||||
The layout strategy is interchangeable, the default one being the
|
||||
`uh-layout` ruby gem. A layout is a simple ruby object responding to
|
||||
specific messages.
|
||||
|
||||
Main features:
|
||||
|
||||
* Xinerama support;
|
||||
* multiple event handling strategy: blocking or multiplexing
|
||||
with `select()`;
|
||||
* configuration with a run control file (ruby DSL);
|
||||
* key bindings with user defined code as callback;
|
||||
* configurable modifier key;
|
||||
* support user-defined layout strategies;
|
||||
* program execution;
|
||||
* no re-parenting (therefore, no window decoration either);
|
||||
* no grabbing of the modifier key alone;
|
||||
* no mouse handling;
|
||||
* no EWMH support;
|
||||
* very limited ICCCM support.
|
||||
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
### Installation (requires ruby ~> 2.1 and rubygems)
|
||||
### Installation (requires ruby ~> 2.1 with rubygems)
|
||||
|
||||
```
|
||||
$ gem install uh-wm
|
||||
@ -24,13 +47,15 @@ $ gem install uh-wm
|
||||
Usage: uhwm [options]
|
||||
|
||||
options:
|
||||
-h, --help print this message
|
||||
-v, --verbose enable verbose mode
|
||||
-d, --debug enable debug mode
|
||||
-f, --run-control PATH specify alternate run control file
|
||||
-r, --require PATH require ruby feature
|
||||
-l, --layout LAYOUT specify layout
|
||||
-w, --worker WORKER specify worker
|
||||
|
||||
-h, --help print this message
|
||||
-V, --version print version
|
||||
```
|
||||
|
||||
|
||||
|
12
Rakefile
12
Rakefile
@ -10,13 +10,19 @@ XEPHYR_SCREENS_XINERAMA =
|
||||
|
||||
task default: %i[features spec]
|
||||
|
||||
Cucumber::Rake::Task.new(:features)
|
||||
Cucumber::Rake::Task.new :features do |t|
|
||||
t.profile = 'quiet' if ENV.key? 'TRAVIS'
|
||||
end
|
||||
|
||||
RSpec::Core::RakeTask.new
|
||||
RSpec::Core::RakeTask.new do |t|
|
||||
t.rspec_opts = '--format progress' if ENV.key? 'TRAVIS'
|
||||
end
|
||||
|
||||
desc 'Run uhwm in a Xephyr X server'
|
||||
task :run do
|
||||
uhwm_args = ARGV.slice_after('--').to_a.last || %w[-d]
|
||||
uhwm_args = ARGV.include?('--') ?
|
||||
ARGV.slice_after('--').to_a.last :
|
||||
%w[-d]
|
||||
Tempfile.create('uhwm_xinitrc') do |xinitrc|
|
||||
xinitrc.write <<-eoh
|
||||
[ -f $HOME/.Xdefaults ] && xrdb $HOME/.Xdefaults
|
||||
|
@ -1 +1,2 @@
|
||||
default: --require features/support --require features/steps
|
||||
default: --require features/support --require features/steps --no-source
|
||||
quiet: --format progress
|
||||
|
11
features/actions/kill.feature
Normal file
11
features/actions/kill.feature
Normal file
@ -0,0 +1,11 @@
|
||||
Feature: `kill' action keyword
|
||||
|
||||
@icccm_window
|
||||
Scenario: kills current client
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:f) { kill_current }
|
||||
"""
|
||||
And an ICCCM compliant window is mapped
|
||||
When I press the alt+f keys
|
||||
Then the ICCCM window must be unmapped by the manager
|
28
features/actions/layout_delegation.feature
Normal file
28
features/actions/layout_delegation.feature
Normal file
@ -0,0 +1,28 @@
|
||||
Feature: `layout_*' action keywords
|
||||
|
||||
Scenario: delegates messages matching `layout_*' to `layout_handle_*'
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class Layout
|
||||
def register _; end
|
||||
|
||||
def handle_some_action arg
|
||||
puts "testing_layout_action_#{arg}"
|
||||
end
|
||||
end
|
||||
"""
|
||||
And a run control file with:
|
||||
"""
|
||||
key(:f) { layout_some_action 'with_arg' }
|
||||
"""
|
||||
And uhwm is running with options -v -r./layout.rb -l Layout
|
||||
When I press the alt+f keys
|
||||
Then the output must contain "testing_layout_action_with_arg"
|
||||
|
||||
Scenario: logs an error about unimplemented messages
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:f) { layout_unknown_action }
|
||||
"""
|
||||
When I press the alt+f keys
|
||||
Then the output must match /layout.+no.+implem.+handle_unknown_action/i
|
9
features/actions/log_separator.feature
Normal file
9
features/actions/log_separator.feature
Normal file
@ -0,0 +1,9 @@
|
||||
Feature: `log_separator' action keyword
|
||||
|
||||
Scenario: logs a separator
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:f) { log_separator }
|
||||
"""
|
||||
When I press the alt+f keys
|
||||
Then the output must contain "- - - - - - - - - - - - - - - - - - - - - - -"
|
@ -4,8 +4,14 @@ Feature: layout CLI option
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class MyLayout
|
||||
def register _; end
|
||||
def register *_
|
||||
puts 'testing_cli_layout'
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run uhwm with option -v -r./layout -l MyLayout
|
||||
Then the output must match /layout.+mylayout/i
|
||||
When I run uhwm with options -r./layout -l MyLayout
|
||||
Then the output must contain "testing_cli_layout"
|
||||
|
||||
Scenario: resolves layout class from the root namespace
|
||||
When I run uhwm with option -l Layout
|
||||
Then the output must contain "uninitialized constant Layout"
|
||||
|
@ -1,5 +1,9 @@
|
||||
Feature: require CLI option
|
||||
|
||||
Scenario: requires a ruby feature
|
||||
When I run uhwm with option -v -r abbrev
|
||||
Then the output must match /load.+abbrev.+ruby feature/i
|
||||
Given a file named my_feature.rb with:
|
||||
"""
|
||||
puts 'testing_feature_load'
|
||||
"""
|
||||
When I run uhwm with option -r ./my_feature.rb
|
||||
Then the output must contain "testing_feature_load"
|
||||
|
@ -3,7 +3,7 @@ Feature: run control file path CLI option
|
||||
Scenario: specifies run control file path
|
||||
Given a file named uhwmrc.rb with:
|
||||
"""
|
||||
puts 'run control evaluation'
|
||||
puts 'testing_run_control'
|
||||
"""
|
||||
When I run uhwm with option -f uhwmrc.rb
|
||||
Then the output must contain "run control evaluation"
|
||||
Then the output must contain "testing_run_control"
|
||||
|
@ -1,9 +1,9 @@
|
||||
Feature: worker CLI option
|
||||
|
||||
Scenario: uses the blocking worker when `block' is given
|
||||
When I run uhwm with option -v -w block
|
||||
When I run uhwm with options -v -w block
|
||||
Then the output must match /work.+event.+block/i
|
||||
|
||||
Scenario: uses the multiplexing worker when `mux' is given
|
||||
When I run uhwm with option -v -w mux
|
||||
When I run uhwm with options -v -w mux
|
||||
Then the output must match /work.+event.+mux/i
|
||||
|
@ -1,16 +1,12 @@
|
||||
Feature: layout client management
|
||||
|
||||
Scenario: sends the #<< message when telling the layout to manage a client
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class Layout
|
||||
def register *_; end
|
||||
Background:
|
||||
Given uhwm is running
|
||||
|
||||
def << client
|
||||
puts client
|
||||
end
|
||||
end
|
||||
"""
|
||||
And uhwm is running with options -v -r./layout -l Layout
|
||||
Scenario: maps new client window
|
||||
When a window requests to be mapped
|
||||
Then the output must contain the window name
|
||||
Then the window must be mapped
|
||||
|
||||
Scenario: focuses new client window
|
||||
When a window is mapped
|
||||
Then the window must be focused
|
||||
|
56
features/layout/protocol.feature
Normal file
56
features/layout/protocol.feature
Normal file
@ -0,0 +1,56 @@
|
||||
Feature: layout protocol
|
||||
|
||||
Background:
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class Layout
|
||||
def register display
|
||||
puts display
|
||||
display.create_subwindow(Uh::Geo.new(0, 0, 640, 16)).tap do |o|
|
||||
o.show
|
||||
end
|
||||
end
|
||||
|
||||
def << client
|
||||
puts "testing_#<<_#{client.name}"
|
||||
client.show
|
||||
end
|
||||
|
||||
def remove client
|
||||
puts "testing_#remove_#{client.name}"
|
||||
end
|
||||
|
||||
def update client
|
||||
puts "testing_#update_#{client.name}"
|
||||
end
|
||||
|
||||
def expose window
|
||||
puts "testing_#expose_#{window.id}"
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
Scenario: tells the layout to register with #register message
|
||||
When I run uhwm with option -r./layout -l Layout
|
||||
Then the output must contain current display
|
||||
|
||||
Scenario: tells the layout to manage a client with #<< message
|
||||
Given uhwm is running with options -v -r./layout -l Layout
|
||||
When a window requests to be mapped
|
||||
Then the output must contain "testing_#<<_XClient/default"
|
||||
|
||||
Scenario: tells the layout to unmanage a client with #remove message
|
||||
Given uhwm is running with options -v -r./layout -l Layout
|
||||
And a window is mapped
|
||||
When the window is unmapped
|
||||
Then the output must contain "testing_#remove_XClient/default"
|
||||
|
||||
Scenario: tells the layout to update a changed client with #update message
|
||||
Given uhwm is running with options -v -r./layout -l Layout
|
||||
And a window is mapped
|
||||
When the window name changes to "testing_new_name"
|
||||
Then the output must contain "testing_#update_testing_new_name"
|
||||
|
||||
Scenario: tells the layout about an exposed window with #expose message
|
||||
Given I run uhwm with options -r./layout -l Layout
|
||||
Then the output must match /testing_#expose_\d+/
|
@ -1,13 +0,0 @@
|
||||
Feature: layout registration
|
||||
|
||||
Scenario: sends the #register message to the layout with the display
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class Layout
|
||||
def register display
|
||||
puts display
|
||||
end
|
||||
end
|
||||
"""
|
||||
When I run uhwm with option -r./layout -l Layout
|
||||
Then the output must contain current display
|
10
features/layout/unmanage.feature
Normal file
10
features/layout/unmanage.feature
Normal file
@ -0,0 +1,10 @@
|
||||
Feature: layout client unmanagement
|
||||
|
||||
Background:
|
||||
Given uhwm is running
|
||||
And a first window is mapped
|
||||
And a second window is mapped
|
||||
|
||||
Scenario: maps another window
|
||||
When the second window is unmapped
|
||||
Then the first window must be mapped
|
7
features/manager/change.feature
Normal file
7
features/manager/change.feature
Normal file
@ -0,0 +1,7 @@
|
||||
Feature: clients window properties updating
|
||||
|
||||
Scenario: logs when the window properties of a client change
|
||||
Given uhwm is running
|
||||
And a window is mapped
|
||||
When the window name changes to "testing_new_name"
|
||||
Then the output must match /updat.+testing_new_name/i
|
5
features/manager/expose.feature
Normal file
5
features/manager/expose.feature
Normal file
@ -0,0 +1,5 @@
|
||||
Feature: expose events handling
|
||||
|
||||
Scenario: logs when an expose event is handled
|
||||
Given uhwm is running
|
||||
Then the output must match /expos.+window.+\d+/i
|
14
features/manager/manage.feature
Normal file
14
features/manager/manage.feature
Normal file
@ -0,0 +1,14 @@
|
||||
Feature: manager client management
|
||||
|
||||
Background:
|
||||
Given uhwm is running
|
||||
|
||||
Scenario: logs when a new client is managed
|
||||
When a window requests to be mapped
|
||||
Then the output must match /manag.+xclient/i
|
||||
|
||||
Scenario: manages a given client only once
|
||||
When a window requests to be mapped 2 times
|
||||
And I quit uhwm
|
||||
Then the output must not match /manag.*\n.*manag/mi
|
||||
And the output must not match /xerror/i
|
13
features/manager/unmanage.feature
Normal file
13
features/manager/unmanage.feature
Normal file
@ -0,0 +1,13 @@
|
||||
Feature: manager client unmanagement
|
||||
|
||||
Background:
|
||||
Given uhwm is running
|
||||
And a window is mapped
|
||||
|
||||
Scenario: logs when a new client is unmanaged
|
||||
When the window is unmapped
|
||||
Then the output must match /unmanag.+xclient/i
|
||||
|
||||
Scenario: unmanages client on destroy notify X events
|
||||
When the window is destroyed
|
||||
Then the output must match /unmanag.+xclient/i
|
17
features/manager/x_errors.feature
Normal file
17
features/manager/x_errors.feature
Normal file
@ -0,0 +1,17 @@
|
||||
Feature: X errors logging
|
||||
|
||||
Scenario: logs error details
|
||||
Given a file named layout.rb with:
|
||||
"""
|
||||
class Layout
|
||||
def register _; end
|
||||
|
||||
# Focusing a client before mapping will force an error
|
||||
def << client
|
||||
client.focus
|
||||
end
|
||||
end
|
||||
"""
|
||||
And uhwm is running with options -v -r./layout -l Layout
|
||||
When a window requests to be mapped
|
||||
Then the output must match /x.*error.+x_setinputfocus.+/i
|
@ -1,18 +1,10 @@
|
||||
Feature: run control file evaluation
|
||||
|
||||
Scenario: evaluates the default run control file when present
|
||||
Given a run control file with:
|
||||
"""
|
||||
puts 'run control evaluation'
|
||||
"""
|
||||
When I start uhwm
|
||||
Then the output must contain "run control evaluation"
|
||||
|
||||
Scenario: reports run control code in backtrace on errors
|
||||
Given a run control file with:
|
||||
"""
|
||||
'no error on first line'
|
||||
fail 'fails on second line'
|
||||
fail 'testing_rc_failure'
|
||||
"""
|
||||
When I start uhwm
|
||||
Then the output must match /\.uhwmrc\.rb:2:.+fails on second line/
|
||||
Then the output must match /\.uhwmrc\.rb:2:.+testing_rc_failure/
|
||||
|
@ -3,23 +3,31 @@ Feature: `key' run control keyword
|
||||
Scenario: defines code to run when given key is pressed
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:f) { puts 'trigger f key code' }
|
||||
key(:f) { puts 'testing_rc_key' }
|
||||
"""
|
||||
When I press the alt+f keys
|
||||
Then the output must contain "trigger f key code"
|
||||
Then the output must contain "testing_rc_key"
|
||||
|
||||
Scenario: defines code to run when given keys are pressed
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:f, :shift) { puts 'testing_rc_key' }
|
||||
"""
|
||||
When I press the alt+shift+f keys
|
||||
Then the output must contain "testing_rc_key"
|
||||
|
||||
Scenario: translates common key names to their X equivalent
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:enter) { puts 'trigger return key code' }
|
||||
key(:enter) { puts 'testing_rc_key' }
|
||||
"""
|
||||
When I press the alt+Return keys
|
||||
Then the output must contain "trigger return key code"
|
||||
Then the output must contain "testing_rc_key"
|
||||
|
||||
Scenario: translates upcased key names to combination with shift key
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
key(:F) { puts 'trigger shift+f key code' }
|
||||
key(:F) { puts 'testing_rc_key' }
|
||||
"""
|
||||
When I press the alt+shift+f keys
|
||||
Then the output must contain "trigger shift+f key code"
|
||||
Then the output must contain "testing_rc_key"
|
||||
|
39
features/run_control/layout.feature
Normal file
39
features/run_control/layout.feature
Normal file
@ -0,0 +1,39 @@
|
||||
Feature: `layout' run control keyword
|
||||
|
||||
Background:
|
||||
Given a file named my_layout.rb with:
|
||||
"""
|
||||
class MyLayout
|
||||
def initialize **options
|
||||
@options = options
|
||||
end
|
||||
|
||||
def register *_
|
||||
puts "testing_rc_layout_#{@options.inspect}"
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
Scenario: configures a layout class
|
||||
Given a run control file with:
|
||||
"""
|
||||
layout MyLayout
|
||||
"""
|
||||
When I run uhwm with options -r./my_layout
|
||||
Then the output must contain "testing_rc_layout_{}"
|
||||
|
||||
Scenario: configures a layout class with options
|
||||
Given a run control file with:
|
||||
"""
|
||||
layout MyLayout, foo: :bar
|
||||
"""
|
||||
When I run uhwm with options -r./my_layout
|
||||
Then the output must contain "testing_rc_layout_{:foo=>:bar}"
|
||||
|
||||
Scenario: configures a layout instance
|
||||
Given a run control file with:
|
||||
"""
|
||||
layout MyLayout.new
|
||||
"""
|
||||
When I run uhwm with options -r./my_layout
|
||||
Then the output must contain "testing_rc_layout_{}"
|
@ -1,10 +1,9 @@
|
||||
Feature: `modifier' run control keyword
|
||||
|
||||
Scenario: configures the modifier key
|
||||
Given a run control file with:
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
modifier :ctrl
|
||||
"""
|
||||
And uhwm is running
|
||||
When I press the ctrl+q keys
|
||||
When I press the ctrl+shift+q keys
|
||||
Then uhwm must terminate successfully
|
||||
|
@ -1,9 +1,8 @@
|
||||
Feature: `worker' run control keyword
|
||||
|
||||
Scenario: configures the modifier key
|
||||
Given a run control file with:
|
||||
Given uhwm is running with this run control file:
|
||||
"""
|
||||
worker :mux
|
||||
"""
|
||||
And I start uhwm
|
||||
Then the output must match /work.+event.+mux/i
|
||||
|
@ -1,5 +0,0 @@
|
||||
Feature: connection to X server
|
||||
|
||||
Scenario: connects to X server
|
||||
When I start uhwm
|
||||
Then it must connect to X display
|
@ -9,5 +9,4 @@ Feature: program termination
|
||||
|
||||
Scenario: logs about termination
|
||||
When I tell uhwm to quit
|
||||
Then uhwm must terminate successfully
|
||||
And the output must match /terminat/i
|
||||
Then the output must match /terminat/i
|
||||
|
@ -16,7 +16,6 @@ options:
|
||||
end
|
||||
|
||||
Then /^the output must contain exactly the version$/ do
|
||||
#require File.expand_path('../lib/uh/wm/version', __FILE__)
|
||||
assert_exact_output "%s\n" % Uh::WM::VERSION, all_output
|
||||
end
|
||||
|
||||
@ -24,6 +23,21 @@ Then /^the output must match \/([^\/]+)\/([a-z]*)$/ do |pattern, options|
|
||||
uhwm_wait_output Regexp.new(pattern, options)
|
||||
end
|
||||
|
||||
Then /^the output must not match \/([^\/]+)\/([a-z]*)$/ do |pattern, options|
|
||||
expect(all_output).not_to match Regexp.new(pattern, options)
|
||||
end
|
||||
|
||||
Then /^the output must match \/([^\/]+)\/([a-z]*) at least (\d+) times$/ do
|
||||
|pattern, options, times|
|
||||
uhwm_wait_output Regexp.new(pattern, options), times.to_i
|
||||
end
|
||||
|
||||
Then /^the output must match \/([^\/]+)\/([a-z]*) exactly (\d+) times$/ do
|
||||
|pattern, options, times|
|
||||
scans = uhwm_wait_output Regexp.new(pattern, options)
|
||||
expect(scans.size).to eq times.to_i
|
||||
end
|
||||
|
||||
Then /^the output must contain:$/ do |content|
|
||||
uhwm_wait_output content.to_s
|
||||
end
|
||||
|
@ -24,7 +24,12 @@ When /^I run uhwm with options? (-.+)$/ do |options|
|
||||
end
|
||||
|
||||
When /^I tell uhwm to quit$/ do
|
||||
x_key 'alt+q'
|
||||
uhwm_request_quit
|
||||
end
|
||||
|
||||
When /^I quit uhwm$/ do
|
||||
uhwm_request_quit
|
||||
assert_exit_status 0
|
||||
end
|
||||
|
||||
Then /^the exit status must be (\d+)$/ do |exit_status|
|
||||
|
@ -1,12 +1,68 @@
|
||||
Given /^an ICCCM compliant window is mapped$/ do
|
||||
icccm_window_start
|
||||
timeout_until 'window not mapped after %d seconds' do
|
||||
x_window_map_state(icccm_window_name) == 'IsViewable'
|
||||
end
|
||||
end
|
||||
|
||||
Given /^a(?:\s(\w+))? window is mapped$/ do |ident|
|
||||
x_client(ident).map.sync
|
||||
timeout_until 'window not mapped after %d seconds' do
|
||||
x_window_map_state(x_client(ident).window_id) == 'IsViewable'
|
||||
end
|
||||
end
|
||||
|
||||
When /^I press the ([^ ]+) keys?$/ do |keys|
|
||||
x_key keys
|
||||
end
|
||||
|
||||
When /^a window requests to be mapped$/ do
|
||||
x_window_map
|
||||
When /^I press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
|
||||
times.to_i.times { x_key keys }
|
||||
end
|
||||
|
||||
Then /^it must connect to X display$/ do
|
||||
uhwm_wait_output 'Connected to'
|
||||
expect(x_socket_check uhwm_pid).to be true
|
||||
When /^I quickly press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
|
||||
x_key [keys] * times.to_i, delay: 0
|
||||
end
|
||||
|
||||
When /^a window requests to be mapped$/ do
|
||||
x_client.map.sync
|
||||
end
|
||||
|
||||
When /^a window requests to be mapped (\d+) times$/ do |times|
|
||||
x_client.map times: times.to_i
|
||||
end
|
||||
|
||||
When /^the(?:\s(\w+))? window is unmapped$/ do |ident|
|
||||
x_client(ident).unmap.sync
|
||||
timeout_until 'window not unmapped after %d seconds' do
|
||||
x_window_map_state(x_client(ident).window_id) == 'IsUnMapped'
|
||||
end
|
||||
end
|
||||
|
||||
When /^the window is destroyed$/ do
|
||||
x_client.destroy.sync
|
||||
end
|
||||
|
||||
When /^the window name changes to "([^"]+)"$/ do |name|
|
||||
x_client.window_name = name
|
||||
end
|
||||
|
||||
Then /^the(?:\s(\w+))? window must be mapped$/ do |ident|
|
||||
timeout_until 'window not mapped after %d seconds' do
|
||||
x_window_map_state(x_client(ident).window_id) == 'IsViewable'
|
||||
end
|
||||
end
|
||||
|
||||
Then /^the ICCCM window must be unmapped by the manager$/ do
|
||||
uhwm_wait_output /unmanag.+#{icccm_window_name}/i
|
||||
end
|
||||
|
||||
Then /^the window must be focused$/ do
|
||||
timeout_until 'window not focused after %d seconds' do
|
||||
x_focused_window_id == x_client.window_id
|
||||
end
|
||||
end
|
||||
|
||||
Then /^the input event mask must include (.+)$/ do |mask|
|
||||
expect(x_input_event_masks).to include mask
|
||||
end
|
||||
|
@ -28,6 +28,10 @@ Around '@other_wm_running' do |_, block|
|
||||
with_other_wm { block.call }
|
||||
end
|
||||
|
||||
After '@icccm_window' do
|
||||
icccm_window_ensure_stop
|
||||
end
|
||||
|
||||
if ENV.key? 'TRAVIS'
|
||||
ENV['UHWMTEST_TIMEOUT'] = 8.to_s
|
||||
end
|
||||
|
15
features/workers/block.feature
Normal file
15
features/workers/block.feature
Normal file
@ -0,0 +1,15 @@
|
||||
Feature: blocking worker
|
||||
|
||||
Scenario: processes initial events
|
||||
Given uhwm is running with options -d -w block
|
||||
Then the output must match /xevent/i at least 2 times
|
||||
|
||||
Scenario: processes generated events
|
||||
Given a run control file with:
|
||||
"""
|
||||
key(:f) { puts 'testing_worker_read' }
|
||||
"""
|
||||
And uhwm is running with options -v -w block
|
||||
When I quickly press the alt+f key 1024 times
|
||||
And I quit uhwm
|
||||
Then the output must match /(testing_worker_read)/ exactly 1024 times
|
15
features/workers/mux.feature
Normal file
15
features/workers/mux.feature
Normal file
@ -0,0 +1,15 @@
|
||||
Feature: multiplexing worker
|
||||
|
||||
Scenario: processes initial events
|
||||
Given uhwm is running with options -d -w mux
|
||||
Then the output must match /xevent/i at least 2 times
|
||||
|
||||
Scenario: processes generated events
|
||||
Given a run control file with:
|
||||
"""
|
||||
key(:f) { puts 'testing_worker_read' }
|
||||
"""
|
||||
And uhwm is running with options -d -w mux
|
||||
When I press the alt+f key 3 times
|
||||
And I quit uhwm
|
||||
Then the output must match /(testing_worker_read)/ exactly 3 times
|
@ -3,6 +3,8 @@ require 'logger'
|
||||
require 'optparse'
|
||||
require 'uh'
|
||||
|
||||
require 'uh/wm/env_logging'
|
||||
|
||||
require 'uh/wm/actions_handler'
|
||||
require 'uh/wm/cli'
|
||||
require 'uh/wm/client'
|
||||
|
@ -1,32 +1,75 @@
|
||||
module Uh
|
||||
module WM
|
||||
class ActionsHandler
|
||||
include EnvLogging
|
||||
|
||||
extend Forwardable
|
||||
def_delegator :@env, :layout
|
||||
|
||||
def initialize env, events
|
||||
@env, @events = env, events
|
||||
end
|
||||
|
||||
def evaluate code
|
||||
instance_eval &code
|
||||
def evaluate code = nil, &block
|
||||
if code
|
||||
instance_exec &code
|
||||
else
|
||||
instance_exec &block
|
||||
end
|
||||
end
|
||||
|
||||
def quit
|
||||
log 'Quit requested'
|
||||
@events.emit :quit
|
||||
end
|
||||
|
||||
def execute command
|
||||
@env.log "Execute: #{command}"
|
||||
log "Execute: #{command}"
|
||||
pid = fork do
|
||||
fork do
|
||||
Process.setsid
|
||||
begin
|
||||
exec command
|
||||
rescue Errno::ENOENT => e
|
||||
@env.log_error "ExecuteError: #{e}"
|
||||
log_error "ExecuteError: #{e}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Process.waitpid pid
|
||||
end
|
||||
|
||||
def kill_current
|
||||
layout.current_client.kill
|
||||
end
|
||||
|
||||
def log_separator
|
||||
log '- ' * 24
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
if respond_to? m
|
||||
meth = layout_method m
|
||||
log "#{layout.class.name}##{meth} #{args.inspect}"
|
||||
begin
|
||||
layout.send(meth, *args)
|
||||
rescue NoMethodError
|
||||
log_error "Layout does not implement `#{meth}'"
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(m, *)
|
||||
m.to_s =~ /\Alayout_/ || super
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def layout_method(m)
|
||||
m.to_s.gsub(/\Alayout_/, 'handle_').to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,8 @@ module Uh
|
||||
class CLI
|
||||
ArgumentError = Class.new(ArgumentError)
|
||||
|
||||
include EnvLogging
|
||||
|
||||
USAGE = "Usage: #{File.basename $0} [options]".freeze
|
||||
|
||||
EX_USAGE = 64
|
||||
@ -63,10 +65,10 @@ module Uh
|
||||
end
|
||||
opts.on '-r', '--require PATH', 'require ruby feature' do |feature|
|
||||
require feature
|
||||
@env.log "Loaded `#{feature}' ruby feature"
|
||||
log "Loaded `#{feature}' ruby feature"
|
||||
end
|
||||
opts.on '-l', '--layout LAYOUT', 'specify layout' do |layout|
|
||||
@env.layout_class = self.class.const_get layout.to_sym
|
||||
@env.layout_class = Object.const_get layout.to_sym
|
||||
end
|
||||
opts.on '-w', Workers.types, '--worker WORKER',
|
||||
'specify worker' do |worker|
|
||||
|
@ -1,8 +1,10 @@
|
||||
module Uh
|
||||
module WM
|
||||
class Client
|
||||
attr_reader :window, :unmap_count
|
||||
attr_accessor :geo
|
||||
include GeoAccessors
|
||||
|
||||
attr_reader :window
|
||||
attr_accessor :geo, :unmap_count
|
||||
|
||||
def initialize window, geo = nil
|
||||
@window = window
|
||||
@ -31,6 +33,16 @@ module Uh
|
||||
@wclass ||= @window.wclass
|
||||
end
|
||||
|
||||
def update_window_properties
|
||||
@wname = @window.name
|
||||
@wclass = @window.wclass
|
||||
end
|
||||
|
||||
def configure
|
||||
@window.configure @geo
|
||||
self
|
||||
end
|
||||
|
||||
def moveresize
|
||||
@window.moveresize @geo
|
||||
self
|
||||
@ -54,6 +66,20 @@ module Uh
|
||||
@window.focus
|
||||
self
|
||||
end
|
||||
|
||||
def kill
|
||||
if @window.icccm_wm_protocols.include? :WM_DELETE_WINDOW
|
||||
@window.icccm_wm_delete
|
||||
else
|
||||
@window.kill
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def kill!
|
||||
window.kill
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -16,7 +16,9 @@ module Uh
|
||||
end
|
||||
|
||||
def emit *key, args: []
|
||||
@hooks[translate_key key].each { |e| e.call *args }
|
||||
value = nil
|
||||
@hooks[translate_key key].each { |e| value = e.call *args }
|
||||
value
|
||||
end
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ module Uh
|
||||
|
||||
MODIFIER = :mod1
|
||||
KEYBINDS = {
|
||||
q: proc { quit }
|
||||
[:q, :shift] => proc { quit }
|
||||
}.freeze
|
||||
WORKER = :block
|
||||
|
||||
@ -21,8 +21,8 @@ module Uh
|
||||
def_delegators :@output, :print, :puts
|
||||
|
||||
attr_reader :output, :keybinds
|
||||
attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier,
|
||||
:worker
|
||||
attr_accessor :verbose, :debug, :rc_path, :modifier, :worker,
|
||||
:layout, :layout_class
|
||||
|
||||
def initialize output
|
||||
@output = output
|
||||
|
8
lib/uh/wm/env_logging.rb
Normal file
8
lib/uh/wm/env_logging.rb
Normal file
@ -0,0 +1,8 @@
|
||||
module Uh
|
||||
module WM
|
||||
module EnvLogging
|
||||
extend Forwardable
|
||||
def_delegators :@env, :log, :log_error, :log_debug
|
||||
end
|
||||
end
|
||||
end
|
@ -6,6 +6,7 @@ module Uh
|
||||
Events::SUBSTRUCTURE_REDIRECT_MASK |
|
||||
Events::SUBSTRUCTURE_NOTIFY_MASK |
|
||||
Events::STRUCTURE_NOTIFY_MASK
|
||||
DEFAULT_GEO = Geo.new(0, 0, 320, 240).freeze
|
||||
|
||||
attr_reader :modifier, :display, :clients
|
||||
|
||||
@ -45,6 +46,42 @@ module Uh
|
||||
@display.grab_key keysym.to_s, mod_mask
|
||||
end
|
||||
|
||||
def configure window
|
||||
if client = client_for(window)
|
||||
client.configure
|
||||
else
|
||||
geo = @events.emit :configure, args: window
|
||||
window.configure_event geo ? geo : DEFAULT_GEO
|
||||
end
|
||||
end
|
||||
|
||||
def map window
|
||||
return if window.override_redirect? || client_for(window)
|
||||
@clients << client = Client.new(window)
|
||||
@events.emit :manage, args: client
|
||||
@display.listen_events window, Events::PROPERTY_CHANGE_MASK
|
||||
end
|
||||
|
||||
def unmap window
|
||||
return unless client = client_for(window)
|
||||
if client.unmap_count > 0
|
||||
client.unmap_count -= 1
|
||||
else
|
||||
unmanage client
|
||||
end
|
||||
end
|
||||
|
||||
def destroy window
|
||||
return unless client = client_for(window)
|
||||
unmanage client
|
||||
end
|
||||
|
||||
def update_properties window
|
||||
return unless client = client_for(window)
|
||||
client.update_window_properties
|
||||
@events.emit :change, args: client
|
||||
end
|
||||
|
||||
def handle_next_event
|
||||
handle @display.next_event
|
||||
end
|
||||
@ -63,7 +100,7 @@ module Uh
|
||||
private
|
||||
|
||||
def handle_error *args
|
||||
@dispatcher.emit :error, args: args
|
||||
@events.emit :xerror, args: args
|
||||
end
|
||||
|
||||
def handle_key_press event
|
||||
@ -73,9 +110,37 @@ module Uh
|
||||
@events.emit :key, *key_selector
|
||||
end
|
||||
|
||||
def handle_configure_request event
|
||||
configure event.window
|
||||
end
|
||||
|
||||
def handle_destroy_notify event
|
||||
destroy event.window
|
||||
end
|
||||
|
||||
def handle_expose event
|
||||
@events.emit :expose, args: event.window
|
||||
end
|
||||
|
||||
def handle_map_request event
|
||||
@clients << client = Client.new(event.window)
|
||||
@events.emit :manage, args: client
|
||||
map event.window
|
||||
end
|
||||
|
||||
def handle_property_notify event
|
||||
update_properties event.window
|
||||
end
|
||||
|
||||
def handle_unmap_notify event
|
||||
unmap event.window
|
||||
end
|
||||
|
||||
def client_for window
|
||||
@clients.find { |e| e.window == window }
|
||||
end
|
||||
|
||||
def unmanage client
|
||||
@clients.delete client
|
||||
@events.emit :unmanage, args: client
|
||||
end
|
||||
|
||||
def check_other_wm!
|
||||
|
@ -28,8 +28,20 @@ module Uh
|
||||
@env.modifier = mod
|
||||
end
|
||||
|
||||
def key keysym, &block
|
||||
@env.keybinds[translate_keysym keysym] = block
|
||||
def key *keysyms, &block
|
||||
@env.keybinds[translate_keysym *keysyms] = block
|
||||
end
|
||||
|
||||
def layout arg, **options
|
||||
if arg.is_a? Class
|
||||
if options.any?
|
||||
@env.layout = arg.new options
|
||||
else
|
||||
@env.layout_class = arg
|
||||
end
|
||||
else
|
||||
@env.layout = arg
|
||||
end
|
||||
end
|
||||
|
||||
def worker type, **options
|
||||
@ -39,7 +51,8 @@ module Uh
|
||||
|
||||
private
|
||||
|
||||
def translate_keysym keysym
|
||||
def translate_keysym keysym, modifier = nil
|
||||
return [translate_keysym(keysym)[0].to_sym, modifier] if modifier
|
||||
translate_key = keysym.to_s.downcase.to_sym
|
||||
translated_keysym = KEYSYM_TRANSLATIONS.key?(translate_key) ?
|
||||
KEYSYM_TRANSLATIONS[translate_key] :
|
||||
|
@ -1,6 +1,8 @@
|
||||
module Uh
|
||||
module WM
|
||||
class Runner
|
||||
include EnvLogging
|
||||
|
||||
class << self
|
||||
def run env, **options
|
||||
runner = new env, **options
|
||||
@ -49,24 +51,17 @@ module Uh
|
||||
|
||||
def connect_manager
|
||||
manager.connect
|
||||
@env.keybinds.each do |keysym, _|
|
||||
manager.grab_key *keysym
|
||||
end
|
||||
@env.keybinds.each { |keysym, _| manager.grab_key *keysym }
|
||||
end
|
||||
|
||||
def worker
|
||||
@worker ||= Workers.build(*(@env.worker)).tap do |w|
|
||||
w.on_read do
|
||||
@env.log_debug 'Processing pending events'
|
||||
@manager.handle_pending_events
|
||||
end
|
||||
w.on_read_next do
|
||||
@env.log_debug 'Processing next event'
|
||||
@manager.handle_next_event
|
||||
end
|
||||
w.before_watch { @manager.handle_pending_events }
|
||||
w.on_read { @manager.handle_pending_events }
|
||||
w.on_read_next { @manager.handle_next_event }
|
||||
w.on_timeout do |*args|
|
||||
@env.log_debug "Worker timeout: #{args.inspect}"
|
||||
@env.log_debug 'Flushing X output buffer'
|
||||
log_debug "Worker timeout: #{args.inspect}"
|
||||
log_debug 'Flushing X output buffer'
|
||||
@manager.flush
|
||||
end
|
||||
end
|
||||
@ -74,12 +69,12 @@ module Uh
|
||||
|
||||
def run_until &block
|
||||
worker.watch @manager
|
||||
@env.log "Working events with `#{worker.class}'"
|
||||
log "Working events with `#{worker.class}'"
|
||||
worker.work_events until block.call
|
||||
end
|
||||
|
||||
def terminate
|
||||
@env.log "Terminating..."
|
||||
log "Terminating..."
|
||||
manager.disconnect
|
||||
end
|
||||
|
||||
@ -92,24 +87,41 @@ module Uh
|
||||
|
||||
def register_manager_hooks
|
||||
@events.on :connecting do |display|
|
||||
@env.log_debug "Connecting to X server on `#{display}'"
|
||||
log_debug "Connecting to X server on `#{display}'"
|
||||
end
|
||||
@events.on :connected do |display|
|
||||
@env.log "Connected to X server on `#{display}'"
|
||||
log "Connected to X server on `#{display}'"
|
||||
end
|
||||
@events.on(:disconnected) { @env.log "Disconnected from X server" }
|
||||
@events.on(:disconnected) { log "Disconnected from X server" }
|
||||
@events.on(:xevent) { |event| XEventLogger.new(env).log_event event }
|
||||
@events.on(:xerror) { |*error| XEventLogger.new(env).log_xerror *error }
|
||||
end
|
||||
|
||||
def register_layout_hooks
|
||||
@events.on :connected do |display|
|
||||
@env.log "Registering layout `#{layout.class}'"
|
||||
log "Registering layout `#{layout.class}'"
|
||||
layout.register display
|
||||
end
|
||||
@events.on :configure do |window|
|
||||
log "Configuring window: #{window}"
|
||||
layout.suggest_geo
|
||||
end
|
||||
@events.on :manage do |client|
|
||||
@env.log "Manage client #{client}"
|
||||
log "Managing client #{client}"
|
||||
layout << client
|
||||
end
|
||||
@events.on :unmanage do |client|
|
||||
log "Unmanaging client #{client}"
|
||||
layout.remove client
|
||||
end
|
||||
@events.on :change do |client|
|
||||
log "Updating client #{client}"
|
||||
layout.update client
|
||||
end
|
||||
@events.on :expose do |window|
|
||||
log "Exposing window: #{window}"
|
||||
layout.expose window
|
||||
end
|
||||
end
|
||||
|
||||
def register_keybinds_hooks
|
||||
@ -120,6 +132,8 @@ module Uh
|
||||
|
||||
|
||||
class XEventLogger
|
||||
include EnvLogging
|
||||
|
||||
def initialize env
|
||||
@env = env
|
||||
end
|
||||
@ -132,13 +146,17 @@ module Uh
|
||||
"window: #{xev.window}"
|
||||
end
|
||||
|
||||
@env.log_debug [
|
||||
log_debug [
|
||||
'XEvent',
|
||||
xev.type,
|
||||
xev.send_event ? 'SENT' : nil,
|
||||
complement
|
||||
].compact.join ' '
|
||||
end
|
||||
|
||||
def log_xerror req, resource_id, msg
|
||||
log_error "XERROR: #{resource_id} #{req} #{msg}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,45 +4,67 @@ module Uh
|
||||
module WM
|
||||
module Testing
|
||||
module AcceptanceHelpers
|
||||
TIMEOUT_DEFAULT = 2
|
||||
QUIT_KEYBINDING = 'alt+shift+q'.freeze
|
||||
LOG_READY = 'Working events'.freeze
|
||||
|
||||
def icccm_window_start
|
||||
@icccm_window = ChildProcess.build(*%w[xmessage window])
|
||||
@icccm_window.start
|
||||
end
|
||||
|
||||
def icccm_window_ensure_stop
|
||||
@icccm_window.stop
|
||||
end
|
||||
|
||||
def icccm_window_name
|
||||
'xmessage'
|
||||
end
|
||||
|
||||
def uhwm_run options = '-v'
|
||||
command = %w[uhwm]
|
||||
command << options if options
|
||||
@interactive = @process = run command.join ' '
|
||||
end
|
||||
|
||||
def uhwm
|
||||
@process
|
||||
end
|
||||
|
||||
def uhwm_request_quit
|
||||
x_key QUIT_KEYBINDING
|
||||
end
|
||||
|
||||
def uhwm_ensure_stop
|
||||
if @process
|
||||
x_key 'alt+q'
|
||||
x_key 'alt+shift+q'
|
||||
@process.terminate
|
||||
end
|
||||
end
|
||||
|
||||
def uhwm_pid
|
||||
@process.pid
|
||||
end
|
||||
|
||||
def uhwm_output
|
||||
@process.stdout
|
||||
end
|
||||
|
||||
def uhwm_wait_output message
|
||||
def uhwm_wait_output message, times = 1, value = nil
|
||||
output = -> { @process.stdout + @process.stderr }
|
||||
timeout_until do
|
||||
case message
|
||||
when Regexp then output.call =~ message
|
||||
when String then assert_partial_output_interactive message
|
||||
when Regexp then (value = output.call.scan(message)).size >= times
|
||||
when String then output.call.include? message
|
||||
end
|
||||
end
|
||||
value
|
||||
rescue TimeoutError => e
|
||||
fail [
|
||||
"expected `#{message}' not seen after #{e.timeout} seconds in:",
|
||||
" ```\n#{output.call.lines.map { |e| " #{e}" }.join} ```"
|
||||
].join "\n"
|
||||
fail <<-eoh
|
||||
expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
|
||||
```\n#{output.call.lines.map { |e| " #{e}" }.join} ```
|
||||
eoh
|
||||
end
|
||||
|
||||
def uhwm_wait_ready
|
||||
uhwm_wait_output LOG_READY
|
||||
end
|
||||
|
||||
def uhwm_run_wait_ready options = nil
|
||||
if options then uhwm_run options else uhwm_run end
|
||||
uhwm_wait_output 'Connected to'
|
||||
uhwm_wait_ready
|
||||
end
|
||||
|
||||
def with_other_wm
|
||||
@ -56,42 +78,48 @@ module Uh
|
||||
@other_wm
|
||||
end
|
||||
|
||||
def x_client
|
||||
@x_client ||= XClient.new
|
||||
end
|
||||
|
||||
def x_key key
|
||||
fail "cannot simulate X key `#{key}'" unless system "xdotool key #{key}"
|
||||
end
|
||||
|
||||
def x_socket_check pid
|
||||
case RbConfig::CONFIG['host_os']
|
||||
when /linux/
|
||||
`netstat -xp 2> /dev/null`.lines.grep /\s+#{pid}\/ruby/
|
||||
else
|
||||
`sockstat -u`.lines.grep /\s+ruby.+\s+#{pid}/
|
||||
end.any?
|
||||
end
|
||||
|
||||
def x_window_name
|
||||
@x_client.window_name
|
||||
end
|
||||
|
||||
def x_window_map
|
||||
x_client.map.sync
|
||||
def x_client ident = nil
|
||||
ident ||= :default
|
||||
@x_clients ||= {}
|
||||
@x_clients[ident] ||= XClient.new(ident)
|
||||
end
|
||||
|
||||
def x_clients_ensure_stop
|
||||
@x_client and @x_client.terminate
|
||||
@x_clients and @x_clients.any? and @x_clients.values.each &:terminate
|
||||
end
|
||||
|
||||
def x_focused_window_id
|
||||
Integer(`xdpyinfo`[/^focus:\s+window\s+(0x\h+)/, 1])
|
||||
end
|
||||
|
||||
def x_input_event_masks
|
||||
`xdpyinfo`[/current input event mask:\s+0x\h+([\w\s]+):/, 1]
|
||||
.split(/\s+/)
|
||||
.grep /Mask\z/
|
||||
end
|
||||
|
||||
def x_key *k, delay: 12
|
||||
k = k.join " key --delay #{delay} "
|
||||
fail "cannot simulate X key `#{k}'" unless system "xdotool key #{k}"
|
||||
end
|
||||
|
||||
def x_window_map_state window_selector
|
||||
select_args = case window_selector
|
||||
when Integer then "-id #{window_selector}"
|
||||
when String then "-name #{window_selector}"
|
||||
else fail ArgumentError,
|
||||
"not an Integer nor a String: `#{window_selector.inspect}'"
|
||||
end
|
||||
`xwininfo #{select_args} 2> /dev/null`[/Map State: (\w+)/, 1]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def timeout_until
|
||||
def timeout_until message = 'condition not met after %d seconds'
|
||||
timeout = ENV.key?('UHWMTEST_TIMEOUT') ?
|
||||
ENV['UHWMTEST_TIMEOUT'].to_i :
|
||||
1
|
||||
TIMEOUT_DEFAULT
|
||||
Timeout.timeout(timeout) do
|
||||
loop do
|
||||
break if yield
|
||||
@ -99,7 +127,7 @@ module Uh
|
||||
end
|
||||
end
|
||||
rescue Timeout::Error
|
||||
fail TimeoutError.new('execution expired', timeout)
|
||||
fail TimeoutError.new(message % timeout, timeout)
|
||||
end
|
||||
|
||||
|
||||
@ -115,8 +143,8 @@ module Uh
|
||||
class XClient
|
||||
attr_reader :name
|
||||
|
||||
def initialize
|
||||
@name = "#{self.class.name.split('::').last}/#{object_id}"
|
||||
def initialize name = object_id
|
||||
@name = "#{self.class.name.split('::').last}/#{name}"
|
||||
@geo = Geo.new(0, 0, 640, 480)
|
||||
@display = Display.new.tap { |o| o.open }
|
||||
end
|
||||
@ -130,19 +158,37 @@ module Uh
|
||||
end
|
||||
|
||||
def window
|
||||
@window ||= @display.create_window(@geo).tap do |o|
|
||||
o.name = @name
|
||||
end
|
||||
@window ||= @display.create_window(@geo).tap { |o| o.name = @name }
|
||||
end
|
||||
|
||||
def window_id
|
||||
@window.id
|
||||
end
|
||||
|
||||
def window_name
|
||||
@name
|
||||
end
|
||||
|
||||
def map
|
||||
def window_name= name
|
||||
@name = @window.name = name
|
||||
window.name
|
||||
end
|
||||
|
||||
def map times: 1
|
||||
times.times { window.map }
|
||||
window.map
|
||||
self
|
||||
end
|
||||
|
||||
def unmap
|
||||
window.unmap
|
||||
self
|
||||
end
|
||||
|
||||
def destroy
|
||||
window.destroy
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
module Uh
|
||||
module WM
|
||||
VERSION = '0.0.1'
|
||||
VERSION = '0.0.6'
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ module Uh
|
||||
module WM
|
||||
module Workers
|
||||
class Base
|
||||
CALLBACKS = %w[before_wait on_timeout on_read on_read_next].freeze
|
||||
CALLBACKS = %w[before_watch on_timeout on_read on_read_next].freeze
|
||||
|
||||
def initialize **options
|
||||
@ios = []
|
||||
|
@ -2,13 +2,15 @@ module Uh
|
||||
module WM
|
||||
module Workers
|
||||
class Mux < Base
|
||||
def initialize timeout: 1
|
||||
TIMEOUT_DEFAULT = 1
|
||||
|
||||
def initialize timeout: TIMEOUT_DEFAULT
|
||||
super
|
||||
@timeout = timeout
|
||||
end
|
||||
|
||||
def work_events
|
||||
@before_wait.call if @before_wait
|
||||
@before_watch.call if @before_watch
|
||||
if res = select(@ios, [], [], @timeout) then @on_read.call res
|
||||
else @on_timeout.call if @on_timeout end
|
||||
end
|
||||
|
@ -2,7 +2,11 @@ require 'headless'
|
||||
|
||||
require 'uh/wm'
|
||||
|
||||
Dir['spec/support/**/*.rb'].map { |e| require e.gsub 'spec/', '' }
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Factories
|
||||
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
28
spec/support/factories.rb
Normal file
28
spec/support/factories.rb
Normal file
@ -0,0 +1,28 @@
|
||||
module Factories
|
||||
def build_geo x = 0, y = 0, width = 640, height = 480
|
||||
Uh::Geo.new(x, y, width, height)
|
||||
end
|
||||
|
||||
def build_client window = mock_window
|
||||
Uh::WM::Client.new(window)
|
||||
end
|
||||
|
||||
def mock_event type = :xany, **options
|
||||
double 'event', type: type, **options
|
||||
end
|
||||
|
||||
def mock_event_key_press key, modifier_mask
|
||||
mock_event :key_press,
|
||||
key: 'f',
|
||||
modifier_mask: modifier_mask
|
||||
end
|
||||
|
||||
def mock_window override_redirect: false, icccm_wm_protocols: []
|
||||
instance_spy Uh::Window, 'window',
|
||||
to_s: 'wid',
|
||||
name: 'wname',
|
||||
wclass: 'wclass',
|
||||
override_redirect?: override_redirect,
|
||||
icccm_wm_protocols: icccm_wm_protocols
|
||||
end
|
||||
end
|
@ -6,10 +6,15 @@ module Uh
|
||||
subject(:actions) { described_class.new env, events }
|
||||
|
||||
describe '#evaluate' do
|
||||
it 'evaluates given code' do
|
||||
it 'evaluates code given as Proc argument' do
|
||||
expect { actions.evaluate proc { throw :action_code } }
|
||||
.to throw_symbol :action_code
|
||||
end
|
||||
|
||||
it 'evaluates code given as block' do
|
||||
expect { actions.evaluate { throw :action_code } }
|
||||
.to throw_symbol :action_code
|
||||
end
|
||||
end
|
||||
|
||||
describe '#quit' do
|
||||
@ -18,6 +23,35 @@ module Uh
|
||||
actions.quit
|
||||
end
|
||||
end
|
||||
|
||||
describe '#kill_current' do
|
||||
let(:client) { instance_spy Client }
|
||||
|
||||
context 'when layout has a client' do
|
||||
before do
|
||||
allow(actions.layout).to receive(:current_client) { client }
|
||||
end
|
||||
|
||||
it 'kills layout current client' do
|
||||
expect(client).to receive :kill
|
||||
actions.kill_current
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#log_separator' do
|
||||
it 'logs a separator' do
|
||||
expect(env).to receive(:log).with /(?:- ){20,}/
|
||||
actions.log_separator
|
||||
end
|
||||
end
|
||||
|
||||
describe '#layout_*' do
|
||||
it 'delegates messages to the layout with handle_ prefix' do
|
||||
expect(env.layout).to receive :handle_screen_sel
|
||||
actions.layout_screen_sel :succ
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,9 @@
|
||||
module Uh
|
||||
module WM
|
||||
RSpec.describe Client do
|
||||
let(:geo) { Geo.new(0, 0, 640, 480) }
|
||||
let(:window) do
|
||||
instance_spy Window, 'window', to_s: 'wid',
|
||||
name: 'wname', wclass: 'wclass'
|
||||
end
|
||||
let(:protocols) { [] }
|
||||
let(:window) { mock_window icccm_wm_protocols: protocols }
|
||||
let(:geo) { build_geo }
|
||||
subject(:client) { described_class.new window, geo }
|
||||
|
||||
it 'is not visible' do
|
||||
@ -33,7 +31,7 @@ module Uh
|
||||
expect(client.to_s).to include geo.to_s
|
||||
end
|
||||
|
||||
it 'includes window id' do
|
||||
it 'includes window string representation' do
|
||||
expect(client.to_s).to include 'wid'
|
||||
end
|
||||
end
|
||||
@ -50,6 +48,35 @@ module Uh
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_window_properties' do
|
||||
it 'updates the cached window name' do
|
||||
client.name
|
||||
allow(window).to receive(:name) { 'new name' }
|
||||
expect { client.update_window_properties }
|
||||
.to change { client.name }
|
||||
.from('wname').to 'new name'
|
||||
end
|
||||
|
||||
it 'updates the cached window class' do
|
||||
client.wclass
|
||||
allow(window).to receive(:wclass) { 'new class' }
|
||||
expect { client.update_window_properties }
|
||||
.to change { client.wclass }
|
||||
.from('wclass').to 'new class'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#configure' do
|
||||
it 'configures the window with client geo' do
|
||||
expect(window).to receive(:configure).with geo
|
||||
client.configure
|
||||
end
|
||||
|
||||
it 'returns self' do
|
||||
expect(client.configure).to be client
|
||||
end
|
||||
end
|
||||
|
||||
describe '#moveresize' do
|
||||
it 'moveresizes the window with client geo' do
|
||||
expect(window).to receive(:moveresize).with geo
|
||||
@ -117,6 +144,37 @@ module Uh
|
||||
expect(client.focus).to be client
|
||||
end
|
||||
end
|
||||
|
||||
describe '#kill' do
|
||||
it 'kills the window' do
|
||||
expect(window).to receive :kill
|
||||
client.kill
|
||||
end
|
||||
|
||||
it 'returns self' do
|
||||
expect(client.kill).to be client
|
||||
end
|
||||
|
||||
context 'when window supports icccm wm delete' do
|
||||
let(:protocols) { [:WM_DELETE_WINDOW] }
|
||||
|
||||
it 'icccm deletes the window' do
|
||||
expect(window).to receive :icccm_wm_delete
|
||||
client.kill
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#kill!' do
|
||||
it 'kills the window' do
|
||||
expect(window).to receive :kill
|
||||
client.kill!
|
||||
end
|
||||
|
||||
it 'returns self' do
|
||||
expect(client.kill!).to be client
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Uh
|
||||
module WM
|
||||
RSpec.describe Dispatcher do
|
||||
subject(:dispatcher) { described_class.new }
|
||||
subject(:dispatcher) { described_class.new }
|
||||
|
||||
describe '#[]' do
|
||||
context 'when given key for existing hook' do
|
||||
@ -45,6 +45,11 @@ module Uh
|
||||
expect { dispatcher.emit :hook_key }.to throw_symbol :hook_code
|
||||
end
|
||||
|
||||
it 'returns the value returned by a registered hook' do
|
||||
dispatcher.on(:hook_key) { :hook_code }
|
||||
expect(dispatcher.emit :hook_key).to eq :hook_code
|
||||
end
|
||||
|
||||
context 'when no hooks are registered for given key' do
|
||||
it 'does not call another hook' do
|
||||
dispatcher.on(:hook_key) { throw :hook_code }
|
||||
|
@ -25,8 +25,8 @@ module Uh
|
||||
expect(env.modifier).to eq :mod1
|
||||
end
|
||||
|
||||
it 'has defaults key bindings set' do
|
||||
expect(env.keybinds.keys).to eq %i[q]
|
||||
it 'has default key binding for quit set' do
|
||||
expect(env.keybinds.keys).to include [:q, :shift]
|
||||
end
|
||||
|
||||
it 'has the blocking worker by default' do
|
||||
@ -70,19 +70,27 @@ module Uh
|
||||
end
|
||||
|
||||
describe '#layout' do
|
||||
context 'when a layout class is set' do
|
||||
let(:some_layout) { Class.new }
|
||||
let(:some_layout_class) { Class.new }
|
||||
|
||||
before { env.layout_class = some_layout }
|
||||
it 'returns the default layout' do
|
||||
expect(env.layout).to be_an_instance_of ::Uh::Layout
|
||||
end
|
||||
|
||||
it 'returns a new instance of this layout class' do
|
||||
expect(env.layout).to be_an_instance_of some_layout
|
||||
context 'when a layout is set' do
|
||||
let(:some_layout) { some_layout_class.new }
|
||||
|
||||
before { env.layout = some_layout }
|
||||
|
||||
it 'returns the assigned layout' do
|
||||
expect(env.layout).to be some_layout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a layout class is not set' do
|
||||
it 'returns an instance of the default layout' do
|
||||
expect(env.layout).to be_an_instance_of ::Uh::Layout
|
||||
context 'when a layout class is set' do
|
||||
before { env.layout_class = some_layout_class }
|
||||
|
||||
it 'returns a new instance of this layout class' do
|
||||
expect(env.layout).to be_an_instance_of some_layout_class
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -129,7 +137,8 @@ module Uh
|
||||
|
||||
describe '#log_logger_level' do
|
||||
it 'logs the logger level' do
|
||||
expect(env.logger).to receive(:info).with /log.+(warn|info|debug).+level/i
|
||||
expect(env.logger)
|
||||
.to receive(:info).with /log.+(warn|info|debug).+level/i
|
||||
env.log_logger_level
|
||||
end
|
||||
end
|
||||
|
@ -2,15 +2,13 @@ module Uh
|
||||
module WM
|
||||
RSpec.describe Manager do
|
||||
let(:block) { proc { } }
|
||||
let(:window) { mock_window }
|
||||
let(:client) { build_client window }
|
||||
let(:events) { Dispatcher.new }
|
||||
let(:modifier) { :mod1 }
|
||||
let(:display) { Display.new }
|
||||
subject(:manager) { described_class.new events, modifier, display }
|
||||
|
||||
it 'has a new display' do
|
||||
expect(manager.display).to be_a Display
|
||||
end
|
||||
|
||||
it 'has no clients' do
|
||||
expect(manager.clients).to be_empty
|
||||
end
|
||||
@ -106,6 +104,174 @@ module Uh
|
||||
end
|
||||
end
|
||||
|
||||
describe '#configure' do
|
||||
context 'with new window' do
|
||||
it 'sends a configure event to the window with a default geo' do
|
||||
expect(window)
|
||||
.to receive(:configure_event).with(build_geo 0, 0, 320, 240)
|
||||
manager.configure window
|
||||
end
|
||||
|
||||
context 'when :configure event returns a geo' do
|
||||
it 'sends a configure event with geo returned by event' do
|
||||
geo = build_geo 0, 0, 42, 42
|
||||
events.on(:configure) { geo }
|
||||
expect(window).to receive(:configure_event).with geo
|
||||
manager.configure window
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with known window' do
|
||||
before { manager.clients << client }
|
||||
|
||||
it 'tells the client to configure' do
|
||||
expect(client).to receive :configure
|
||||
manager.configure window
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#map' do
|
||||
let(:display) { instance_spy Display }
|
||||
|
||||
it 'registers a new client wrapping the given window' do
|
||||
manager.map window
|
||||
expect(manager.clients[0])
|
||||
.to be_a(Client)
|
||||
.and have_attributes(window: window)
|
||||
end
|
||||
|
||||
it 'registers new client only once for a given window' do
|
||||
manager.map window
|
||||
expect { manager.map window }.not_to change { manager.clients }
|
||||
end
|
||||
|
||||
it 'ignores event when window has override redirect' do
|
||||
allow(window).to receive(:override_redirect?) { true }
|
||||
expect { manager.map window }.not_to change { manager.clients }
|
||||
end
|
||||
|
||||
it 'emits :manage event with the registered client' do
|
||||
events.on :manage, &block
|
||||
expect(block).to receive :call do |client|
|
||||
expect(client)
|
||||
.to be_a(Client)
|
||||
.and have_attributes(window: window)
|
||||
end
|
||||
manager.map window
|
||||
end
|
||||
|
||||
it 'listens for property notify events on given window' do
|
||||
expect(display)
|
||||
.to receive(:listen_events)
|
||||
.with window, Events::PROPERTY_CHANGE_MASK
|
||||
manager.map window
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unmap' do
|
||||
before { manager.clients << client }
|
||||
|
||||
context 'when client unmap count is 0 or less' do
|
||||
it 'preserves the client unmap count' do
|
||||
expect { manager.unmap window }.not_to change { client.unmap_count }
|
||||
end
|
||||
|
||||
it 'unregisters the client' do
|
||||
manager.unmap window
|
||||
expect(manager.clients).not_to include client
|
||||
end
|
||||
|
||||
it 'emits :unmanage event with the client' do
|
||||
events.on :unmanage, &block
|
||||
expect(block).to receive(:call).with client
|
||||
manager.unmap window
|
||||
end
|
||||
end
|
||||
|
||||
context 'when client unmap count is strictly positive' do
|
||||
before { client.unmap_count += 1 }
|
||||
|
||||
it 'does not unregister the client' do
|
||||
manager.unmap window
|
||||
expect(manager.clients).to include client
|
||||
end
|
||||
|
||||
it 'decrements the unmap count' do
|
||||
manager.unmap window
|
||||
expect(client.unmap_count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unknown window' do
|
||||
let(:unknown_window) { window.dup }
|
||||
|
||||
it 'does not change registered clients' do
|
||||
expect { manager.unmap unknown_window }
|
||||
.not_to change { manager.clients }
|
||||
end
|
||||
|
||||
it 'does not emit any event' do
|
||||
expect(events).not_to receive :emit
|
||||
manager.unmap unknown_window
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
before { manager.clients << client }
|
||||
|
||||
it 'unregisters the client' do
|
||||
manager.destroy window
|
||||
expect(manager.clients).not_to include client
|
||||
end
|
||||
|
||||
it 'emits :unmanage event with the client' do
|
||||
events.on :unmanage, &block
|
||||
expect(block).to receive(:call).with client
|
||||
manager.destroy window
|
||||
end
|
||||
|
||||
context 'with unknown window' do
|
||||
let(:unknown_window) { window.dup }
|
||||
|
||||
it 'does not change registered clients' do
|
||||
expect { manager.destroy unknown_window }
|
||||
.not_to change { manager.clients }
|
||||
end
|
||||
|
||||
it 'does not emit any event' do
|
||||
expect(events).not_to receive :emit
|
||||
manager.destroy unknown_window
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_properties' do
|
||||
context 'with known window' do
|
||||
before { manager.clients << client }
|
||||
|
||||
it 'tells the client to update its window properties' do
|
||||
expect(client).to receive :update_window_properties
|
||||
manager.update_properties window
|
||||
end
|
||||
|
||||
it 'emits :change event with the client' do
|
||||
events.on :change, &block
|
||||
expect(block).to receive(:call).with client
|
||||
manager.update_properties window
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unknown window' do
|
||||
it 'does not emit any event' do
|
||||
expect(events).not_to receive :emit
|
||||
manager.update_properties window
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#handle_next_event' do
|
||||
it 'handles the next available event on display' do
|
||||
event = double 'event'
|
||||
@ -116,7 +282,7 @@ module Uh
|
||||
end
|
||||
|
||||
describe '#handle_pending_events' do
|
||||
let(:event) { double 'event' }
|
||||
let(:event) { mock_event }
|
||||
|
||||
context 'when an event is pending on display' do
|
||||
before do
|
||||
@ -144,7 +310,7 @@ module Uh
|
||||
end
|
||||
|
||||
describe '#handle' do
|
||||
let(:event) { double 'event', type: :any }
|
||||
let(:event) { mock_event }
|
||||
|
||||
it 'emits :xevent event with the X event' do
|
||||
events.on :xevent, &block
|
||||
@ -152,13 +318,8 @@ module Uh
|
||||
end
|
||||
|
||||
context 'when key_press event is given' do
|
||||
let(:mod_mask) { KEY_MODIFIERS[modifier] }
|
||||
let(:event) do
|
||||
double 'event',
|
||||
type: :key_press,
|
||||
key: 'f',
|
||||
modifier_mask: mod_mask
|
||||
end
|
||||
let(:mod_mask) { KEY_MODIFIERS[modifier] }
|
||||
let(:event) { mock_event_key_press 'f', mod_mask }
|
||||
|
||||
it 'emits :key event with the corresponding key' do
|
||||
events.on(:key, :f) { throw :key_press_code }
|
||||
@ -175,23 +336,47 @@ module Uh
|
||||
end
|
||||
end
|
||||
|
||||
context 'when map_request event is given' do
|
||||
let(:event) { double 'event', type: :map_request, window: :window }
|
||||
context 'when configure request event is given' do
|
||||
let(:event) { mock_event :configure_request, window: :window }
|
||||
|
||||
it 'registers a new client wrapping the event window' do
|
||||
it 'configures the event window' do
|
||||
expect(manager).to receive(:configure).with :window
|
||||
manager.handle event
|
||||
expect(manager.clients[0])
|
||||
.to be_a(Client)
|
||||
.and have_attributes(window: :window)
|
||||
end
|
||||
end
|
||||
|
||||
it 'emits :manage event with the registered client' do
|
||||
events.on :manage, &block
|
||||
expect(block).to receive :call do |client|
|
||||
expect(client)
|
||||
.to be_a(Client)
|
||||
.and have_attributes(window: :window)
|
||||
end
|
||||
context 'when destroy_notify event is given' do
|
||||
let(:event) { mock_event :destroy_notify, window: :window }
|
||||
|
||||
it 'destroy the event window' do
|
||||
expect(manager).to receive(:destroy).with :window
|
||||
manager.handle event
|
||||
end
|
||||
end
|
||||
|
||||
context 'when map_request event is given' do
|
||||
let(:event) { mock_event :map_request, window: :window }
|
||||
|
||||
it 'maps the event window' do
|
||||
expect(manager).to receive(:map).with :window
|
||||
manager.handle event
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unmap_notify event is given' do
|
||||
let(:event) { mock_event :unmap_notify, window: :window }
|
||||
|
||||
it 'unmaps the event window' do
|
||||
expect(manager).to receive(:unmap).with :window
|
||||
manager.handle event
|
||||
end
|
||||
end
|
||||
|
||||
context 'when property_notify event is given' do
|
||||
let(:event) { mock_event :property_notify, window: :window }
|
||||
|
||||
it 'updates event window properties' do
|
||||
expect(manager).to receive(:update_properties).with :window
|
||||
manager.handle event
|
||||
end
|
||||
end
|
||||
|
@ -5,7 +5,6 @@ module Uh
|
||||
RSpec.describe RunControl do
|
||||
include FileSystemHelpers
|
||||
|
||||
let(:code) { :run_control_code }
|
||||
let(:env) { Env.new(StringIO.new) }
|
||||
subject(:rc) { described_class.new env }
|
||||
|
||||
@ -28,14 +27,6 @@ module Uh
|
||||
allow(described_class).to receive(:new) { rc }
|
||||
described_class.evaluate env
|
||||
end
|
||||
|
||||
context 'when run control file is not present' do
|
||||
before { env.rc_path = 'non_existent_rc_file.rb' }
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect { described_class.evaluate env }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#evaluate' do
|
||||
@ -65,6 +56,11 @@ module Uh
|
||||
expect(env.keybinds.keys).to include :f
|
||||
end
|
||||
|
||||
it 'registers a combined keys binding in the env' do
|
||||
rc.key :f, :shift, &code
|
||||
expect(env.keybinds.keys).to include %i[f shift]
|
||||
end
|
||||
|
||||
it 'registers given block with the key binding' do
|
||||
rc.key :f, &code
|
||||
expect(env.keybinds[:f].call).to eq :keybind_code
|
||||
@ -81,6 +77,31 @@ module Uh
|
||||
end
|
||||
end
|
||||
|
||||
describe '#layout' do
|
||||
context 'when given a class' do
|
||||
let(:layout_class) { Class.new }
|
||||
|
||||
it 'sets a layout class in the env' do
|
||||
rc.layout layout_class
|
||||
expect(env.layout_class).to be layout_class
|
||||
end
|
||||
|
||||
context 'when given options' do
|
||||
it 'instantiates the class with given options' do
|
||||
expect(layout_class).to receive(:new).with(foo: :bar)
|
||||
rc.layout layout_class, foo: :bar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given an object' do
|
||||
it 'sets a layout instance in the env' do
|
||||
rc.layout layout = Object.new
|
||||
expect(env.layout).to eq layout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#worker' do
|
||||
it 'sets the worker type in the env' do
|
||||
rc.worker :some_worker
|
||||
|
@ -1,6 +1,11 @@
|
||||
SomeLayout = Class.new do
|
||||
define_method(:register) { |*args| }
|
||||
define_method(:<<) { |*args| }
|
||||
include Factories
|
||||
|
||||
define_method(:register) { |*_| }
|
||||
define_method(:suggest_geo) { build_geo 0, 0, 42, 42 }
|
||||
define_method(:<<) { |*_| }
|
||||
define_method(:remove) { |*_| }
|
||||
define_method(:update) { |*_| }
|
||||
end
|
||||
|
||||
module Uh
|
||||
@ -14,10 +19,6 @@ module Uh
|
||||
env.rc_path = 'non_existent_run_control.rb'
|
||||
end
|
||||
|
||||
it 'has the env' do
|
||||
expect(runner.env).to be env
|
||||
end
|
||||
|
||||
it 'has a dispatcher' do
|
||||
expect(runner.events).to be_a Dispatcher
|
||||
end
|
||||
@ -70,39 +71,61 @@ module Uh
|
||||
describe '#register_event_hooks' do
|
||||
it 'registers quit event hook' do
|
||||
runner.register_event_hooks
|
||||
expect(runner).to receive(:stop!)
|
||||
expect(runner).to receive :stop!
|
||||
runner.events.emit :quit
|
||||
end
|
||||
|
||||
it 'registers layout hook for :connected event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:register).with :display
|
||||
runner.events.emit :connected, args: :display
|
||||
context 'layout hooks' do
|
||||
it 'registers for :connected event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:register).with :display
|
||||
runner.events.emit :connected, args: :display
|
||||
end
|
||||
|
||||
it 'registers for :configure event' do
|
||||
runner.register_event_hooks
|
||||
expect(runner.events.emit :configure, args: :window)
|
||||
.to eq build_geo 0, 0, 42, 42
|
||||
end
|
||||
|
||||
it 'registers for :manage event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:<<).with :client
|
||||
runner.events.emit :manage, args: :client
|
||||
end
|
||||
|
||||
it 'registers for :unmanage event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:remove).with :client
|
||||
runner.events.emit :unmanage, args: :client
|
||||
end
|
||||
|
||||
it 'registers for :change event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:update).with :client
|
||||
runner.events.emit :change, args: :client
|
||||
end
|
||||
end
|
||||
|
||||
it 'registers layout hook for :manage event' do
|
||||
runner.register_event_hooks
|
||||
expect(env.layout).to receive(:<<).with :window
|
||||
runner.events.emit :manage, args: :window
|
||||
end
|
||||
context 'keys hooks' do
|
||||
it 'registers for :key' do
|
||||
env.keybinds[:f] = -> { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.events[:key, :f]).not_to be_empty
|
||||
end
|
||||
|
||||
it 'registers key bindings event hooks' do
|
||||
env.keybinds[:f] = -> { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.events[:key, :f]).not_to be_empty
|
||||
end
|
||||
it 'registers for :key with combined key bindings' do
|
||||
env.keybinds[[:f, :shift]] = -> { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.events[:key, :f, :shift]).not_to be_empty
|
||||
end
|
||||
|
||||
it 'registers combined key bindings event hooks' do
|
||||
env.keybinds[[:f, :shift]] = -> { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.events[:key, :f, :shift]).not_to be_empty
|
||||
end
|
||||
|
||||
it 'registers key bindings code evaluation with the actions handler' do
|
||||
env.keybinds[:f] = code = proc { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.actions).to receive(:evaluate).with code
|
||||
runner.events.emit :key, :f
|
||||
it 'registers code evaluation with the actions handler' do
|
||||
env.keybinds[:f] = code = proc { }
|
||||
runner.register_event_hooks
|
||||
expect(runner.actions).to receive(:evaluate).with code
|
||||
runner.events.emit :key, :f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -127,17 +150,22 @@ module Uh
|
||||
expect(runner.worker).to respond_to :work_events
|
||||
end
|
||||
|
||||
it 'setups the read callback to tell manager to handle pending events' do
|
||||
it 'setups the before_watch callback' do
|
||||
expect(runner.manager).to receive :handle_pending_events
|
||||
runner.worker.before_watch.call
|
||||
end
|
||||
|
||||
it 'setups the read callback' do
|
||||
expect(runner.manager).to receive :handle_pending_events
|
||||
runner.worker.on_read.call
|
||||
end
|
||||
|
||||
it 'setups the read_next callback to tell manager to handle next event' do
|
||||
it 'setups the read_next callback' do
|
||||
expect(runner.manager).to receive :handle_next_event
|
||||
runner.worker.on_read_next.call
|
||||
end
|
||||
|
||||
it 'setups the timeout callback to tell manager to flush the output' do
|
||||
it 'setups the timeout callback' do
|
||||
expect(runner.manager).to receive :flush
|
||||
runner.worker.on_timeout.call
|
||||
end
|
||||
|
@ -3,8 +3,9 @@ require File.expand_path('../lib/uh/wm/version', __FILE__)
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'uh-wm'
|
||||
s.version = Uh::WM::VERSION.dup
|
||||
s.summary = 'uh window manager'
|
||||
s.summary = 'minimalistic tiling and stacking window manager for X'
|
||||
s.description = s.name
|
||||
s.license = 'BSD-3-Clause'
|
||||
s.homepage = 'https://rubygems.org/gems/uh-wm'
|
||||
|
||||
s.authors = 'Thibault Jouan'
|
||||
@ -14,8 +15,8 @@ Gem::Specification.new do |s|
|
||||
s.test_files = s.files.grep /\A(spec|features)\//
|
||||
s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
|
||||
|
||||
s.add_dependency 'uh', '2.0.0.pre1'
|
||||
s.add_dependency 'uh-layout', '0.2.0.pre'
|
||||
s.add_dependency 'uh', '2.0.0'
|
||||
s.add_dependency 'uh-layout', '~> 0.4.0'
|
||||
|
||||
s.add_development_dependency 'aruba', '~> 0.6'
|
||||
s.add_development_dependency 'cucumber', '~> 2.0'
|
||||
|
Loading…
x
Reference in New Issue
Block a user