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
|
- ruby-head
|
||||||
- 2.1
|
- 2.1
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update -qq
|
- travis_retry sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq twm x11-utils xdotool
|
- travis_retry sudo apt-get install -qq twm x11-utils xdotool
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- rvm: ruby-head
|
- rvm: ruby-head
|
||||||
|
33
README.md
33
README.md
@ -1,17 +1,40 @@
|
|||||||
uh-wm
|
uh-wm
|
||||||
=====
|
=====
|
||||||
|
|
||||||
Xlib Window Manager prototype.
|
|
||||||
|
|
||||||
[![Version ][badge-version-img]][badge-version-uri]
|
[![Version ][badge-version-img]][badge-version-uri]
|
||||||
[![Build status ][badge-build-img]][badge-build-uri]
|
[![Build status ][badge-build-img]][badge-build-uri]
|
||||||
[![Code Climate ][badge-cclimate-img]][badge-cclimate-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
|
Getting started
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
### Installation (requires ruby ~> 2.1 and rubygems)
|
### Installation (requires ruby ~> 2.1 with rubygems)
|
||||||
|
|
||||||
```
|
```
|
||||||
$ gem install uh-wm
|
$ gem install uh-wm
|
||||||
@ -24,13 +47,15 @@ $ gem install uh-wm
|
|||||||
Usage: uhwm [options]
|
Usage: uhwm [options]
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help print this message
|
|
||||||
-v, --verbose enable verbose mode
|
-v, --verbose enable verbose mode
|
||||||
-d, --debug enable debug mode
|
-d, --debug enable debug mode
|
||||||
-f, --run-control PATH specify alternate run control file
|
-f, --run-control PATH specify alternate run control file
|
||||||
-r, --require PATH require ruby feature
|
-r, --require PATH require ruby feature
|
||||||
-l, --layout LAYOUT specify layout
|
-l, --layout LAYOUT specify layout
|
||||||
-w, --worker WORKER specify worker
|
-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]
|
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'
|
desc 'Run uhwm in a Xephyr X server'
|
||||||
task :run do
|
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|
|
Tempfile.create('uhwm_xinitrc') do |xinitrc|
|
||||||
xinitrc.write <<-eoh
|
xinitrc.write <<-eoh
|
||||||
[ -f $HOME/.Xdefaults ] && xrdb $HOME/.Xdefaults
|
[ -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:
|
Given a file named layout.rb with:
|
||||||
"""
|
"""
|
||||||
class MyLayout
|
class MyLayout
|
||||||
def register _; end
|
def register *_
|
||||||
|
puts 'testing_cli_layout'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
When I run uhwm with option -v -r./layout -l MyLayout
|
When I run uhwm with options -r./layout -l MyLayout
|
||||||
Then the output must match /layout.+mylayout/i
|
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
|
Feature: require CLI option
|
||||||
|
|
||||||
Scenario: requires a ruby feature
|
Scenario: requires a ruby feature
|
||||||
When I run uhwm with option -v -r abbrev
|
Given a file named my_feature.rb with:
|
||||||
Then the output must match /load.+abbrev.+ruby feature/i
|
"""
|
||||||
|
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
|
Scenario: specifies run control file path
|
||||||
Given a file named uhwmrc.rb with:
|
Given a file named uhwmrc.rb with:
|
||||||
"""
|
"""
|
||||||
puts 'run control evaluation'
|
puts 'testing_run_control'
|
||||||
"""
|
"""
|
||||||
When I run uhwm with option -f uhwmrc.rb
|
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
|
Feature: worker CLI option
|
||||||
|
|
||||||
Scenario: uses the blocking worker when `block' is given
|
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
|
Then the output must match /work.+event.+block/i
|
||||||
|
|
||||||
Scenario: uses the multiplexing worker when `mux' is given
|
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
|
Then the output must match /work.+event.+mux/i
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
Feature: layout client management
|
Feature: layout client management
|
||||||
|
|
||||||
Scenario: sends the #<< message when telling the layout to manage a client
|
Background:
|
||||||
Given a file named layout.rb with:
|
Given uhwm is running
|
||||||
"""
|
|
||||||
class Layout
|
|
||||||
def register *_; end
|
|
||||||
|
|
||||||
def << client
|
Scenario: maps new client window
|
||||||
puts client
|
|
||||||
end
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
And uhwm is running with options -v -r./layout -l Layout
|
|
||||||
When a window requests to be mapped
|
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
|
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
|
Scenario: reports run control code in backtrace on errors
|
||||||
Given a run control file with:
|
Given a run control file with:
|
||||||
"""
|
"""
|
||||||
'no error on first line'
|
'no error on first line'
|
||||||
fail 'fails on second line'
|
fail 'testing_rc_failure'
|
||||||
"""
|
"""
|
||||||
When I start uhwm
|
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
|
Scenario: defines code to run when given key is pressed
|
||||||
Given uhwm is running with this run control file:
|
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
|
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
|
Scenario: translates common key names to their X equivalent
|
||||||
Given uhwm is running with this run control file:
|
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
|
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
|
Scenario: translates upcased key names to combination with shift key
|
||||||
Given uhwm is running with this run control file:
|
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
|
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
|
Feature: `modifier' run control keyword
|
||||||
|
|
||||||
Scenario: configures the modifier key
|
Scenario: configures the modifier key
|
||||||
Given a run control file with:
|
Given uhwm is running with this run control file:
|
||||||
"""
|
"""
|
||||||
modifier :ctrl
|
modifier :ctrl
|
||||||
"""
|
"""
|
||||||
And uhwm is running
|
When I press the ctrl+shift+q keys
|
||||||
When I press the ctrl+q keys
|
|
||||||
Then uhwm must terminate successfully
|
Then uhwm must terminate successfully
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
Feature: `worker' run control keyword
|
Feature: `worker' run control keyword
|
||||||
|
|
||||||
Scenario: configures the modifier key
|
Scenario: configures the modifier key
|
||||||
Given a run control file with:
|
Given uhwm is running with this run control file:
|
||||||
"""
|
"""
|
||||||
worker :mux
|
worker :mux
|
||||||
"""
|
"""
|
||||||
And I start uhwm
|
|
||||||
Then the output must match /work.+event.+mux/i
|
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
|
Scenario: logs about termination
|
||||||
When I tell uhwm to quit
|
When I tell uhwm to quit
|
||||||
Then uhwm must terminate successfully
|
Then the output must match /terminat/i
|
||||||
And the output must match /terminat/i
|
|
||||||
|
@ -16,7 +16,6 @@ options:
|
|||||||
end
|
end
|
||||||
|
|
||||||
Then /^the output must contain exactly the version$/ do
|
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
|
assert_exact_output "%s\n" % Uh::WM::VERSION, all_output
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -24,6 +23,21 @@ Then /^the output must match \/([^\/]+)\/([a-z]*)$/ do |pattern, options|
|
|||||||
uhwm_wait_output Regexp.new(pattern, options)
|
uhwm_wait_output Regexp.new(pattern, options)
|
||||||
end
|
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|
|
Then /^the output must contain:$/ do |content|
|
||||||
uhwm_wait_output content.to_s
|
uhwm_wait_output content.to_s
|
||||||
end
|
end
|
||||||
|
@ -24,7 +24,12 @@ When /^I run uhwm with options? (-.+)$/ do |options|
|
|||||||
end
|
end
|
||||||
|
|
||||||
When /^I tell uhwm to quit$/ do
|
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
|
end
|
||||||
|
|
||||||
Then /^the exit status must be (\d+)$/ do |exit_status|
|
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|
|
When /^I press the ([^ ]+) keys?$/ do |keys|
|
||||||
x_key keys
|
x_key keys
|
||||||
end
|
end
|
||||||
|
|
||||||
When /^a window requests to be mapped$/ do
|
When /^I press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
|
||||||
x_window_map
|
times.to_i.times { x_key keys }
|
||||||
end
|
end
|
||||||
|
|
||||||
Then /^it must connect to X display$/ do
|
When /^I quickly press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
|
||||||
uhwm_wait_output 'Connected to'
|
x_key [keys] * times.to_i, delay: 0
|
||||||
expect(x_socket_check uhwm_pid).to be true
|
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
|
end
|
||||||
|
@ -28,6 +28,10 @@ Around '@other_wm_running' do |_, block|
|
|||||||
with_other_wm { block.call }
|
with_other_wm { block.call }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
After '@icccm_window' do
|
||||||
|
icccm_window_ensure_stop
|
||||||
|
end
|
||||||
|
|
||||||
if ENV.key? 'TRAVIS'
|
if ENV.key? 'TRAVIS'
|
||||||
ENV['UHWMTEST_TIMEOUT'] = 8.to_s
|
ENV['UHWMTEST_TIMEOUT'] = 8.to_s
|
||||||
end
|
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 'optparse'
|
||||||
require 'uh'
|
require 'uh'
|
||||||
|
|
||||||
|
require 'uh/wm/env_logging'
|
||||||
|
|
||||||
require 'uh/wm/actions_handler'
|
require 'uh/wm/actions_handler'
|
||||||
require 'uh/wm/cli'
|
require 'uh/wm/cli'
|
||||||
require 'uh/wm/client'
|
require 'uh/wm/client'
|
||||||
|
@ -1,32 +1,75 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
class ActionsHandler
|
class ActionsHandler
|
||||||
|
include EnvLogging
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
def_delegator :@env, :layout
|
||||||
|
|
||||||
def initialize env, events
|
def initialize env, events
|
||||||
@env, @events = env, events
|
@env, @events = env, events
|
||||||
end
|
end
|
||||||
|
|
||||||
def evaluate code
|
def evaluate code = nil, &block
|
||||||
instance_eval &code
|
if code
|
||||||
|
instance_exec &code
|
||||||
|
else
|
||||||
|
instance_exec &block
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def quit
|
def quit
|
||||||
|
log 'Quit requested'
|
||||||
@events.emit :quit
|
@events.emit :quit
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute command
|
def execute command
|
||||||
@env.log "Execute: #{command}"
|
log "Execute: #{command}"
|
||||||
pid = fork do
|
pid = fork do
|
||||||
fork do
|
fork do
|
||||||
Process.setsid
|
Process.setsid
|
||||||
begin
|
begin
|
||||||
exec command
|
exec command
|
||||||
rescue Errno::ENOENT => e
|
rescue Errno::ENOENT => e
|
||||||
@env.log_error "ExecuteError: #{e}"
|
log_error "ExecuteError: #{e}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Process.waitpid pid
|
Process.waitpid pid
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,6 +3,8 @@ module Uh
|
|||||||
class CLI
|
class CLI
|
||||||
ArgumentError = Class.new(ArgumentError)
|
ArgumentError = Class.new(ArgumentError)
|
||||||
|
|
||||||
|
include EnvLogging
|
||||||
|
|
||||||
USAGE = "Usage: #{File.basename $0} [options]".freeze
|
USAGE = "Usage: #{File.basename $0} [options]".freeze
|
||||||
|
|
||||||
EX_USAGE = 64
|
EX_USAGE = 64
|
||||||
@ -63,10 +65,10 @@ module Uh
|
|||||||
end
|
end
|
||||||
opts.on '-r', '--require PATH', 'require ruby feature' do |feature|
|
opts.on '-r', '--require PATH', 'require ruby feature' do |feature|
|
||||||
require feature
|
require feature
|
||||||
@env.log "Loaded `#{feature}' ruby feature"
|
log "Loaded `#{feature}' ruby feature"
|
||||||
end
|
end
|
||||||
opts.on '-l', '--layout LAYOUT', 'specify layout' do |layout|
|
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
|
end
|
||||||
opts.on '-w', Workers.types, '--worker WORKER',
|
opts.on '-w', Workers.types, '--worker WORKER',
|
||||||
'specify worker' do |worker|
|
'specify worker' do |worker|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
class Client
|
class Client
|
||||||
attr_reader :window, :unmap_count
|
include GeoAccessors
|
||||||
attr_accessor :geo
|
|
||||||
|
attr_reader :window
|
||||||
|
attr_accessor :geo, :unmap_count
|
||||||
|
|
||||||
def initialize window, geo = nil
|
def initialize window, geo = nil
|
||||||
@window = window
|
@window = window
|
||||||
@ -31,6 +33,16 @@ module Uh
|
|||||||
@wclass ||= @window.wclass
|
@wclass ||= @window.wclass
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_window_properties
|
||||||
|
@wname = @window.name
|
||||||
|
@wclass = @window.wclass
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure
|
||||||
|
@window.configure @geo
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
def moveresize
|
def moveresize
|
||||||
@window.moveresize @geo
|
@window.moveresize @geo
|
||||||
self
|
self
|
||||||
@ -54,6 +66,20 @@ module Uh
|
|||||||
@window.focus
|
@window.focus
|
||||||
self
|
self
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -16,7 +16,9 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
def emit *key, args: []
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ module Uh
|
|||||||
|
|
||||||
MODIFIER = :mod1
|
MODIFIER = :mod1
|
||||||
KEYBINDS = {
|
KEYBINDS = {
|
||||||
q: proc { quit }
|
[:q, :shift] => proc { quit }
|
||||||
}.freeze
|
}.freeze
|
||||||
WORKER = :block
|
WORKER = :block
|
||||||
|
|
||||||
@ -21,8 +21,8 @@ module Uh
|
|||||||
def_delegators :@output, :print, :puts
|
def_delegators :@output, :print, :puts
|
||||||
|
|
||||||
attr_reader :output, :keybinds
|
attr_reader :output, :keybinds
|
||||||
attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier,
|
attr_accessor :verbose, :debug, :rc_path, :modifier, :worker,
|
||||||
:worker
|
:layout, :layout_class
|
||||||
|
|
||||||
def initialize output
|
def initialize output
|
||||||
@output = 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_REDIRECT_MASK |
|
||||||
Events::SUBSTRUCTURE_NOTIFY_MASK |
|
Events::SUBSTRUCTURE_NOTIFY_MASK |
|
||||||
Events::STRUCTURE_NOTIFY_MASK
|
Events::STRUCTURE_NOTIFY_MASK
|
||||||
|
DEFAULT_GEO = Geo.new(0, 0, 320, 240).freeze
|
||||||
|
|
||||||
attr_reader :modifier, :display, :clients
|
attr_reader :modifier, :display, :clients
|
||||||
|
|
||||||
@ -45,6 +46,42 @@ module Uh
|
|||||||
@display.grab_key keysym.to_s, mod_mask
|
@display.grab_key keysym.to_s, mod_mask
|
||||||
end
|
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
|
def handle_next_event
|
||||||
handle @display.next_event
|
handle @display.next_event
|
||||||
end
|
end
|
||||||
@ -63,7 +100,7 @@ module Uh
|
|||||||
private
|
private
|
||||||
|
|
||||||
def handle_error *args
|
def handle_error *args
|
||||||
@dispatcher.emit :error, args: args
|
@events.emit :xerror, args: args
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_key_press event
|
def handle_key_press event
|
||||||
@ -73,9 +110,37 @@ module Uh
|
|||||||
@events.emit :key, *key_selector
|
@events.emit :key, *key_selector
|
||||||
end
|
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
|
def handle_map_request event
|
||||||
@clients << client = Client.new(event.window)
|
map event.window
|
||||||
@events.emit :manage, args: client
|
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
|
end
|
||||||
|
|
||||||
def check_other_wm!
|
def check_other_wm!
|
||||||
|
@ -28,8 +28,20 @@ module Uh
|
|||||||
@env.modifier = mod
|
@env.modifier = mod
|
||||||
end
|
end
|
||||||
|
|
||||||
def key keysym, &block
|
def key *keysyms, &block
|
||||||
@env.keybinds[translate_keysym keysym] = 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
|
end
|
||||||
|
|
||||||
def worker type, **options
|
def worker type, **options
|
||||||
@ -39,7 +51,8 @@ module Uh
|
|||||||
|
|
||||||
private
|
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
|
translate_key = keysym.to_s.downcase.to_sym
|
||||||
translated_keysym = KEYSYM_TRANSLATIONS.key?(translate_key) ?
|
translated_keysym = KEYSYM_TRANSLATIONS.key?(translate_key) ?
|
||||||
KEYSYM_TRANSLATIONS[translate_key] :
|
KEYSYM_TRANSLATIONS[translate_key] :
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
class Runner
|
class Runner
|
||||||
|
include EnvLogging
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def run env, **options
|
def run env, **options
|
||||||
runner = new env, **options
|
runner = new env, **options
|
||||||
@ -49,24 +51,17 @@ module Uh
|
|||||||
|
|
||||||
def connect_manager
|
def connect_manager
|
||||||
manager.connect
|
manager.connect
|
||||||
@env.keybinds.each do |keysym, _|
|
@env.keybinds.each { |keysym, _| manager.grab_key *keysym }
|
||||||
manager.grab_key *keysym
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def worker
|
def worker
|
||||||
@worker ||= Workers.build(*(@env.worker)).tap do |w|
|
@worker ||= Workers.build(*(@env.worker)).tap do |w|
|
||||||
w.on_read do
|
w.before_watch { @manager.handle_pending_events }
|
||||||
@env.log_debug 'Processing pending events'
|
w.on_read { @manager.handle_pending_events }
|
||||||
@manager.handle_pending_events
|
w.on_read_next { @manager.handle_next_event }
|
||||||
end
|
|
||||||
w.on_read_next do
|
|
||||||
@env.log_debug 'Processing next event'
|
|
||||||
@manager.handle_next_event
|
|
||||||
end
|
|
||||||
w.on_timeout do |*args|
|
w.on_timeout do |*args|
|
||||||
@env.log_debug "Worker timeout: #{args.inspect}"
|
log_debug "Worker timeout: #{args.inspect}"
|
||||||
@env.log_debug 'Flushing X output buffer'
|
log_debug 'Flushing X output buffer'
|
||||||
@manager.flush
|
@manager.flush
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -74,12 +69,12 @@ module Uh
|
|||||||
|
|
||||||
def run_until &block
|
def run_until &block
|
||||||
worker.watch @manager
|
worker.watch @manager
|
||||||
@env.log "Working events with `#{worker.class}'"
|
log "Working events with `#{worker.class}'"
|
||||||
worker.work_events until block.call
|
worker.work_events until block.call
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate
|
def terminate
|
||||||
@env.log "Terminating..."
|
log "Terminating..."
|
||||||
manager.disconnect
|
manager.disconnect
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -92,24 +87,41 @@ module Uh
|
|||||||
|
|
||||||
def register_manager_hooks
|
def register_manager_hooks
|
||||||
@events.on :connecting do |display|
|
@events.on :connecting do |display|
|
||||||
@env.log_debug "Connecting to X server on `#{display}'"
|
log_debug "Connecting to X server on `#{display}'"
|
||||||
end
|
end
|
||||||
@events.on :connected do |display|
|
@events.on :connected do |display|
|
||||||
@env.log "Connected to X server on `#{display}'"
|
log "Connected to X server on `#{display}'"
|
||||||
end
|
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(:xevent) { |event| XEventLogger.new(env).log_event event }
|
||||||
|
@events.on(:xerror) { |*error| XEventLogger.new(env).log_xerror *error }
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_layout_hooks
|
def register_layout_hooks
|
||||||
@events.on :connected do |display|
|
@events.on :connected do |display|
|
||||||
@env.log "Registering layout `#{layout.class}'"
|
log "Registering layout `#{layout.class}'"
|
||||||
layout.register display
|
layout.register display
|
||||||
end
|
end
|
||||||
|
@events.on :configure do |window|
|
||||||
|
log "Configuring window: #{window}"
|
||||||
|
layout.suggest_geo
|
||||||
|
end
|
||||||
@events.on :manage do |client|
|
@events.on :manage do |client|
|
||||||
@env.log "Manage client #{client}"
|
log "Managing client #{client}"
|
||||||
layout << client
|
layout << client
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def register_keybinds_hooks
|
def register_keybinds_hooks
|
||||||
@ -120,6 +132,8 @@ module Uh
|
|||||||
|
|
||||||
|
|
||||||
class XEventLogger
|
class XEventLogger
|
||||||
|
include EnvLogging
|
||||||
|
|
||||||
def initialize env
|
def initialize env
|
||||||
@env = env
|
@env = env
|
||||||
end
|
end
|
||||||
@ -132,13 +146,17 @@ module Uh
|
|||||||
"window: #{xev.window}"
|
"window: #{xev.window}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@env.log_debug [
|
log_debug [
|
||||||
'XEvent',
|
'XEvent',
|
||||||
xev.type,
|
xev.type,
|
||||||
xev.send_event ? 'SENT' : nil,
|
xev.send_event ? 'SENT' : nil,
|
||||||
complement
|
complement
|
||||||
].compact.join ' '
|
].compact.join ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_xerror req, resource_id, msg
|
||||||
|
log_error "XERROR: #{resource_id} #{req} #{msg}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,45 +4,67 @@ module Uh
|
|||||||
module WM
|
module WM
|
||||||
module Testing
|
module Testing
|
||||||
module AcceptanceHelpers
|
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'
|
def uhwm_run options = '-v'
|
||||||
command = %w[uhwm]
|
command = %w[uhwm]
|
||||||
command << options if options
|
command << options if options
|
||||||
@interactive = @process = run command.join ' '
|
@interactive = @process = run command.join ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uhwm
|
||||||
|
@process
|
||||||
|
end
|
||||||
|
|
||||||
|
def uhwm_request_quit
|
||||||
|
x_key QUIT_KEYBINDING
|
||||||
|
end
|
||||||
|
|
||||||
def uhwm_ensure_stop
|
def uhwm_ensure_stop
|
||||||
if @process
|
if @process
|
||||||
x_key 'alt+q'
|
x_key 'alt+shift+q'
|
||||||
@process.terminate
|
@process.terminate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uhwm_pid
|
def uhwm_wait_output message, times = 1, value = nil
|
||||||
@process.pid
|
|
||||||
end
|
|
||||||
|
|
||||||
def uhwm_output
|
|
||||||
@process.stdout
|
|
||||||
end
|
|
||||||
|
|
||||||
def uhwm_wait_output message
|
|
||||||
output = -> { @process.stdout + @process.stderr }
|
output = -> { @process.stdout + @process.stderr }
|
||||||
timeout_until do
|
timeout_until do
|
||||||
case message
|
case message
|
||||||
when Regexp then output.call =~ message
|
when Regexp then (value = output.call.scan(message)).size >= times
|
||||||
when String then assert_partial_output_interactive message
|
when String then output.call.include? message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
value
|
||||||
rescue TimeoutError => e
|
rescue TimeoutError => e
|
||||||
fail [
|
fail <<-eoh
|
||||||
"expected `#{message}' not seen after #{e.timeout} seconds in:",
|
expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
|
||||||
" ```\n#{output.call.lines.map { |e| " #{e}" }.join} ```"
|
```\n#{output.call.lines.map { |e| " #{e}" }.join} ```
|
||||||
].join "\n"
|
eoh
|
||||||
|
end
|
||||||
|
|
||||||
|
def uhwm_wait_ready
|
||||||
|
uhwm_wait_output LOG_READY
|
||||||
end
|
end
|
||||||
|
|
||||||
def uhwm_run_wait_ready options = nil
|
def uhwm_run_wait_ready options = nil
|
||||||
if options then uhwm_run options else uhwm_run end
|
if options then uhwm_run options else uhwm_run end
|
||||||
uhwm_wait_output 'Connected to'
|
uhwm_wait_ready
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_other_wm
|
def with_other_wm
|
||||||
@ -56,42 +78,48 @@ module Uh
|
|||||||
@other_wm
|
@other_wm
|
||||||
end
|
end
|
||||||
|
|
||||||
def x_client
|
def x_client ident = nil
|
||||||
@x_client ||= XClient.new
|
ident ||= :default
|
||||||
end
|
@x_clients ||= {}
|
||||||
|
@x_clients[ident] ||= XClient.new(ident)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def x_clients_ensure_stop
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def timeout_until
|
def timeout_until message = 'condition not met after %d seconds'
|
||||||
timeout = ENV.key?('UHWMTEST_TIMEOUT') ?
|
timeout = ENV.key?('UHWMTEST_TIMEOUT') ?
|
||||||
ENV['UHWMTEST_TIMEOUT'].to_i :
|
ENV['UHWMTEST_TIMEOUT'].to_i :
|
||||||
1
|
TIMEOUT_DEFAULT
|
||||||
Timeout.timeout(timeout) do
|
Timeout.timeout(timeout) do
|
||||||
loop do
|
loop do
|
||||||
break if yield
|
break if yield
|
||||||
@ -99,7 +127,7 @@ module Uh
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue Timeout::Error
|
rescue Timeout::Error
|
||||||
fail TimeoutError.new('execution expired', timeout)
|
fail TimeoutError.new(message % timeout, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -115,8 +143,8 @@ module Uh
|
|||||||
class XClient
|
class XClient
|
||||||
attr_reader :name
|
attr_reader :name
|
||||||
|
|
||||||
def initialize
|
def initialize name = object_id
|
||||||
@name = "#{self.class.name.split('::').last}/#{object_id}"
|
@name = "#{self.class.name.split('::').last}/#{name}"
|
||||||
@geo = Geo.new(0, 0, 640, 480)
|
@geo = Geo.new(0, 0, 640, 480)
|
||||||
@display = Display.new.tap { |o| o.open }
|
@display = Display.new.tap { |o| o.open }
|
||||||
end
|
end
|
||||||
@ -130,19 +158,37 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
def window
|
def window
|
||||||
@window ||= @display.create_window(@geo).tap do |o|
|
@window ||= @display.create_window(@geo).tap { |o| o.name = @name }
|
||||||
o.name = @name
|
end
|
||||||
end
|
|
||||||
|
def window_id
|
||||||
|
@window.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def window_name
|
def window_name
|
||||||
@name
|
@name
|
||||||
end
|
end
|
||||||
|
|
||||||
def map
|
def window_name= name
|
||||||
|
@name = @window.name = name
|
||||||
|
window.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def map times: 1
|
||||||
|
times.times { window.map }
|
||||||
window.map
|
window.map
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unmap
|
||||||
|
window.unmap
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
window.destroy
|
||||||
|
self
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.6'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@ module Uh
|
|||||||
module WM
|
module WM
|
||||||
module Workers
|
module Workers
|
||||||
class Base
|
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
|
def initialize **options
|
||||||
@ios = []
|
@ios = []
|
||||||
|
@ -2,13 +2,15 @@ module Uh
|
|||||||
module WM
|
module WM
|
||||||
module Workers
|
module Workers
|
||||||
class Mux < Base
|
class Mux < Base
|
||||||
def initialize timeout: 1
|
TIMEOUT_DEFAULT = 1
|
||||||
|
|
||||||
|
def initialize timeout: TIMEOUT_DEFAULT
|
||||||
super
|
super
|
||||||
@timeout = timeout
|
@timeout = timeout
|
||||||
end
|
end
|
||||||
|
|
||||||
def work_events
|
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
|
if res = select(@ios, [], [], @timeout) then @on_read.call res
|
||||||
else @on_timeout.call if @on_timeout end
|
else @on_timeout.call if @on_timeout end
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,11 @@ require 'headless'
|
|||||||
|
|
||||||
require 'uh/wm'
|
require 'uh/wm'
|
||||||
|
|
||||||
|
Dir['spec/support/**/*.rb'].map { |e| require e.gsub 'spec/', '' }
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
|
config.include Factories
|
||||||
|
|
||||||
config.expect_with :rspec do |expectations|
|
config.expect_with :rspec do |expectations|
|
||||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||||
end
|
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 }
|
subject(:actions) { described_class.new env, events }
|
||||||
|
|
||||||
describe '#evaluate' do
|
describe '#evaluate' do
|
||||||
it 'evaluates given code' do
|
it 'evaluates code given as Proc argument' do
|
||||||
expect { actions.evaluate proc { throw :action_code } }
|
expect { actions.evaluate proc { throw :action_code } }
|
||||||
.to throw_symbol :action_code
|
.to throw_symbol :action_code
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'evaluates code given as block' do
|
||||||
|
expect { actions.evaluate { throw :action_code } }
|
||||||
|
.to throw_symbol :action_code
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#quit' do
|
describe '#quit' do
|
||||||
@ -18,6 +23,35 @@ module Uh
|
|||||||
actions.quit
|
actions.quit
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
RSpec.describe Client do
|
RSpec.describe Client do
|
||||||
let(:geo) { Geo.new(0, 0, 640, 480) }
|
let(:protocols) { [] }
|
||||||
let(:window) do
|
let(:window) { mock_window icccm_wm_protocols: protocols }
|
||||||
instance_spy Window, 'window', to_s: 'wid',
|
let(:geo) { build_geo }
|
||||||
name: 'wname', wclass: 'wclass'
|
|
||||||
end
|
|
||||||
subject(:client) { described_class.new window, geo }
|
subject(:client) { described_class.new window, geo }
|
||||||
|
|
||||||
it 'is not visible' do
|
it 'is not visible' do
|
||||||
@ -33,7 +31,7 @@ module Uh
|
|||||||
expect(client.to_s).to include geo.to_s
|
expect(client.to_s).to include geo.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes window id' do
|
it 'includes window string representation' do
|
||||||
expect(client.to_s).to include 'wid'
|
expect(client.to_s).to include 'wid'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -50,6 +48,35 @@ module Uh
|
|||||||
end
|
end
|
||||||
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
|
describe '#moveresize' do
|
||||||
it 'moveresizes the window with client geo' do
|
it 'moveresizes the window with client geo' do
|
||||||
expect(window).to receive(:moveresize).with geo
|
expect(window).to receive(:moveresize).with geo
|
||||||
@ -117,6 +144,37 @@ module Uh
|
|||||||
expect(client.focus).to be client
|
expect(client.focus).to be client
|
||||||
end
|
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module Uh
|
module Uh
|
||||||
module WM
|
module WM
|
||||||
RSpec.describe Dispatcher do
|
RSpec.describe Dispatcher do
|
||||||
subject(:dispatcher) { described_class.new }
|
subject(:dispatcher) { described_class.new }
|
||||||
|
|
||||||
describe '#[]' do
|
describe '#[]' do
|
||||||
context 'when given key for existing hook' 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
|
expect { dispatcher.emit :hook_key }.to throw_symbol :hook_code
|
||||||
end
|
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
|
context 'when no hooks are registered for given key' do
|
||||||
it 'does not call another hook' do
|
it 'does not call another hook' do
|
||||||
dispatcher.on(:hook_key) { throw :hook_code }
|
dispatcher.on(:hook_key) { throw :hook_code }
|
||||||
|
@ -25,8 +25,8 @@ module Uh
|
|||||||
expect(env.modifier).to eq :mod1
|
expect(env.modifier).to eq :mod1
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has defaults key bindings set' do
|
it 'has default key binding for quit set' do
|
||||||
expect(env.keybinds.keys).to eq %i[q]
|
expect(env.keybinds.keys).to include [:q, :shift]
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the blocking worker by default' do
|
it 'has the blocking worker by default' do
|
||||||
@ -70,19 +70,27 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#layout' do
|
describe '#layout' do
|
||||||
context 'when a layout class is set' do
|
let(:some_layout_class) { Class.new }
|
||||||
let(:some_layout) { 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
|
context 'when a layout is set' do
|
||||||
expect(env.layout).to be_an_instance_of some_layout
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a layout class is not set' do
|
context 'when a layout class is set' do
|
||||||
it 'returns an instance of the default layout' do
|
before { env.layout_class = some_layout_class }
|
||||||
expect(env.layout).to be_an_instance_of ::Uh::Layout
|
|
||||||
|
it 'returns a new instance of this layout class' do
|
||||||
|
expect(env.layout).to be_an_instance_of some_layout_class
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -129,7 +137,8 @@ module Uh
|
|||||||
|
|
||||||
describe '#log_logger_level' do
|
describe '#log_logger_level' do
|
||||||
it 'logs the 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
|
env.log_logger_level
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,15 +2,13 @@ module Uh
|
|||||||
module WM
|
module WM
|
||||||
RSpec.describe Manager do
|
RSpec.describe Manager do
|
||||||
let(:block) { proc { } }
|
let(:block) { proc { } }
|
||||||
|
let(:window) { mock_window }
|
||||||
|
let(:client) { build_client window }
|
||||||
let(:events) { Dispatcher.new }
|
let(:events) { Dispatcher.new }
|
||||||
let(:modifier) { :mod1 }
|
let(:modifier) { :mod1 }
|
||||||
let(:display) { Display.new }
|
let(:display) { Display.new }
|
||||||
subject(:manager) { described_class.new events, modifier, display }
|
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
|
it 'has no clients' do
|
||||||
expect(manager.clients).to be_empty
|
expect(manager.clients).to be_empty
|
||||||
end
|
end
|
||||||
@ -106,6 +104,174 @@ module Uh
|
|||||||
end
|
end
|
||||||
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
|
describe '#handle_next_event' do
|
||||||
it 'handles the next available event on display' do
|
it 'handles the next available event on display' do
|
||||||
event = double 'event'
|
event = double 'event'
|
||||||
@ -116,7 +282,7 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#handle_pending_events' do
|
describe '#handle_pending_events' do
|
||||||
let(:event) { double 'event' }
|
let(:event) { mock_event }
|
||||||
|
|
||||||
context 'when an event is pending on display' do
|
context 'when an event is pending on display' do
|
||||||
before do
|
before do
|
||||||
@ -144,7 +310,7 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#handle' do
|
describe '#handle' do
|
||||||
let(:event) { double 'event', type: :any }
|
let(:event) { mock_event }
|
||||||
|
|
||||||
it 'emits :xevent event with the X event' do
|
it 'emits :xevent event with the X event' do
|
||||||
events.on :xevent, &block
|
events.on :xevent, &block
|
||||||
@ -152,13 +318,8 @@ module Uh
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when key_press event is given' do
|
context 'when key_press event is given' do
|
||||||
let(:mod_mask) { KEY_MODIFIERS[modifier] }
|
let(:mod_mask) { KEY_MODIFIERS[modifier] }
|
||||||
let(:event) do
|
let(:event) { mock_event_key_press 'f', mod_mask }
|
||||||
double 'event',
|
|
||||||
type: :key_press,
|
|
||||||
key: 'f',
|
|
||||||
modifier_mask: mod_mask
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'emits :key event with the corresponding key' do
|
it 'emits :key event with the corresponding key' do
|
||||||
events.on(:key, :f) { throw :key_press_code }
|
events.on(:key, :f) { throw :key_press_code }
|
||||||
@ -175,23 +336,47 @@ module Uh
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when map_request event is given' do
|
context 'when configure request event is given' do
|
||||||
let(:event) { double 'event', type: :map_request, window: :window }
|
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
|
manager.handle event
|
||||||
expect(manager.clients[0])
|
|
||||||
.to be_a(Client)
|
|
||||||
.and have_attributes(window: :window)
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'emits :manage event with the registered client' do
|
context 'when destroy_notify event is given' do
|
||||||
events.on :manage, &block
|
let(:event) { mock_event :destroy_notify, window: :window }
|
||||||
expect(block).to receive :call do |client|
|
|
||||||
expect(client)
|
it 'destroy the event window' do
|
||||||
.to be_a(Client)
|
expect(manager).to receive(:destroy).with :window
|
||||||
.and have_attributes(window: :window)
|
manager.handle event
|
||||||
end
|
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
|
manager.handle event
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,7 +5,6 @@ module Uh
|
|||||||
RSpec.describe RunControl do
|
RSpec.describe RunControl do
|
||||||
include FileSystemHelpers
|
include FileSystemHelpers
|
||||||
|
|
||||||
let(:code) { :run_control_code }
|
|
||||||
let(:env) { Env.new(StringIO.new) }
|
let(:env) { Env.new(StringIO.new) }
|
||||||
subject(:rc) { described_class.new env }
|
subject(:rc) { described_class.new env }
|
||||||
|
|
||||||
@ -28,14 +27,6 @@ module Uh
|
|||||||
allow(described_class).to receive(:new) { rc }
|
allow(described_class).to receive(:new) { rc }
|
||||||
described_class.evaluate env
|
described_class.evaluate env
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe '#evaluate' do
|
describe '#evaluate' do
|
||||||
@ -65,6 +56,11 @@ module Uh
|
|||||||
expect(env.keybinds.keys).to include :f
|
expect(env.keybinds.keys).to include :f
|
||||||
end
|
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
|
it 'registers given block with the key binding' do
|
||||||
rc.key :f, &code
|
rc.key :f, &code
|
||||||
expect(env.keybinds[:f].call).to eq :keybind_code
|
expect(env.keybinds[:f].call).to eq :keybind_code
|
||||||
@ -81,6 +77,31 @@ module Uh
|
|||||||
end
|
end
|
||||||
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
|
describe '#worker' do
|
||||||
it 'sets the worker type in the env' do
|
it 'sets the worker type in the env' do
|
||||||
rc.worker :some_worker
|
rc.worker :some_worker
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
SomeLayout = Class.new do
|
SomeLayout = Class.new do
|
||||||
define_method(:register) { |*args| }
|
include Factories
|
||||||
define_method(:<<) { |*args| }
|
|
||||||
|
define_method(:register) { |*_| }
|
||||||
|
define_method(:suggest_geo) { build_geo 0, 0, 42, 42 }
|
||||||
|
define_method(:<<) { |*_| }
|
||||||
|
define_method(:remove) { |*_| }
|
||||||
|
define_method(:update) { |*_| }
|
||||||
end
|
end
|
||||||
|
|
||||||
module Uh
|
module Uh
|
||||||
@ -14,10 +19,6 @@ module Uh
|
|||||||
env.rc_path = 'non_existent_run_control.rb'
|
env.rc_path = 'non_existent_run_control.rb'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the env' do
|
|
||||||
expect(runner.env).to be env
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has a dispatcher' do
|
it 'has a dispatcher' do
|
||||||
expect(runner.events).to be_a Dispatcher
|
expect(runner.events).to be_a Dispatcher
|
||||||
end
|
end
|
||||||
@ -70,39 +71,61 @@ module Uh
|
|||||||
describe '#register_event_hooks' do
|
describe '#register_event_hooks' do
|
||||||
it 'registers quit event hook' do
|
it 'registers quit event hook' do
|
||||||
runner.register_event_hooks
|
runner.register_event_hooks
|
||||||
expect(runner).to receive(:stop!)
|
expect(runner).to receive :stop!
|
||||||
runner.events.emit :quit
|
runner.events.emit :quit
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'registers layout hook for :connected event' do
|
context 'layout hooks' do
|
||||||
runner.register_event_hooks
|
it 'registers for :connected event' do
|
||||||
expect(env.layout).to receive(:register).with :display
|
runner.register_event_hooks
|
||||||
runner.events.emit :connected, args: :display
|
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
|
end
|
||||||
|
|
||||||
it 'registers layout hook for :manage event' do
|
context 'keys hooks' do
|
||||||
runner.register_event_hooks
|
it 'registers for :key' do
|
||||||
expect(env.layout).to receive(:<<).with :window
|
env.keybinds[:f] = -> { }
|
||||||
runner.events.emit :manage, args: :window
|
runner.register_event_hooks
|
||||||
end
|
expect(runner.events[:key, :f]).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
it 'registers key bindings event hooks' do
|
it 'registers for :key with combined key bindings' do
|
||||||
env.keybinds[:f] = -> { }
|
env.keybinds[[:f, :shift]] = -> { }
|
||||||
runner.register_event_hooks
|
runner.register_event_hooks
|
||||||
expect(runner.events[:key, :f]).not_to be_empty
|
expect(runner.events[:key, :f, :shift]).not_to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'registers combined key bindings event hooks' do
|
it 'registers code evaluation with the actions handler' do
|
||||||
env.keybinds[[:f, :shift]] = -> { }
|
env.keybinds[:f] = code = proc { }
|
||||||
runner.register_event_hooks
|
runner.register_event_hooks
|
||||||
expect(runner.events[:key, :f, :shift]).not_to be_empty
|
expect(runner.actions).to receive(:evaluate).with code
|
||||||
end
|
runner.events.emit :key, :f
|
||||||
|
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -127,17 +150,22 @@ module Uh
|
|||||||
expect(runner.worker).to respond_to :work_events
|
expect(runner.worker).to respond_to :work_events
|
||||||
end
|
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
|
expect(runner.manager).to receive :handle_pending_events
|
||||||
runner.worker.on_read.call
|
runner.worker.on_read.call
|
||||||
end
|
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
|
expect(runner.manager).to receive :handle_next_event
|
||||||
runner.worker.on_read_next.call
|
runner.worker.on_read_next.call
|
||||||
end
|
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
|
expect(runner.manager).to receive :flush
|
||||||
runner.worker.on_timeout.call
|
runner.worker.on_timeout.call
|
||||||
end
|
end
|
||||||
|
@ -3,8 +3,9 @@ require File.expand_path('../lib/uh/wm/version', __FILE__)
|
|||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = 'uh-wm'
|
s.name = 'uh-wm'
|
||||||
s.version = Uh::WM::VERSION.dup
|
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.description = s.name
|
||||||
|
s.license = 'BSD-3-Clause'
|
||||||
s.homepage = 'https://rubygems.org/gems/uh-wm'
|
s.homepage = 'https://rubygems.org/gems/uh-wm'
|
||||||
|
|
||||||
s.authors = 'Thibault Jouan'
|
s.authors = 'Thibault Jouan'
|
||||||
@ -14,8 +15,8 @@ Gem::Specification.new do |s|
|
|||||||
s.test_files = s.files.grep /\A(spec|features)\//
|
s.test_files = s.files.grep /\A(spec|features)\//
|
||||||
s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
|
s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
|
||||||
|
|
||||||
s.add_dependency 'uh', '2.0.0.pre1'
|
s.add_dependency 'uh', '2.0.0'
|
||||||
s.add_dependency 'uh-layout', '0.2.0.pre'
|
s.add_dependency 'uh-layout', '~> 0.4.0'
|
||||||
|
|
||||||
s.add_development_dependency 'aruba', '~> 0.6'
|
s.add_development_dependency 'aruba', '~> 0.6'
|
||||||
s.add_development_dependency 'cucumber', '~> 2.0'
|
s.add_development_dependency 'cucumber', '~> 2.0'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user