Compare commits

..

3 Commits

Author SHA1 Message Date
Thibault Jouan
6f9a2c94be Fix the build 2015-04-18 17:38:39 +00:00
Thibault Jouan
2567f1c847 Break the build 2015-04-18 17:25:57 +00:00
Thibault Jouan
3321331fc4 Configure travis CI to send email notifications 2015-04-18 17:25:13 +00:00
57 changed files with 262 additions and 1197 deletions

View File

@ -5,8 +5,8 @@ rvm:
- ruby-head
- 2.1
before_install:
- travis_retry sudo apt-get update -qq
- travis_retry sudo apt-get install -qq twm x11-utils xdotool
- sudo apt-get update -qq
- sudo apt-get install -qq twm x11-utils xdotool
matrix:
allow_failures:
- rvm: ruby-head

View File

@ -1,40 +1,17 @@
uh-wm
=====
Xlib Window Manager prototype.
[![Version ][badge-version-img]][badge-version-uri]
[![Build status ][badge-build-img]][badge-build-uri]
[![Code Climate ][badge-cclimate-img]][badge-cclimate-uri]
uh-wm is a minimalistic tiling and stacking window manager for X. It
shares some similarities with dwm and wmii, but is written in ruby so
you can configure and extend features directly with ruby code.
The layout strategy is interchangeable, the default one being the
`uh-layout` ruby gem. A layout is a simple ruby object responding to
specific messages.
Main features:
* Xinerama support;
* multiple event handling strategy: blocking or multiplexing
with `select()`;
* configuration with a run control file (ruby DSL);
* key bindings with user defined code as callback;
* configurable modifier key;
* support user-defined layout strategies;
* program execution;
* no re-parenting (therefore, no window decoration either);
* no grabbing of the modifier key alone;
* no mouse handling;
* no EWMH support;
* very limited ICCCM support.
Getting started
---------------
### Installation (requires ruby ~> 2.1 with rubygems)
### Installation (requires ruby ~> 2.1 and rubygems)
```
$ gem install uh-wm
@ -47,15 +24,13 @@ $ gem install uh-wm
Usage: uhwm [options]
options:
-h, --help print this message
-v, --verbose enable verbose mode
-d, --debug enable debug mode
-f, --run-control PATH specify alternate run control file
-r, --require PATH require ruby feature
-l, --layout LAYOUT specify layout
-w, --worker WORKER specify worker
-h, --help print this message
-V, --version print version
```

View File

@ -10,19 +10,13 @@ XEPHYR_SCREENS_XINERAMA =
task default: %i[features spec]
Cucumber::Rake::Task.new :features do |t|
t.profile = 'quiet' if ENV.key? 'TRAVIS'
end
Cucumber::Rake::Task.new(:features)
RSpec::Core::RakeTask.new do |t|
t.rspec_opts = '--format progress' if ENV.key? 'TRAVIS'
end
RSpec::Core::RakeTask.new
desc 'Run uhwm in a Xephyr X server'
task :run do
uhwm_args = ARGV.include?('--') ?
ARGV.slice_after('--').to_a.last :
%w[-d]
uhwm_args = ARGV.slice_after('--').to_a.last || %w[-d]
Tempfile.create('uhwm_xinitrc') do |xinitrc|
xinitrc.write <<-eoh
[ -f $HOME/.Xdefaults ] && xrdb $HOME/.Xdefaults

View File

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

View File

@ -1,11 +0,0 @@
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

@ -1,28 +0,0 @@
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

@ -1,9 +0,0 @@
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,14 +4,8 @@ Feature: layout CLI option
Given a file named layout.rb with:
"""
class MyLayout
def register *_
puts 'testing_cli_layout'
end
def register _; end
end
"""
When I run uhwm with options -r./layout -l MyLayout
Then the output must contain "testing_cli_layout"
Scenario: resolves layout class from the root namespace
When I run uhwm with option -l Layout
Then the output must contain "uninitialized constant Layout"
When I run uhwm with option -v -r./layout -l MyLayout
Then the output must match /layout.+mylayout/i

View File

@ -1,9 +1,5 @@
Feature: require CLI option
Scenario: requires a ruby feature
Given a file named my_feature.rb with:
"""
puts 'testing_feature_load'
"""
When I run uhwm with option -r ./my_feature.rb
Then the output must contain "testing_feature_load"
When I run uhwm with option -v -r abbrev
Then the output must match /load.+abbrev.+ruby feature/i

View File

@ -3,7 +3,7 @@ Feature: run control file path CLI option
Scenario: specifies run control file path
Given a file named uhwmrc.rb with:
"""
puts 'testing_run_control'
puts 'run control evaluation'
"""
When I run uhwm with option -f uhwmrc.rb
Then the output must contain "testing_run_control"
Then the output must contain "run control evaluation"

View File

@ -1,9 +1,9 @@
Feature: worker CLI option
Scenario: uses the blocking worker when `block' is given
When I run uhwm with options -v -w block
When I run uhwm with option -v -w block
Then the output must match /work.+event.+block/i
Scenario: uses the multiplexing worker when `mux' is given
When I run uhwm with options -v -w mux
When I run uhwm with option -v -w mux
Then the output must match /work.+event.+mux/i

View File

@ -1,12 +1,16 @@
Feature: layout client management
Background:
Given uhwm is running
Scenario: sends the #<< message when telling the layout to manage a client
Given a file named layout.rb with:
"""
class Layout
def register *_; end
Scenario: maps new client window
def << client
puts client
end
end
"""
And uhwm is running with options -v -r./layout -l Layout
When a window requests to be mapped
Then the window must be mapped
Scenario: focuses new client window
When a window is mapped
Then the window must be focused
Then the output must contain the window name

View File

@ -1,56 +0,0 @@
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

@ -0,0 +1,13 @@
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

@ -1,10 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,5 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,13 +0,0 @@
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

@ -1,17 +0,0 @@
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,10 +1,18 @@
Feature: run control file evaluation
Scenario: evaluates the default run control file when present
Given a run control file with:
"""
puts 'run control evaluation'
"""
When I start uhwm
Then the output must contain "run control evaluation"
Scenario: reports run control code in backtrace on errors
Given a run control file with:
"""
'no error on first line'
fail 'testing_rc_failure'
fail 'fails on second line'
"""
When I start uhwm
Then the output must match /\.uhwmrc\.rb:2:.+testing_rc_failure/
Then the output must match /\.uhwmrc\.rb:2:.+fails on second line/

View File

@ -3,31 +3,23 @@ Feature: `key' run control keyword
Scenario: defines code to run when given key is pressed
Given uhwm is running with this run control file:
"""
key(:f) { puts 'testing_rc_key' }
key(:f) { puts 'trigger f key code' }
"""
When I press the alt+f keys
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"
Then the output must contain "trigger f key code"
Scenario: translates common key names to their X equivalent
Given uhwm is running with this run control file:
"""
key(:enter) { puts 'testing_rc_key' }
key(:enter) { puts 'trigger return key code' }
"""
When I press the alt+Return keys
Then the output must contain "testing_rc_key"
Then the output must contain "trigger return key code"
Scenario: translates upcased key names to combination with shift key
Given uhwm is running with this run control file:
"""
key(:F) { puts 'testing_rc_key' }
key(:F) { puts 'trigger shift+f key code' }
"""
When I press the alt+shift+f keys
Then the output must contain "testing_rc_key"
Then the output must contain "trigger shift+f key code"

View File

@ -1,39 +0,0 @@
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,9 +1,10 @@
Feature: `modifier' run control keyword
Scenario: configures the modifier key
Given uhwm is running with this run control file:
Given a run control file with:
"""
modifier :ctrl
"""
When I press the ctrl+shift+q keys
And uhwm is running
When I press the ctrl+q keys
Then uhwm must terminate successfully

View File

@ -1,8 +1,9 @@
Feature: `worker' run control keyword
Scenario: configures the modifier key
Given uhwm is running with this run control file:
Given a run control file with:
"""
worker :mux
"""
And I start uhwm
Then the output must match /work.+event.+mux/i

View File

@ -0,0 +1,5 @@
Feature: connection to X server
Scenario: connects to X server
When I start uhwm
Then it must connect to X display

View File

@ -9,4 +9,5 @@ Feature: program termination
Scenario: logs about termination
When I tell uhwm to quit
Then the output must match /terminat/i
Then uhwm must terminate successfully
And the output must match /terminat/i

View File

@ -16,6 +16,7 @@ options:
end
Then /^the output must contain exactly the version$/ do
#require File.expand_path('../lib/uh/wm/version', __FILE__)
assert_exact_output "%s\n" % Uh::WM::VERSION, all_output
end
@ -23,21 +24,6 @@ Then /^the output must match \/([^\/]+)\/([a-z]*)$/ do |pattern, options|
uhwm_wait_output Regexp.new(pattern, options)
end
Then /^the output must not match \/([^\/]+)\/([a-z]*)$/ do |pattern, options|
expect(all_output).not_to match Regexp.new(pattern, options)
end
Then /^the output must match \/([^\/]+)\/([a-z]*) at least (\d+) times$/ do
|pattern, options, times|
uhwm_wait_output Regexp.new(pattern, options), times.to_i
end
Then /^the output must match \/([^\/]+)\/([a-z]*) exactly (\d+) times$/ do
|pattern, options, times|
scans = uhwm_wait_output Regexp.new(pattern, options)
expect(scans.size).to eq times.to_i
end
Then /^the output must contain:$/ do |content|
uhwm_wait_output content.to_s
end

View File

@ -24,12 +24,7 @@ When /^I run uhwm with options? (-.+)$/ do |options|
end
When /^I tell uhwm to quit$/ do
uhwm_request_quit
end
When /^I quit uhwm$/ do
uhwm_request_quit
assert_exit_status 0
x_key 'alt+q'
end
Then /^the exit status must be (\d+)$/ do |exit_status|

View File

@ -1,68 +1,12 @@
Given /^an ICCCM compliant window is mapped$/ do
icccm_window_start
timeout_until 'window not mapped after %d seconds' do
x_window_map_state(icccm_window_name) == 'IsViewable'
end
end
Given /^a(?:\s(\w+))? window is mapped$/ do |ident|
x_client(ident).map.sync
timeout_until 'window not mapped after %d seconds' do
x_window_map_state(x_client(ident).window_id) == 'IsViewable'
end
end
When /^I press the ([^ ]+) keys?$/ do |keys|
x_key keys
end
When /^I press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
times.to_i.times { x_key keys }
end
When /^I quickly press the ([^ ]+) keys? (\d+) times$/ do |keys, times|
x_key [keys] * times.to_i, delay: 0
end
When /^a window requests to be mapped$/ do
x_client.map.sync
x_window_map
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
Then /^it must connect to X display$/ do
uhwm_wait_output 'Connected to'
expect(x_socket_check uhwm_pid).to be true
end

View File

@ -28,10 +28,6 @@ Around '@other_wm_running' do |_, block|
with_other_wm { block.call }
end
After '@icccm_window' do
icccm_window_ensure_stop
end
if ENV.key? 'TRAVIS'
ENV['UHWMTEST_TIMEOUT'] = 8.to_s
end

View File

@ -1,15 +0,0 @@
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

@ -1,15 +0,0 @@
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,8 +3,6 @@ require 'logger'
require 'optparse'
require 'uh'
require 'uh/wm/env_logging'
require 'uh/wm/actions_handler'
require 'uh/wm/cli'
require 'uh/wm/client'

View File

@ -1,75 +1,32 @@
module Uh
module WM
class ActionsHandler
include EnvLogging
extend Forwardable
def_delegator :@env, :layout
def initialize env, events
@env, @events = env, events
end
def evaluate code = nil, &block
if code
instance_exec &code
else
instance_exec &block
end
def evaluate code
instance_eval &code
end
def quit
log 'Quit requested'
@events.emit :quit
end
def execute command
log "Execute: #{command}"
@env.log "Execute: #{command}"
pid = fork do
fork do
Process.setsid
begin
exec command
rescue Errno::ENOENT => e
log_error "ExecuteError: #{e}"
@env.log_error "ExecuteError: #{e}"
end
end
end
Process.waitpid pid
end
def kill_current
layout.current_client.kill
end
def log_separator
log '- ' * 24
end
def method_missing(m, *args, &block)
if respond_to? m
meth = layout_method m
log "#{layout.class.name}##{meth} #{args.inspect}"
begin
layout.send(meth, *args)
rescue NoMethodError
log_error "Layout does not implement `#{meth}'"
end
else
super
end
end
def respond_to_missing?(m, *)
m.to_s =~ /\Alayout_/ || super
end
private
def layout_method(m)
m.to_s.gsub(/\Alayout_/, 'handle_').to_sym
end
end
end
end

View File

@ -3,8 +3,6 @@ module Uh
class CLI
ArgumentError = Class.new(ArgumentError)
include EnvLogging
USAGE = "Usage: #{File.basename $0} [options]".freeze
EX_USAGE = 64
@ -65,10 +63,10 @@ module Uh
end
opts.on '-r', '--require PATH', 'require ruby feature' do |feature|
require feature
log "Loaded `#{feature}' ruby feature"
@env.log "Loaded `#{feature}' ruby feature"
end
opts.on '-l', '--layout LAYOUT', 'specify layout' do |layout|
@env.layout_class = Object.const_get layout.to_sym
@env.layout_class = self.class.const_get layout.to_sym
end
opts.on '-w', Workers.types, '--worker WORKER',
'specify worker' do |worker|

View File

@ -1,10 +1,8 @@
module Uh
module WM
class Client
include GeoAccessors
attr_reader :window
attr_accessor :geo, :unmap_count
attr_reader :window, :unmap_count
attr_accessor :geo
def initialize window, geo = nil
@window = window
@ -33,16 +31,6 @@ module Uh
@wclass ||= @window.wclass
end
def update_window_properties
@wname = @window.name
@wclass = @window.wclass
end
def configure
@window.configure @geo
self
end
def moveresize
@window.moveresize @geo
self
@ -66,20 +54,6 @@ module Uh
@window.focus
self
end
def kill
if @window.icccm_wm_protocols.include? :WM_DELETE_WINDOW
@window.icccm_wm_delete
else
@window.kill
end
self
end
def kill!
window.kill
self
end
end
end
end

View File

@ -16,9 +16,7 @@ module Uh
end
def emit *key, args: []
value = nil
@hooks[translate_key key].each { |e| value = e.call *args }
value
@hooks[translate_key key].each { |e| e.call *args }
end

View File

@ -5,7 +5,7 @@ module Uh
MODIFIER = :mod1
KEYBINDS = {
[:q, :shift] => proc { quit }
q: proc { quit }
}.freeze
WORKER = :block
@ -21,8 +21,8 @@ module Uh
def_delegators :@output, :print, :puts
attr_reader :output, :keybinds
attr_accessor :verbose, :debug, :rc_path, :modifier, :worker,
:layout, :layout_class
attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier,
:worker
def initialize output
@output = output

View File

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

View File

@ -6,7 +6,6 @@ module Uh
Events::SUBSTRUCTURE_REDIRECT_MASK |
Events::SUBSTRUCTURE_NOTIFY_MASK |
Events::STRUCTURE_NOTIFY_MASK
DEFAULT_GEO = Geo.new(0, 0, 320, 240).freeze
attr_reader :modifier, :display, :clients
@ -46,42 +45,6 @@ module Uh
@display.grab_key keysym.to_s, mod_mask
end
def configure window
if client = client_for(window)
client.configure
else
geo = @events.emit :configure, args: window
window.configure_event geo ? geo : DEFAULT_GEO
end
end
def map window
return if window.override_redirect? || client_for(window)
@clients << client = Client.new(window)
@events.emit :manage, args: client
@display.listen_events window, Events::PROPERTY_CHANGE_MASK
end
def unmap window
return unless client = client_for(window)
if client.unmap_count > 0
client.unmap_count -= 1
else
unmanage client
end
end
def destroy window
return unless client = client_for(window)
unmanage client
end
def update_properties window
return unless client = client_for(window)
client.update_window_properties
@events.emit :change, args: client
end
def handle_next_event
handle @display.next_event
end
@ -100,7 +63,7 @@ module Uh
private
def handle_error *args
@events.emit :xerror, args: args
@dispatcher.emit :error, args: args
end
def handle_key_press event
@ -110,37 +73,9 @@ module Uh
@events.emit :key, *key_selector
end
def handle_configure_request event
configure event.window
end
def handle_destroy_notify event
destroy event.window
end
def handle_expose event
@events.emit :expose, args: event.window
end
def handle_map_request event
map event.window
end
def handle_property_notify event
update_properties event.window
end
def handle_unmap_notify event
unmap event.window
end
def client_for window
@clients.find { |e| e.window == window }
end
def unmanage client
@clients.delete client
@events.emit :unmanage, args: client
@clients << client = Client.new(event.window)
@events.emit :manage, args: client
end
def check_other_wm!

View File

@ -28,20 +28,8 @@ module Uh
@env.modifier = mod
end
def key *keysyms, &block
@env.keybinds[translate_keysym *keysyms] = block
end
def layout arg, **options
if arg.is_a? Class
if options.any?
@env.layout = arg.new options
else
@env.layout_class = arg
end
else
@env.layout = arg
end
def key keysym, &block
@env.keybinds[translate_keysym keysym] = block
end
def worker type, **options
@ -51,8 +39,7 @@ module Uh
private
def translate_keysym keysym, modifier = nil
return [translate_keysym(keysym)[0].to_sym, modifier] if modifier
def translate_keysym keysym
translate_key = keysym.to_s.downcase.to_sym
translated_keysym = KEYSYM_TRANSLATIONS.key?(translate_key) ?
KEYSYM_TRANSLATIONS[translate_key] :

View File

@ -1,8 +1,6 @@
module Uh
module WM
class Runner
include EnvLogging
class << self
def run env, **options
runner = new env, **options
@ -51,17 +49,24 @@ module Uh
def connect_manager
manager.connect
@env.keybinds.each { |keysym, _| manager.grab_key *keysym }
@env.keybinds.each do |keysym, _|
manager.grab_key *keysym
end
end
def worker
@worker ||= Workers.build(*(@env.worker)).tap do |w|
w.before_watch { @manager.handle_pending_events }
w.on_read { @manager.handle_pending_events }
w.on_read_next { @manager.handle_next_event }
w.on_read do
@env.log_debug 'Processing pending events'
@manager.handle_pending_events
end
w.on_read_next do
@env.log_debug 'Processing next event'
@manager.handle_next_event
end
w.on_timeout do |*args|
log_debug "Worker timeout: #{args.inspect}"
log_debug 'Flushing X output buffer'
@env.log_debug "Worker timeout: #{args.inspect}"
@env.log_debug 'Flushing X output buffer'
@manager.flush
end
end
@ -69,12 +74,12 @@ module Uh
def run_until &block
worker.watch @manager
log "Working events with `#{worker.class}'"
@env.log "Working events with `#{worker.class}'"
worker.work_events until block.call
end
def terminate
log "Terminating..."
@env.log "Terminating..."
manager.disconnect
end
@ -87,41 +92,24 @@ module Uh
def register_manager_hooks
@events.on :connecting do |display|
log_debug "Connecting to X server on `#{display}'"
@env.log_debug "Connecting to X server on `#{display}'"
end
@events.on :connected do |display|
log "Connected to X server on `#{display}'"
@env.log "Connected to X server on `#{display}'"
end
@events.on(:disconnected) { log "Disconnected from X server" }
@events.on(:disconnected) { @env.log "Disconnected from X server" }
@events.on(:xevent) { |event| XEventLogger.new(env).log_event event }
@events.on(:xerror) { |*error| XEventLogger.new(env).log_xerror *error }
end
def register_layout_hooks
@events.on :connected do |display|
log "Registering layout `#{layout.class}'"
@env.log "Registering layout `#{layout.class}'"
layout.register display
end
@events.on :configure do |window|
log "Configuring window: #{window}"
layout.suggest_geo
end
@events.on :manage do |client|
log "Managing client #{client}"
@env.log "Manage client #{client}"
layout << client
end
@events.on :unmanage do |client|
log "Unmanaging client #{client}"
layout.remove client
end
@events.on :change do |client|
log "Updating client #{client}"
layout.update client
end
@events.on :expose do |window|
log "Exposing window: #{window}"
layout.expose window
end
end
def register_keybinds_hooks
@ -132,8 +120,6 @@ module Uh
class XEventLogger
include EnvLogging
def initialize env
@env = env
end
@ -146,17 +132,13 @@ module Uh
"window: #{xev.window}"
end
log_debug [
@env.log_debug [
'XEvent',
xev.type,
xev.send_event ? 'SENT' : nil,
complement
].compact.join ' '
end
def log_xerror req, resource_id, msg
log_error "XERROR: #{resource_id} #{req} #{msg}"
end
end
end
end

View File

@ -4,67 +4,45 @@ module Uh
module WM
module Testing
module AcceptanceHelpers
TIMEOUT_DEFAULT = 2
QUIT_KEYBINDING = 'alt+shift+q'.freeze
LOG_READY = 'Working events'.freeze
def icccm_window_start
@icccm_window = ChildProcess.build(*%w[xmessage window])
@icccm_window.start
end
def icccm_window_ensure_stop
@icccm_window.stop
end
def icccm_window_name
'xmessage'
end
def uhwm_run options = '-v'
command = %w[uhwm]
command << options if options
@interactive = @process = run command.join ' '
end
def uhwm
@process
end
def uhwm_request_quit
x_key QUIT_KEYBINDING
end
def uhwm_ensure_stop
if @process
x_key 'alt+shift+q'
x_key 'alt+q'
@process.terminate
end
end
def uhwm_wait_output message, times = 1, value = nil
def uhwm_pid
@process.pid
end
def uhwm_output
@process.stdout
end
def uhwm_wait_output message
output = -> { @process.stdout + @process.stderr }
timeout_until do
case message
when Regexp then (value = output.call.scan(message)).size >= times
when String then output.call.include? message
when Regexp then output.call =~ message
when String then assert_partial_output_interactive message
end
end
value
rescue TimeoutError => e
fail <<-eoh
expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
```\n#{output.call.lines.map { |e| " #{e}" }.join} ```
eoh
end
def uhwm_wait_ready
uhwm_wait_output LOG_READY
fail [
"expected `#{message}' not seen after #{e.timeout} seconds in:",
" ```\n#{output.call.lines.map { |e| " #{e}" }.join} ```"
].join "\n"
end
def uhwm_run_wait_ready options = nil
if options then uhwm_run options else uhwm_run end
uhwm_wait_ready
uhwm_wait_output 'Connected to'
end
def with_other_wm
@ -78,48 +56,42 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
@other_wm
end
def x_client ident = nil
ident ||= :default
@x_clients ||= {}
@x_clients[ident] ||= XClient.new(ident)
def x_client
@x_client ||= XClient.new
end
def x_key key
fail "cannot simulate X key `#{key}'" unless system "xdotool key #{key}"
end
def x_socket_check pid
case RbConfig::CONFIG['host_os']
when /linux/
`netstat -xp 2> /dev/null`.lines.grep /\s+#{pid}\/ruby/
else
`sockstat -u`.lines.grep /\s+ruby.+\s+#{pid}/
end.any?
end
def x_window_name
@x_client.window_name
end
def x_window_map
x_client.map.sync
end
def x_clients_ensure_stop
@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]
@x_client and @x_client.terminate
end
private
def timeout_until message = 'condition not met after %d seconds'
def timeout_until
timeout = ENV.key?('UHWMTEST_TIMEOUT') ?
ENV['UHWMTEST_TIMEOUT'].to_i :
TIMEOUT_DEFAULT
1
Timeout.timeout(timeout) do
loop do
break if yield
@ -127,7 +99,7 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
end
end
rescue Timeout::Error
fail TimeoutError.new(message % timeout, timeout)
fail TimeoutError.new('execution expired', timeout)
end
@ -143,8 +115,8 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
class XClient
attr_reader :name
def initialize name = object_id
@name = "#{self.class.name.split('::').last}/#{name}"
def initialize
@name = "#{self.class.name.split('::').last}/#{object_id}"
@geo = Geo.new(0, 0, 640, 480)
@display = Display.new.tap { |o| o.open }
end
@ -158,37 +130,19 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
end
def window
@window ||= @display.create_window(@geo).tap { |o| o.name = @name }
end
def window_id
@window.id
@window ||= @display.create_window(@geo).tap do |o|
o.name = @name
end
end
def window_name
@name
end
def window_name= name
@name = @window.name = name
window.name
end
def map times: 1
times.times { window.map }
def map
window.map
self
end
def unmap
window.unmap
self
end
def destroy
window.destroy
self
end
end
end
end

View File

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

View File

@ -2,7 +2,7 @@ module Uh
module WM
module Workers
class Base
CALLBACKS = %w[before_watch on_timeout on_read on_read_next].freeze
CALLBACKS = %w[before_wait on_timeout on_read on_read_next].freeze
def initialize **options
@ios = []

View File

@ -2,15 +2,13 @@ module Uh
module WM
module Workers
class Mux < Base
TIMEOUT_DEFAULT = 1
def initialize timeout: TIMEOUT_DEFAULT
def initialize timeout: 1
super
@timeout = timeout
end
def work_events
@before_watch.call if @before_watch
@before_wait.call if @before_wait
if res = select(@ios, [], [], @timeout) then @on_read.call res
else @on_timeout.call if @on_timeout end
end

View File

@ -2,11 +2,7 @@ require 'headless'
require 'uh/wm'
Dir['spec/support/**/*.rb'].map { |e| require e.gsub 'spec/', '' }
RSpec.configure do |config|
config.include Factories
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end

View File

@ -1,28 +0,0 @@
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,15 +6,10 @@ module Uh
subject(:actions) { described_class.new env, events }
describe '#evaluate' do
it 'evaluates code given as Proc argument' do
it 'evaluates given code' do
expect { actions.evaluate proc { throw :action_code } }
.to throw_symbol :action_code
end
it 'evaluates code given as block' do
expect { actions.evaluate { throw :action_code } }
.to throw_symbol :action_code
end
end
describe '#quit' do
@ -23,35 +18,6 @@ module Uh
actions.quit
end
end
describe '#kill_current' do
let(:client) { instance_spy Client }
context 'when layout has a client' do
before do
allow(actions.layout).to receive(:current_client) { client }
end
it 'kills layout current client' do
expect(client).to receive :kill
actions.kill_current
end
end
end
describe '#log_separator' do
it 'logs a separator' do
expect(env).to receive(:log).with /(?:- ){20,}/
actions.log_separator
end
end
describe '#layout_*' do
it 'delegates messages to the layout with handle_ prefix' do
expect(env.layout).to receive :handle_screen_sel
actions.layout_screen_sel :succ
end
end
end
end
end

View File

@ -1,9 +1,11 @@
module Uh
module WM
RSpec.describe Client do
let(:protocols) { [] }
let(:window) { mock_window icccm_wm_protocols: protocols }
let(:geo) { build_geo }
let(:geo) { Geo.new(0, 0, 640, 480) }
let(:window) do
instance_spy Window, 'window', to_s: 'wid',
name: 'wname', wclass: 'wclass'
end
subject(:client) { described_class.new window, geo }
it 'is not visible' do
@ -31,7 +33,7 @@ module Uh
expect(client.to_s).to include geo.to_s
end
it 'includes window string representation' do
it 'includes window id' do
expect(client.to_s).to include 'wid'
end
end
@ -48,35 +50,6 @@ module Uh
end
end
describe '#update_window_properties' do
it 'updates the cached window name' do
client.name
allow(window).to receive(:name) { 'new name' }
expect { client.update_window_properties }
.to change { client.name }
.from('wname').to 'new name'
end
it 'updates the cached window class' do
client.wclass
allow(window).to receive(:wclass) { 'new class' }
expect { client.update_window_properties }
.to change { client.wclass }
.from('wclass').to 'new class'
end
end
describe '#configure' do
it 'configures the window with client geo' do
expect(window).to receive(:configure).with geo
client.configure
end
it 'returns self' do
expect(client.configure).to be client
end
end
describe '#moveresize' do
it 'moveresizes the window with client geo' do
expect(window).to receive(:moveresize).with geo
@ -144,37 +117,6 @@ module Uh
expect(client.focus).to be client
end
end
describe '#kill' do
it 'kills the window' do
expect(window).to receive :kill
client.kill
end
it 'returns self' do
expect(client.kill).to be client
end
context 'when window supports icccm wm delete' do
let(:protocols) { [:WM_DELETE_WINDOW] }
it 'icccm deletes the window' do
expect(window).to receive :icccm_wm_delete
client.kill
end
end
end
describe '#kill!' do
it 'kills the window' do
expect(window).to receive :kill
client.kill!
end
it 'returns self' do
expect(client.kill!).to be client
end
end
end
end
end

View File

@ -1,7 +1,7 @@
module Uh
module WM
RSpec.describe Dispatcher do
subject(:dispatcher) { described_class.new }
subject(:dispatcher) { described_class.new }
describe '#[]' do
context 'when given key for existing hook' do
@ -45,11 +45,6 @@ module Uh
expect { dispatcher.emit :hook_key }.to throw_symbol :hook_code
end
it 'returns the value returned by a registered hook' do
dispatcher.on(:hook_key) { :hook_code }
expect(dispatcher.emit :hook_key).to eq :hook_code
end
context 'when no hooks are registered for given key' do
it 'does not call another hook' do
dispatcher.on(:hook_key) { throw :hook_code }

View File

@ -25,8 +25,8 @@ module Uh
expect(env.modifier).to eq :mod1
end
it 'has default key binding for quit set' do
expect(env.keybinds.keys).to include [:q, :shift]
it 'has defaults key bindings set' do
expect(env.keybinds.keys).to eq %i[q]
end
it 'has the blocking worker by default' do
@ -70,27 +70,19 @@ module Uh
end
describe '#layout' do
let(:some_layout_class) { Class.new }
context 'when a layout class is set' do
let(:some_layout) { Class.new }
it 'returns the default layout' do
expect(env.layout).to be_an_instance_of ::Uh::Layout
end
before { env.layout_class = some_layout }
context 'when a layout is set' do
let(:some_layout) { some_layout_class.new }
before { env.layout = some_layout }
it 'returns the assigned layout' do
expect(env.layout).to be some_layout
it 'returns a new instance of this layout class' do
expect(env.layout).to be_an_instance_of some_layout
end
end
context 'when a layout class is set' do
before { env.layout_class = some_layout_class }
it 'returns a new instance of this layout class' do
expect(env.layout).to be_an_instance_of some_layout_class
context 'when a layout class is not set' do
it 'returns an instance of the default layout' do
expect(env.layout).to be_an_instance_of ::Uh::Layout
end
end
end
@ -137,8 +129,7 @@ module Uh
describe '#log_logger_level' do
it 'logs the logger level' do
expect(env.logger)
.to receive(:info).with /log.+(warn|info|debug).+level/i
expect(env.logger).to receive(:info).with /log.+(warn|info|debug).+level/i
env.log_logger_level
end
end

View File

@ -2,13 +2,15 @@ module Uh
module WM
RSpec.describe Manager do
let(:block) { proc { } }
let(:window) { mock_window }
let(:client) { build_client window }
let(:events) { Dispatcher.new }
let(:modifier) { :mod1 }
let(:display) { Display.new }
subject(:manager) { described_class.new events, modifier, display }
it 'has a new display' do
expect(manager.display).to be_a Display
end
it 'has no clients' do
expect(manager.clients).to be_empty
end
@ -104,174 +106,6 @@ module Uh
end
end
describe '#configure' do
context 'with new window' do
it 'sends a configure event to the window with a default geo' do
expect(window)
.to receive(:configure_event).with(build_geo 0, 0, 320, 240)
manager.configure window
end
context 'when :configure event returns a geo' do
it 'sends a configure event with geo returned by event' do
geo = build_geo 0, 0, 42, 42
events.on(:configure) { geo }
expect(window).to receive(:configure_event).with geo
manager.configure window
end
end
end
context 'with known window' do
before { manager.clients << client }
it 'tells the client to configure' do
expect(client).to receive :configure
manager.configure window
end
end
end
describe '#map' do
let(:display) { instance_spy Display }
it 'registers a new client wrapping the given window' do
manager.map window
expect(manager.clients[0])
.to be_a(Client)
.and have_attributes(window: window)
end
it 'registers new client only once for a given window' do
manager.map window
expect { manager.map window }.not_to change { manager.clients }
end
it 'ignores event when window has override redirect' do
allow(window).to receive(:override_redirect?) { true }
expect { manager.map window }.not_to change { manager.clients }
end
it 'emits :manage event with the registered client' do
events.on :manage, &block
expect(block).to receive :call do |client|
expect(client)
.to be_a(Client)
.and have_attributes(window: window)
end
manager.map window
end
it 'listens for property notify events on given window' do
expect(display)
.to receive(:listen_events)
.with window, Events::PROPERTY_CHANGE_MASK
manager.map window
end
end
describe '#unmap' do
before { manager.clients << client }
context 'when client unmap count is 0 or less' do
it 'preserves the client unmap count' do
expect { manager.unmap window }.not_to change { client.unmap_count }
end
it 'unregisters the client' do
manager.unmap window
expect(manager.clients).not_to include client
end
it 'emits :unmanage event with the client' do
events.on :unmanage, &block
expect(block).to receive(:call).with client
manager.unmap window
end
end
context 'when client unmap count is strictly positive' do
before { client.unmap_count += 1 }
it 'does not unregister the client' do
manager.unmap window
expect(manager.clients).to include client
end
it 'decrements the unmap count' do
manager.unmap window
expect(client.unmap_count).to eq 0
end
end
context 'with unknown window' do
let(:unknown_window) { window.dup }
it 'does not change registered clients' do
expect { manager.unmap unknown_window }
.not_to change { manager.clients }
end
it 'does not emit any event' do
expect(events).not_to receive :emit
manager.unmap unknown_window
end
end
end
describe '#destroy' do
before { manager.clients << client }
it 'unregisters the client' do
manager.destroy window
expect(manager.clients).not_to include client
end
it 'emits :unmanage event with the client' do
events.on :unmanage, &block
expect(block).to receive(:call).with client
manager.destroy window
end
context 'with unknown window' do
let(:unknown_window) { window.dup }
it 'does not change registered clients' do
expect { manager.destroy unknown_window }
.not_to change { manager.clients }
end
it 'does not emit any event' do
expect(events).not_to receive :emit
manager.destroy unknown_window
end
end
end
describe '#update_properties' do
context 'with known window' do
before { manager.clients << client }
it 'tells the client to update its window properties' do
expect(client).to receive :update_window_properties
manager.update_properties window
end
it 'emits :change event with the client' do
events.on :change, &block
expect(block).to receive(:call).with client
manager.update_properties window
end
end
context 'with unknown window' do
it 'does not emit any event' do
expect(events).not_to receive :emit
manager.update_properties window
end
end
end
describe '#handle_next_event' do
it 'handles the next available event on display' do
event = double 'event'
@ -282,7 +116,7 @@ module Uh
end
describe '#handle_pending_events' do
let(:event) { mock_event }
let(:event) { double 'event' }
context 'when an event is pending on display' do
before do
@ -310,7 +144,7 @@ module Uh
end
describe '#handle' do
let(:event) { mock_event }
let(:event) { double 'event', type: :any }
it 'emits :xevent event with the X event' do
events.on :xevent, &block
@ -318,8 +152,13 @@ module Uh
end
context 'when key_press event is given' do
let(:mod_mask) { KEY_MODIFIERS[modifier] }
let(:event) { mock_event_key_press 'f', mod_mask }
let(:mod_mask) { KEY_MODIFIERS[modifier] }
let(:event) do
double 'event',
type: :key_press,
key: 'f',
modifier_mask: mod_mask
end
it 'emits :key event with the corresponding key' do
events.on(:key, :f) { throw :key_press_code }
@ -336,47 +175,23 @@ module Uh
end
end
context 'when configure request event is given' do
let(:event) { mock_event :configure_request, window: :window }
it 'configures the event window' do
expect(manager).to receive(:configure).with :window
manager.handle event
end
end
context 'when destroy_notify event is given' do
let(:event) { mock_event :destroy_notify, window: :window }
it 'destroy the event window' do
expect(manager).to receive(:destroy).with :window
manager.handle event
end
end
context 'when map_request event is given' do
let(:event) { mock_event :map_request, window: :window }
let(:event) { double 'event', type: :map_request, window: :window }
it 'maps the event window' do
expect(manager).to receive(:map).with :window
it 'registers a new client wrapping the event window' do
manager.handle event
expect(manager.clients[0])
.to be_a(Client)
.and have_attributes(window: :window)
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
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.handle event
end
end

View File

@ -5,6 +5,7 @@ module Uh
RSpec.describe RunControl do
include FileSystemHelpers
let(:code) { :run_control_code }
let(:env) { Env.new(StringIO.new) }
subject(:rc) { described_class.new env }
@ -27,6 +28,14 @@ module Uh
allow(described_class).to receive(:new) { rc }
described_class.evaluate env
end
context 'when run control file is not present' do
before { env.rc_path = 'non_existent_rc_file.rb' }
it 'does not raise any error' do
expect { described_class.evaluate env }.not_to raise_error
end
end
end
describe '#evaluate' do
@ -56,11 +65,6 @@ module Uh
expect(env.keybinds.keys).to include :f
end
it 'registers a combined keys binding in the env' do
rc.key :f, :shift, &code
expect(env.keybinds.keys).to include %i[f shift]
end
it 'registers given block with the key binding' do
rc.key :f, &code
expect(env.keybinds[:f].call).to eq :keybind_code
@ -77,31 +81,6 @@ module Uh
end
end
describe '#layout' do
context 'when given a class' do
let(:layout_class) { Class.new }
it 'sets a layout class in the env' do
rc.layout layout_class
expect(env.layout_class).to be layout_class
end
context 'when given options' do
it 'instantiates the class with given options' do
expect(layout_class).to receive(:new).with(foo: :bar)
rc.layout layout_class, foo: :bar
end
end
end
context 'when given an object' do
it 'sets a layout instance in the env' do
rc.layout layout = Object.new
expect(env.layout).to eq layout
end
end
end
describe '#worker' do
it 'sets the worker type in the env' do
rc.worker :some_worker

View File

@ -1,11 +1,6 @@
SomeLayout = Class.new do
include Factories
define_method(:register) { |*_| }
define_method(:suggest_geo) { build_geo 0, 0, 42, 42 }
define_method(:<<) { |*_| }
define_method(:remove) { |*_| }
define_method(:update) { |*_| }
define_method(:register) { |*args| }
define_method(:<<) { |*args| }
end
module Uh
@ -19,6 +14,10 @@ module Uh
env.rc_path = 'non_existent_run_control.rb'
end
it 'has the env' do
expect(runner.env).to be env
end
it 'has a dispatcher' do
expect(runner.events).to be_a Dispatcher
end
@ -71,61 +70,39 @@ module Uh
describe '#register_event_hooks' do
it 'registers quit event hook' do
runner.register_event_hooks
expect(runner).to receive :stop!
expect(runner).to receive(:stop!)
runner.events.emit :quit
end
context 'layout hooks' do
it 'registers for :connected event' do
runner.register_event_hooks
expect(env.layout).to receive(:register).with :display
runner.events.emit :connected, args: :display
end
it 'registers for :configure event' do
runner.register_event_hooks
expect(runner.events.emit :configure, args: :window)
.to eq build_geo 0, 0, 42, 42
end
it 'registers for :manage event' do
runner.register_event_hooks
expect(env.layout).to receive(:<<).with :client
runner.events.emit :manage, args: :client
end
it 'registers for :unmanage event' do
runner.register_event_hooks
expect(env.layout).to receive(:remove).with :client
runner.events.emit :unmanage, args: :client
end
it 'registers for :change event' do
runner.register_event_hooks
expect(env.layout).to receive(:update).with :client
runner.events.emit :change, args: :client
end
it 'registers layout hook for :connected event' do
runner.register_event_hooks
expect(env.layout).to receive(:register).with :display
runner.events.emit :connected, args: :display
end
context 'keys hooks' do
it 'registers for :key' do
env.keybinds[:f] = -> { }
runner.register_event_hooks
expect(runner.events[:key, :f]).not_to be_empty
end
it 'registers layout hook for :manage event' do
runner.register_event_hooks
expect(env.layout).to receive(:<<).with :window
runner.events.emit :manage, args: :window
end
it 'registers for :key with combined key bindings' do
env.keybinds[[:f, :shift]] = -> { }
runner.register_event_hooks
expect(runner.events[:key, :f, :shift]).not_to be_empty
end
it 'registers key bindings event hooks' do
env.keybinds[:f] = -> { }
runner.register_event_hooks
expect(runner.events[:key, :f]).not_to be_empty
end
it 'registers code evaluation with the actions handler' do
env.keybinds[:f] = code = proc { }
runner.register_event_hooks
expect(runner.actions).to receive(:evaluate).with code
runner.events.emit :key, :f
end
it 'registers combined key bindings event hooks' do
env.keybinds[[:f, :shift]] = -> { }
runner.register_event_hooks
expect(runner.events[:key, :f, :shift]).not_to be_empty
end
it 'registers key bindings code evaluation with the actions handler' do
env.keybinds[:f] = code = proc { }
runner.register_event_hooks
expect(runner.actions).to receive(:evaluate).with code
runner.events.emit :key, :f
end
end
@ -150,22 +127,17 @@ module Uh
expect(runner.worker).to respond_to :work_events
end
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
it 'setups the read callback to tell manager to handle pending events' do
expect(runner.manager).to receive :handle_pending_events
runner.worker.on_read.call
end
it 'setups the read_next callback' do
it 'setups the read_next callback to tell manager to handle next event' do
expect(runner.manager).to receive :handle_next_event
runner.worker.on_read_next.call
end
it 'setups the timeout callback' do
it 'setups the timeout callback to tell manager to flush the output' do
expect(runner.manager).to receive :flush
runner.worker.on_timeout.call
end

View File

@ -3,9 +3,8 @@ require File.expand_path('../lib/uh/wm/version', __FILE__)
Gem::Specification.new do |s|
s.name = 'uh-wm'
s.version = Uh::WM::VERSION.dup
s.summary = 'minimalistic tiling and stacking window manager for X'
s.summary = 'uh window manager'
s.description = s.name
s.license = 'BSD-3-Clause'
s.homepage = 'https://rubygems.org/gems/uh-wm'
s.authors = 'Thibault Jouan'
@ -15,8 +14,8 @@ Gem::Specification.new do |s|
s.test_files = s.files.grep /\A(spec|features)\//
s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
s.add_dependency 'uh', '2.0.0'
s.add_dependency 'uh-layout', '~> 0.4.0'
s.add_dependency 'uh', '2.0.0.pre1'
s.add_dependency 'uh-layout', '0.2.0.pre'
s.add_development_dependency 'aruba', '~> 0.6'
s.add_development_dependency 'cucumber', '~> 2.0'