Compare commits

...

136 Commits

Author SHA1 Message Date
Thibault Jouan
d9b4ac6083 Extract default mux worker timeout in a constant 2015-04-29 09:24:50 +00:00
Thibault Jouan
d24f25bbbe Test blocking worker event handling reliability 2015-04-29 09:24:44 +00:00
Thibault Jouan
2731f9b3a3 Support delay modification in x_key UAT helper 2015-04-29 09:24:35 +00:00
Thibault Jouan
4bc513010e Improve blocking worker acceptance test 2015-04-29 02:45:24 +00:00
Thibault Jouan
8a490e155f Merge branch 'client-killing' 2015-04-29 02:44:44 +00:00
Thibault Jouan
1cc1f98ad2 Implement `kill' action keyword
When invoked, will kill the client returned by
`layout.current_client`. ICCCM WM_DELETE_WINDOW is used instead if the
window reports to support this protocol.
2015-04-29 02:44:34 +00:00
Thibault Jouan
10ad5d9a76 Fix x_window_map_state UAT helper to hide output 2015-04-29 02:44:34 +00:00
Thibault Jouan
eed9cdf0c0 Support usage of ICCCM compliant window in UAT 2015-04-29 02:37:31 +00:00
Thibault Jouan
97abd402b1 Accept window names in x_window_map_state test helper 2015-04-29 02:36:36 +00:00
Thibault Jouan
f26e16fe6e Implement Client#kill and Client#kill! 2015-04-29 01:33:13 +00:00
Thibault Jouan
4050d52af7 Remove deprecated specs 2015-04-29 01:32:57 +00:00
Thibault Jouan
92dedd16e7 Implement `log_separator' action keyword 2015-04-28 10:45:56 +00:00
Thibault Jouan
85e5abac8d Workaround travis CI scrolling bugs (quiet output) 2015-04-28 10:31:04 +00:00
Thibault Jouan
6a4a15e9db Remove deprecated X connection test (UAT) 2015-04-28 09:44:49 +00:00
Thibault Jouan
27448abfc6 Improve expectation in rc key test scenario (UAT) 2015-04-28 09:42:39 +00:00
Thibault Jouan
a6b2e98146 Improve expectation in rc eval test scenario (UAT) 2015-04-28 09:38:40 +00:00
Thibault Jouan
1d5ee7cab7 Remove debug code from layout delegation UAT 2015-04-28 09:37:57 +00:00
Thibault Jouan
d9ce2eb120 Remove deprecated test scenario (UAT) 2015-04-28 09:37:08 +00:00
Thibault Jouan
c9ab16c847 Fix typo in tests (UAT) 2015-04-28 09:35:10 +00:00
Thibault Jouan
265bb1d626 Remove deprecated test (UAT) 2015-04-28 09:34:49 +00:00
Thibault Jouan
c394fee2a6 Improve cli layout test expectations (UAT) 2015-04-28 09:31:40 +00:00
Thibault Jouan
01d9bdb3b3 Improve test expectations consistency (UAT) 2015-04-28 09:28:38 +00:00
Thibault Jouan
ace86cf0e3 Fix how layout delegation is tested (UAT) 2015-04-28 09:20:42 +00:00
Thibault Jouan
daa028e794 Improve cli require cucumber scenario 2015-04-28 09:10:16 +00:00
Thibault Jouan
258812013b Refactor some steps in cucumber scenarios 2015-04-28 08:37:04 +00:00
Thibault Jouan
66413b6ccb Bump patch version 2015-04-28 08:22:28 +00:00
Thibault Jouan
1332be5f39 Include GeoAccessors module in Client 2015-04-28 08:22:09 +00:00
Thibault Jouan
9199efaeda Bump patch version 2015-04-28 07:50:40 +00:00
Thibault Jouan
7d7db62f0d Bump uh dependency requirement 2015-04-28 07:34:59 +00:00
Thibault Jouan
ed478c51b2 Update uh dependency requirement to 2.0.0.pre4 2015-04-28 06:09:25 +00:00
Thibault Jouan
56bf7fb03a Update uh dependency requirement to 2.0.0.pre3 2015-04-28 05:54:18 +00:00
Thibault Jouan
ba28838441 Update documented feature list 2015-04-28 03:05:39 +00:00
Thibault Jouan
a649a2b450 Fix layout protocol explanation in documentation 2015-04-27 23:50:05 +00:00
Thibault Jouan
e8647a938b Update introduction in documentation 2015-04-27 23:48:36 +00:00
Thibault Jouan
1e2bf97762 Fix capitalization in documented feature list 2015-04-27 23:45:34 +00:00
Thibault Jouan
42beda3f50 Bump patch version 2015-04-27 06:55:46 +00:00
Thibault Jouan
c8106d2ef5 Update uh-layout dependency to ~> 0.4.0 2015-04-27 06:55:08 +00:00
Thibault Jouan
341e6fd4d2 Bump patch version 2015-04-26 08:23:31 +00:00
Thibault Jouan
f9d905e498 Merge branch 'layout-rc-config' 2015-04-26 02:55:15 +00:00
Thibault Jouan
7c2712924a Allow layout options configuration from run control 2015-04-26 02:49:39 +00:00
Thibault Jouan
70fe9bdb03 Update uh-layout dependency to ~> 0.3.0 2015-04-26 02:43:39 +00:00
Thibault Jouan
bf6f08e848 Implement `layout' run control keyword
Allow configuration of the layout by specifying either a class or an
instance.
2015-04-26 00:59:24 +00:00
Thibault Jouan
7c06b52664 Update wording in documented feature list 2015-04-24 20:17:51 +00:00
Thibault Jouan
85f799174a Fix markdown formatting in README 2015-04-24 20:15:58 +00:00
Thibault Jouan
4af83c181a Update README 2015-04-24 20:14:50 +00:00
Thibault Jouan
3a338e2bb7 Fix coding standards (use one-line blocks) 2015-04-24 19:58:27 +00:00
Thibault Jouan
f5bd8872c5 Remove debug comment 2015-04-24 19:57:51 +00:00
Thibault Jouan
cd06f99e9f Fix initial events handling for mux worker
* Rename `before_wait' worker callback to `before_watch';
* Setup `before_watch' callback in the runner, handling events with the
  manager;
* Test the mux worker with a cucumber scenario.
2015-04-24 19:53:08 +00:00
Thibault Jouan
e68d3fd6a3 Fix coding standards (runner spec descriptions) 2015-04-24 19:53:08 +00:00
Thibault Jouan
6cfed91653 Test blocking worker (UAT) 2015-04-23 21:25:58 +00:00
Thibault Jouan
eaa1d279dc Refactor Manager 2015-04-22 17:19:41 +00:00
Thibault Jouan
4e4b15871e Merge branch 'window-expose-handling'
Handle X expose events, generate an :expose internal event, and
support #expose layout protocol message.
2015-04-22 16:34:52 +00:00
Thibault Jouan
ddf875eab2 Test layout protocol for #expose message 2015-04-22 16:34:34 +00:00
Thibault Jouan
009d5dbedd Handle and forward expose events to the layout 2015-04-22 09:55:25 +00:00
Thibault Jouan
eb9c8d16db Update uh-layout dependency requirement 2015-04-22 09:50:14 +00:00
Thibault Jouan
44a5be6bd9 Merge branch 'handle-window-property-changes' 2015-04-22 08:14:51 +00:00
Thibault Jouan
a421b496c0 Test layout protocol for #update message 2015-04-22 08:14:45 +00:00
Thibault Jouan
accbebe97c Test how the manager handles window changes 2015-04-22 08:14:45 +00:00
Thibault Jouan
95245e1178 Forward :change events to Layout#update in runner 2015-04-22 08:14:45 +00:00
Thibault Jouan
0601fe0560 Handle property_notify events in Manager#handle 2015-04-22 08:14:45 +00:00
Thibault Jouan
b1a601918e Implement Manager#update_properties 2015-04-22 08:14:45 +00:00
Thibault Jouan
933bcdfcd8 Make the manager listen for changes on new window
When mapping a new window, listen for property notify events.
2015-04-22 08:14:45 +00:00
Thibault Jouan
03a6c323ac Implement Client#update_window_properties 2015-04-22 08:14:45 +00:00
Thibault Jouan
fb4af963eb Update uh-layout dependency requirement to 0.2.2 2015-04-22 08:14:45 +00:00
Thibault Jouan
b58a143aff Fix coding standards in Runner specs 2015-04-22 08:14:32 +00:00
Thibault Jouan
b8e31e1981 Fix typo in Manager#handle specs 2015-04-22 08:14:32 +00:00
Thibault Jouan
a7fe5d767f Avoid Manager#map dependency in Manager specs 2015-04-22 08:14:32 +00:00
Thibault Jouan
b3a82fcf92 Fix how uh-wm is tested for readiness in UAT 2015-04-21 13:58:25 +00:00
Thibault Jouan
730527b836 Fix non-deterministic user acceptance tests 2015-04-21 13:46:54 +00:00
Thibault Jouan
47bc9c9da2 Refactor specs with factory helper methods 2015-04-21 12:39:21 +00:00
Thibault Jouan
2cad5da620 Fix description wording in Client#to_s specs 2015-04-21 12:19:47 +00:00
Thibault Jouan
3776abf405 Fix coding standards 2015-04-21 12:12:20 +00:00
Thibault Jouan
8ed77a0460 Remove deprecated specs 2015-04-21 12:05:43 +00:00
Thibault Jouan
8afb8961f0 Remove dead code 2015-04-21 12:03:11 +00:00
Thibault Jouan
ff2fc8d70d Fix coding standards (specs) 2015-04-21 11:44:49 +00:00
Thibault Jouan
ae3d78497f Remove deprecated specs 2015-04-21 11:42:56 +00:00
Thibault Jouan
cc66b6a760 Accept code as block in ActionsHandler#evaluate 2015-04-21 11:34:01 +00:00
Thibault Jouan
e8dacfa4ac Fix non-deterministic test in layout/unmanage 2015-04-21 08:20:37 +00:00
Thibault Jouan
1d9571766b Refactor cucumber X steps 2015-04-21 08:08:06 +00:00
Thibault Jouan
7ee70b666b Increase layout protocol test coverage (#remove) 2015-04-21 08:02:05 +00:00
Thibault Jouan
4e2ec5f6ec Remove extra cucumber step in session/termination 2015-04-21 07:33:36 +00:00
Thibault Jouan
6346eb5ee6 Simplify cucumber X steps 2015-04-21 07:24:03 +00:00
Thibault Jouan
c7bd6ea28a Fix non-deterministic test in manager/unmanage 2015-04-21 06:59:07 +00:00
Thibault Jouan
ad67456c52 Fix coding standards in UAT helpers 2015-04-21 06:07:47 +00:00
Thibault Jouan
3fd8658adf Simplify cucumber steps (uhwm process) 2015-04-21 06:01:23 +00:00
Thibault Jouan
a4b6127ec8 Refactor cucumber steps (quitting uhwm) 2015-04-21 05:57:37 +00:00
Thibault Jouan
0b7c3e9128 Add license information in gem specification 2015-04-21 05:54:02 +00:00
Thibault Jouan
2cca771ad0 Bump minor version 2015-04-21 05:48:40 +00:00
Thibault Jouan
e2c46d096c Document project description and features 2015-04-21 05:48:05 +00:00
Thibault Jouan
cc401c8733 Update documented usage 2015-04-21 05:04:17 +00:00
Thibault Jouan
04d999e1b0 Fix non-deterministic test in manager/unmanage 2015-04-21 03:41:14 +00:00
Thibault Jouan
3849471a1b Merge branch 'window-destroy-handling' 2015-04-21 02:26:45 +00:00
Thibault Jouan
29f2b4725d Test how manager should handle destroyed windows 2015-04-21 02:26:29 +00:00
Thibault Jouan
e97dcea87a Handle destroy_notify X events in manager 2015-04-21 02:26:29 +00:00
Thibault Jouan
31a326e7eb Implement Manager#destroy 2015-04-21 02:26:29 +00:00
Thibault Jouan
0423d6df9d Update uh dependency requirement to 2.0.0.pre2 2015-04-21 02:26:29 +00:00
Thibault Jouan
4c9342c2f2 Increase default wait timeout in UAT helpers 2015-04-21 02:22:25 +00:00
Thibault Jouan
42751993a2 Test manager client unmanagement with cucumber 2015-04-21 02:22:21 +00:00
Thibault Jouan
a15af6f0ab Merge branch 'window-unmap-handling'
Handle unmap notify event in the manager, generate :unmanage events
with the client, and forward them to the layout.
2015-04-20 10:24:51 +00:00
Thibault Jouan
a6edac962b Add layout client unmanagement feature 2015-04-20 10:24:39 +00:00
Thibault Jouan
38a90ab0af Register for layout unmanage handling in runner 2015-04-20 10:24:39 +00:00
Thibault Jouan
a052140921 Handle unmap_notify X events in Manager 2015-04-20 10:24:39 +00:00
Thibault Jouan
09d7b6ebb5 Implement Manager#unmap 2015-04-20 10:24:39 +00:00
Thibault Jouan
7722909c22 Allow Client unmap_count accessor to be set 2015-04-20 10:24:39 +00:00
Thibault Jouan
b156665236 Add #x_window_unmap to UAT helpers 2015-04-20 10:24:39 +00:00
Thibault Jouan
9a4f2727d2 Supports multiple clients in UAT helpers 2015-04-20 10:24:39 +00:00
Thibault Jouan
f2c97499ce Fix wording in Runner#register_event_hooks specs 2015-04-20 10:24:27 +00:00
Thibault Jouan
a7941d51cc Split Runner#register_event_hooks spec with context 2015-04-20 10:24:27 +00:00
Thibault Jouan
c71fed4108 Reorder Manager specs 2015-04-20 10:24:27 +00:00
Thibault Jouan
f15fa116c9 Rename Manager#manage as Manager#map 2015-04-20 07:14:55 +00:00
Thibault Jouan
d8994dc165 Fix wording in Manager specs 2015-04-20 07:14:45 +00:00
Thibault Jouan
77226594fa Merge branch 'configure-requests-handling'
Handle configure requests events: asks the layout to suggest a geo,
generates configure events for new windows, else just configure the
client if it is known, or return a default hard-coded geometry.
2015-04-20 05:50:05 +00:00
Thibault Jouan
30cb15b5d3 Register :configure event for layout in runner 2015-04-20 05:49:51 +00:00
Thibault Jouan
84d92798a0 Modify manager to handle configure request events 2015-04-20 05:49:51 +00:00
Thibault Jouan
1f7b2269c7 Implement Manager#configure 2015-04-20 05:49:51 +00:00
Thibault Jouan
7fc695f258 Implement Client#configure 2015-04-20 05:49:51 +00:00
Thibault Jouan
c69a600ca4 Return last hook return value in Dispatcher#emit 2015-04-20 05:49:51 +00:00
Thibault Jouan
7c440e28d2 Fix manager/manage feature 2015-04-20 05:49:33 +00:00
Thibault Jouan
e9baae2962 Remove logging for worker read events 2015-04-20 05:37:08 +00:00
Thibault Jouan
ae2beeac20 Change wording in layout logging 2015-04-20 05:37:08 +00:00
Thibault Jouan
bdbeca399a Ensure only one client is managed for given window 2015-04-20 04:11:41 +00:00
Thibault Jouan
036ca1f989 Log X errors 2015-04-20 04:08:55 +00:00
Thibault Jouan
02861a63d4 Prevent manager from handling override_redirect windows 2015-04-19 04:35:37 +00:00
Thibault Jouan
c51d1525ee Delegates `layout_*' messages as Layout#handle_* 2015-04-19 02:13:26 +00:00
Thibault Jouan
d47e7de6d0 Log a message when `quit' action is invoked 2015-04-19 01:50:48 +00:00
Thibault Jouan
5e057ebab3 Fix default arguments in rake `run' task 2015-04-19 01:49:17 +00:00
Thibault Jouan
5e18dbae95 Change default quit key binding to mod+shift+q 2015-04-19 01:28:03 +00:00
Thibault Jouan
6c7b01c9aa Accept modifier in `key' run control keyword 2015-04-19 01:12:35 +00:00
Thibault Jouan
231e72df42 Fix travis CI errors when before_install timeout 2015-04-18 20:27:08 +00:00
Thibault Jouan
26bac5aea9 Test that manager listen to appropriate events 2015-04-18 20:11:04 +00:00
Thibault Jouan
20cdec5fce Fix constant resolution in layout CLI option 2015-04-18 19:53:23 +00:00
Thibault Jouan
68cec67402 Fix uhwm_wait_output acceptance helper with strings 2015-04-18 19:52:43 +00:00
Thibault Jouan
78dffde6c9 Remove a comment left in cucumber steps 2015-04-18 19:42:03 +00:00
Thibault Jouan
82a4161cc2 Improve UAT on layout
* Move cucumber features regarding the layout protocol in
  layout/protocol;
* Add new cucumber features to test uh-layout integration in uh-wm, at
  least simple mapping and focus behavior;
* Add new acceptance testing helpers
2015-04-18 19:33:47 +00:00
Thibault Jouan
2894783b57 Configure travis CI to send email notifications 2015-04-18 18:05:04 +00:00
Thibault Jouan
a9466a49f0 Refactor env logging common usages with a module 2015-04-18 18:04:46 +00:00
57 changed files with 1202 additions and 264 deletions

View File

@ -5,8 +5,11 @@ 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
notifications:
email:
- tj+travis_uh_wm@a13.fr

View File

@ -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
``` ```

View File

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

View File

@ -1 +1,2 @@
default: --require features/support --require features/steps default: --require features/support --require features/steps --no-source
quiet: --format progress

View 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

View 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

View 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 "- - - - - - - - - - - - - - - - - - - - - - -"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

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

View File

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

View 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+/

View File

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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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/

View File

@ -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"

View 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_{}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View File

@ -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'

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

@ -0,0 +1,8 @@
module Uh
module WM
module EnvLogging
extend Forwardable
def_delegators :@env, :log, :log_error, :log_debug
end
end
end

View File

@ -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!

View File

@ -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] :

View File

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

View File

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

View File

@ -1,5 +1,5 @@
module Uh module Uh
module WM module WM
VERSION = '0.0.1' VERSION = '0.0.6'
end end
end end

View File

@ -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 = []

View File

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

View File

@ -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
View 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

View File

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

View File

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

View File

@ -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 }

View File

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

View File

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

View File

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

View File

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

View File

@ -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'