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 - ruby-head
- 2.1 - 2.1
before_install: before_install:
- travis_retry sudo apt-get update -qq - sudo apt-get update -qq
- travis_retry sudo apt-get install -qq twm x11-utils xdotool - sudo apt-get install -qq twm x11-utils xdotool
matrix: matrix:
allow_failures: allow_failures:
- rvm: ruby-head - rvm: ruby-head

View File

@@ -1,40 +1,17 @@
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 with rubygems) ### Installation (requires ruby ~> 2.1 and rubygems)
``` ```
$ gem install uh-wm $ gem install uh-wm
@@ -47,15 +24,13 @@ $ 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,19 +10,13 @@ XEPHYR_SCREENS_XINERAMA =
task default: %i[features spec] task default: %i[features spec]
Cucumber::Rake::Task.new :features do |t| Cucumber::Rake::Task.new(:features)
t.profile = 'quiet' if ENV.key? 'TRAVIS'
end
RSpec::Core::RakeTask.new do |t| RSpec::Core::RakeTask.new
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.include?('--') ? uhwm_args = ARGV.slice_after('--').to_a.last || %w[-d]
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,2 +1 @@
default: --require features/support --require features/steps --no-source default: --require features/support --require features/steps
quiet: --format progress

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: Given a file named layout.rb with:
""" """
class MyLayout class MyLayout
def register *_ def register _; end
puts 'testing_cli_layout'
end
end end
""" """
When I run uhwm with options -r./layout -l MyLayout When I run uhwm with option -v -r./layout -l MyLayout
Then the output must contain "testing_cli_layout" Then the output must match /layout.+mylayout/i
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,9 +1,5 @@
Feature: require CLI option Feature: require CLI option
Scenario: requires a ruby feature Scenario: requires a ruby feature
Given a file named my_feature.rb with: When I run uhwm with option -v -r abbrev
""" 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 'testing_run_control' puts 'run control evaluation'
""" """
When I run uhwm with option -f uhwmrc.rb 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 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 options -v -w block When I run uhwm with option -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 options -v -w mux When I run uhwm with option -v -w mux
Then the output must match /work.+event.+mux/i Then the output must match /work.+event.+mux/i

View File

@@ -1,12 +1,16 @@
Feature: layout client management Feature: layout client management
Background: Scenario: sends the #<< message when telling the layout to manage a client
Given uhwm is running 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 When a window requests to be mapped
Then the window must be mapped Then the output must contain the window name
Scenario: focuses new client window
When a window is mapped
Then the window must be focused

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 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 'testing_rc_failure' fail 'fails on second line'
""" """
When I start uhwm 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 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 'testing_rc_key' } key(:f) { puts 'trigger f key code' }
""" """
When I press the alt+f keys When I press the alt+f keys
Then the output must contain "testing_rc_key" Then the output must contain "trigger f key code"
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 'testing_rc_key' } key(:enter) { puts 'trigger return key code' }
""" """
When I press the alt+Return keys 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 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 'testing_rc_key' } key(:F) { puts 'trigger shift+f key code' }
""" """
When I press the alt+shift+f keys 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 Feature: `modifier' run control keyword
Scenario: configures the modifier key Scenario: configures the modifier key
Given uhwm is running with this run control file: Given a run control file with:
""" """
modifier :ctrl 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 Then uhwm must terminate successfully

View File

@@ -1,8 +1,9 @@
Feature: `worker' run control keyword Feature: `worker' run control keyword
Scenario: configures the modifier key Scenario: configures the modifier key
Given uhwm is running with this run control file: Given a run control file with:
""" """
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

@@ -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 Scenario: logs about termination
When I tell uhwm to quit 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 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
@@ -23,21 +24,6 @@ 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,12 +24,7 @@ When /^I run uhwm with options? (-.+)$/ do |options|
end end
When /^I tell uhwm to quit$/ do When /^I tell uhwm to quit$/ do
uhwm_request_quit x_key 'alt+q'
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,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| When /^I press the ([^ ]+) keys?$/ do |keys|
x_key keys x_key keys
end 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 When /^a window requests to be mapped$/ do
x_client.map.sync x_window_map
end end
When /^a window requests to be mapped (\d+) times$/ do |times| Then /^it must connect to X display$/ do
x_client.map times: times.to_i uhwm_wait_output 'Connected to'
end expect(x_socket_check uhwm_pid).to be true
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,10 +28,6 @@ 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

@@ -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 '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,75 +1,32 @@
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 = nil, &block def evaluate code
if code instance_eval &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
log "Execute: #{command}" @env.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
log_error "ExecuteError: #{e}" @env.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,8 +3,6 @@ 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
@@ -65,10 +63,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
log "Loaded `#{feature}' ruby feature" @env.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 = Object.const_get layout.to_sym @env.layout_class = self.class.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,10 +1,8 @@
module Uh module Uh
module WM module WM
class Client class Client
include GeoAccessors attr_reader :window, :unmap_count
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
@@ -33,16 +31,6 @@ 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
@@ -66,20 +54,6 @@ 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,9 +16,7 @@ module Uh
end end
def emit *key, args: [] def emit *key, args: []
value = nil @hooks[translate_key key].each { |e| e.call *args }
@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, :shift] => proc { quit } q: 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, :modifier, :worker, attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier,
:layout, :layout_class :worker
def initialize output def initialize output
@output = 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_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
@@ -46,42 +45,6 @@ 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
@@ -100,7 +63,7 @@ module Uh
private private
def handle_error *args def handle_error *args
@events.emit :xerror, args: args @dispatcher.emit :error, args: args
end end
def handle_key_press event def handle_key_press event
@@ -110,37 +73,9 @@ 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
map event.window @clients << client = Client.new(event.window)
end @events.emit :manage, args: client
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,20 +28,8 @@ module Uh
@env.modifier = mod @env.modifier = mod
end end
def key *keysyms, &block def key keysym, &block
@env.keybinds[translate_keysym *keysyms] = block @env.keybinds[translate_keysym keysym] = 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
@@ -51,8 +39,7 @@ module Uh
private private
def translate_keysym keysym, modifier = nil def translate_keysym keysym
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,8 +1,6 @@
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
@@ -51,17 +49,24 @@ module Uh
def connect_manager def connect_manager
manager.connect manager.connect
@env.keybinds.each { |keysym, _| manager.grab_key *keysym } @env.keybinds.each do |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.before_watch { @manager.handle_pending_events } w.on_read do
w.on_read { @manager.handle_pending_events } @env.log_debug 'Processing pending events'
w.on_read_next { @manager.handle_next_event } @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| w.on_timeout do |*args|
log_debug "Worker timeout: #{args.inspect}" @env.log_debug "Worker timeout: #{args.inspect}"
log_debug 'Flushing X output buffer' @env.log_debug 'Flushing X output buffer'
@manager.flush @manager.flush
end end
end end
@@ -69,12 +74,12 @@ module Uh
def run_until &block def run_until &block
worker.watch @manager worker.watch @manager
log "Working events with `#{worker.class}'" @env.log "Working events with `#{worker.class}'"
worker.work_events until block.call worker.work_events until block.call
end end
def terminate def terminate
log "Terminating..." @env.log "Terminating..."
manager.disconnect manager.disconnect
end end
@@ -87,41 +92,24 @@ module Uh
def register_manager_hooks def register_manager_hooks
@events.on :connecting do |display| @events.on :connecting do |display|
log_debug "Connecting to X server on `#{display}'" @env.log_debug "Connecting to X server on `#{display}'"
end end
@events.on :connected do |display| @events.on :connected do |display|
log "Connected to X server on `#{display}'" @env.log "Connected to X server on `#{display}'"
end 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(: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|
log "Registering layout `#{layout.class}'" @env.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|
log "Managing client #{client}" @env.log "Manage 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
@@ -132,8 +120,6 @@ module Uh
class XEventLogger class XEventLogger
include EnvLogging
def initialize env def initialize env
@env = env @env = env
end end
@@ -146,17 +132,13 @@ module Uh
"window: #{xev.window}" "window: #{xev.window}"
end end
log_debug [ @env.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,67 +4,45 @@ 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+shift+q' x_key 'alt+q'
@process.terminate @process.terminate
end end
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 } output = -> { @process.stdout + @process.stderr }
timeout_until do timeout_until do
case message case message
when Regexp then (value = output.call.scan(message)).size >= times when Regexp then output.call =~ message
when String then output.call.include? message when String then assert_partial_output_interactive message
end end
end end
value
rescue TimeoutError => e rescue TimeoutError => e
fail <<-eoh fail [
expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in: "expected `#{message}' not seen after #{e.timeout} seconds in:",
```\n#{output.call.lines.map { |e| " #{e}" }.join} ``` " ```\n#{output.call.lines.map { |e| " #{e}" }.join} ```"
eoh ].join "\n"
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_ready uhwm_wait_output 'Connected to'
end end
def with_other_wm def with_other_wm
@@ -78,48 +56,42 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
@other_wm @other_wm
end end
def x_client ident = nil def x_client
ident ||= :default @x_client ||= XClient.new
@x_clients ||= {} end
@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_clients and @x_clients.any? and @x_clients.values.each &:terminate @x_client and @x_client.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 message = 'condition not met after %d seconds' def timeout_until
timeout = ENV.key?('UHWMTEST_TIMEOUT') ? timeout = ENV.key?('UHWMTEST_TIMEOUT') ?
ENV['UHWMTEST_TIMEOUT'].to_i : ENV['UHWMTEST_TIMEOUT'].to_i :
TIMEOUT_DEFAULT 1
Timeout.timeout(timeout) do Timeout.timeout(timeout) do
loop do loop do
break if yield break if yield
@@ -127,7 +99,7 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
end end
end end
rescue Timeout::Error rescue Timeout::Error
fail TimeoutError.new(message % timeout, timeout) fail TimeoutError.new('execution expired', timeout)
end end
@@ -143,8 +115,8 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
class XClient class XClient
attr_reader :name attr_reader :name
def initialize name = object_id def initialize
@name = "#{self.class.name.split('::').last}/#{name}" @name = "#{self.class.name.split('::').last}/#{object_id}"
@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
@@ -158,37 +130,19 @@ expected `#{message}' (#{times}) not seen after #{e.timeout} seconds in:
end end
def window def window
@window ||= @display.create_window(@geo).tap { |o| o.name = @name } @window ||= @display.create_window(@geo).tap do |o|
o.name = @name
end end
def window_id
@window.id
end end
def window_name def window_name
@name @name
end end
def window_name= name def map
@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.6' VERSION = '0.0.1'
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_watch on_timeout on_read on_read_next].freeze CALLBACKS = %w[before_wait on_timeout on_read on_read_next].freeze
def initialize **options def initialize **options
@ios = [] @ios = []

View File

@@ -2,15 +2,13 @@ module Uh
module WM module WM
module Workers module Workers
class Mux < Base class Mux < Base
TIMEOUT_DEFAULT = 1 def initialize timeout: 1
def initialize timeout: TIMEOUT_DEFAULT
super super
@timeout = timeout @timeout = timeout
end end
def work_events 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 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,11 +2,7 @@ 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

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 } subject(:actions) { described_class.new env, events }
describe '#evaluate' do describe '#evaluate' do
it 'evaluates code given as Proc argument' do it 'evaluates given code' 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
@@ -23,35 +18,6 @@ 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,9 +1,11 @@
module Uh module Uh
module WM module WM
RSpec.describe Client do RSpec.describe Client do
let(:protocols) { [] } let(:geo) { Geo.new(0, 0, 640, 480) }
let(:window) { mock_window icccm_wm_protocols: protocols } let(:window) do
let(:geo) { build_geo } instance_spy Window, 'window', to_s: 'wid',
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
@@ -31,7 +33,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 string representation' do it 'includes window id' do
expect(client.to_s).to include 'wid' expect(client.to_s).to include 'wid'
end end
end end
@@ -48,35 +50,6 @@ 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
@@ -144,37 +117,6 @@ 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

@@ -45,11 +45,6 @@ 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 default key binding for quit set' do it 'has defaults key bindings set' do
expect(env.keybinds.keys).to include [:q, :shift] expect(env.keybinds.keys).to eq %i[q]
end end
it 'has the blocking worker by default' do it 'has the blocking worker by default' do
@@ -70,27 +70,19 @@ module Uh
end end
describe '#layout' do describe '#layout' do
let(:some_layout_class) { Class.new }
it 'returns the default layout' do
expect(env.layout).to be_an_instance_of ::Uh::Layout
end
context 'when a layout is set' do
let(:some_layout) { some_layout_class.new }
before { env.layout = some_layout }
it 'returns the assigned layout' do
expect(env.layout).to be some_layout
end
end
context 'when a layout class is set' do context 'when a layout class is set' do
before { env.layout_class = some_layout_class } let(:some_layout) { Class.new }
before { env.layout_class = some_layout }
it 'returns a new instance of this layout class' do it 'returns a new instance of this layout class' do
expect(env.layout).to be_an_instance_of some_layout_class expect(env.layout).to be_an_instance_of some_layout
end
end
context 'when a layout class is not set' do
it 'returns an instance of the default layout' do
expect(env.layout).to be_an_instance_of ::Uh::Layout
end end
end end
end end
@@ -137,8 +129,7 @@ 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) expect(env.logger).to receive(:info).with /log.+(warn|info|debug).+level/i
.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,13 +2,15 @@ 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
@@ -104,174 +106,6 @@ 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'
@@ -282,7 +116,7 @@ module Uh
end end
describe '#handle_pending_events' do describe '#handle_pending_events' do
let(:event) { mock_event } let(:event) { double 'event' }
context 'when an event is pending on display' do context 'when an event is pending on display' do
before do before do
@@ -310,7 +144,7 @@ module Uh
end end
describe '#handle' do describe '#handle' do
let(:event) { mock_event } let(:event) { double 'event', type: :any }
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
@@ -319,7 +153,12 @@ module Uh
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) { mock_event_key_press 'f', mod_mask } let(:event) do
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 }
@@ -336,47 +175,23 @@ module Uh
end end
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 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 it 'registers a new client wrapping the event window' do
expect(manager).to receive(:map).with :window
manager.handle event manager.handle event
end expect(manager.clients[0])
.to be_a(Client)
.and have_attributes(window: :window)
end end
context 'when unmap_notify event is given' do it 'emits :manage event with the registered client' do
let(:event) { mock_event :unmap_notify, window: :window } events.on :manage, &block
expect(block).to receive :call do |client|
it 'unmaps the event window' do expect(client)
expect(manager).to receive(:unmap).with :window .to be_a(Client)
manager.handle event .and have_attributes(window: :window)
end 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,6 +5,7 @@ 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 }
@@ -27,6 +28,14 @@ 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
@@ -56,11 +65,6 @@ 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
@@ -77,31 +81,6 @@ 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,11 +1,6 @@
SomeLayout = Class.new do SomeLayout = Class.new do
include Factories define_method(:register) { |*args| }
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
@@ -19,6 +14,10 @@ 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
@@ -71,63 +70,41 @@ 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
context 'layout hooks' do it 'registers layout hook for :connected event' do
it 'registers for :connected event' do
runner.register_event_hooks runner.register_event_hooks
expect(env.layout).to receive(:register).with :display expect(env.layout).to receive(:register).with :display
runner.events.emit :connected, args: :display runner.events.emit :connected, args: :display
end end
it 'registers for :configure event' do it 'registers layout hook for :manage event' do
runner.register_event_hooks runner.register_event_hooks
expect(runner.events.emit :configure, args: :window) expect(env.layout).to receive(:<<).with :window
.to eq build_geo 0, 0, 42, 42 runner.events.emit :manage, args: :window
end end
it 'registers for :manage event' do it 'registers key bindings event hooks' 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
context 'keys hooks' do
it 'registers for :key' do
env.keybinds[:f] = -> { } env.keybinds[:f] = -> { }
runner.register_event_hooks runner.register_event_hooks
expect(runner.events[:key, :f]).not_to be_empty expect(runner.events[:key, :f]).not_to be_empty
end end
it 'registers for :key with combined key bindings' do it 'registers combined key bindings event hooks' do
env.keybinds[[:f, :shift]] = -> { } env.keybinds[[:f, :shift]] = -> { }
runner.register_event_hooks runner.register_event_hooks
expect(runner.events[:key, :f, :shift]).not_to be_empty expect(runner.events[:key, :f, :shift]).not_to be_empty
end end
it 'registers code evaluation with the actions handler' do it 'registers key bindings code evaluation with the actions handler' do
env.keybinds[:f] = code = proc { } env.keybinds[:f] = code = proc { }
runner.register_event_hooks runner.register_event_hooks
expect(runner.actions).to receive(:evaluate).with code expect(runner.actions).to receive(:evaluate).with code
runner.events.emit :key, :f runner.events.emit :key, :f
end end
end end
end
describe '#connect_manager' do describe '#connect_manager' do
let(:manager) { instance_spy Manager } let(:manager) { instance_spy Manager }
@@ -150,22 +127,17 @@ module Uh
expect(runner.worker).to respond_to :work_events expect(runner.worker).to respond_to :work_events
end end
it 'setups the before_watch 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.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' do it 'setups the read_next callback to tell manager to handle next event' 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' do it 'setups the timeout callback to tell manager to flush the output' 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,9 +3,8 @@ 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 = 'minimalistic tiling and stacking window manager for X' s.summary = 'uh window manager'
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'
@@ -15,8 +14,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' s.add_dependency 'uh', '2.0.0.pre1'
s.add_dependency 'uh-layout', '~> 0.4.0' s.add_dependency 'uh-layout', '0.2.0.pre'
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'