diff --git a/README.md b/README.md index baf3091..436a421 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ options: -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 ``` diff --git a/features/cli/worker.feature b/features/cli/worker.feature new file mode 100644 index 0000000..94162e7 --- /dev/null +++ b/features/cli/worker.feature @@ -0,0 +1,9 @@ +Feature: worker CLI option + + Scenario: uses the blocking worker when `block' is given + 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 option -v -w mux + Then the output must match /work.+event.+mux/i diff --git a/features/run_control/worker.feature b/features/run_control/worker.feature new file mode 100644 index 0000000..a586c5a --- /dev/null +++ b/features/run_control/worker.feature @@ -0,0 +1,9 @@ +Feature: `worker' run control keyword + + Scenario: configures the modifier key + Given a run control file with: + """ + worker :mux + """ + And I start uhwm + Then the output must match /work.+event.+mux/i diff --git a/features/steps/output_steps.rb b/features/steps/output_steps.rb index 73cd67f..875bf1a 100644 --- a/features/steps/output_steps.rb +++ b/features/steps/output_steps.rb @@ -9,6 +9,7 @@ options: -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 eoh end diff --git a/lib/uh/wm.rb b/lib/uh/wm.rb index c3c6f0c..0614a84 100644 --- a/lib/uh/wm.rb +++ b/lib/uh/wm.rb @@ -12,6 +12,10 @@ require 'uh/wm/logger_formatter' require 'uh/wm/manager' require 'uh/wm/run_control' require 'uh/wm/runner' +require 'uh/wm/workers' +require 'uh/wm/workers/base' +require 'uh/wm/workers/blocking' +require 'uh/wm/workers/mux' module Uh module WM diff --git a/lib/uh/wm/cli.rb b/lib/uh/wm/cli.rb index 8853582..12631d9 100644 --- a/lib/uh/wm/cli.rb +++ b/lib/uh/wm/cli.rb @@ -77,6 +77,11 @@ module Uh opts.on '-l', '--layout LAYOUT', 'specify layout' do |layout| @env.layout_class = self.class.const_get layout.to_sym end + + opts.on '-w', Workers.types, '--worker WORKER', + 'specify worker' do |worker| + @env.worker = worker.to_sym + end end end end diff --git a/lib/uh/wm/env.rb b/lib/uh/wm/env.rb index 2bb63aa..7417647 100644 --- a/lib/uh/wm/env.rb +++ b/lib/uh/wm/env.rb @@ -3,10 +3,11 @@ module Uh class Env RC_PATH = '~/.uhwmrc.rb'.freeze - MODIFIER = :mod1 - KEYBINDS = { + MODIFIER = :mod1 + KEYBINDS = { q: proc { quit } }.freeze + WORKER = :block LOGGER_LEVEL = Logger::WARN LOGGER_LEVEL_VERBOSE = Logger::INFO @@ -20,13 +21,15 @@ module Uh def_delegator :@output, :print attr_reader :output, :keybinds - attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier + attr_accessor :verbose, :debug, :rc_path, :layout_class, :modifier, + :worker def initialize output @output = output @rc_path = RC_PATH @modifier = MODIFIER @keybinds = KEYBINDS.dup + @worker = :block end def verbose? diff --git a/lib/uh/wm/manager.rb b/lib/uh/wm/manager.rb index 5fb9089..d5c6b0b 100644 --- a/lib/uh/wm/manager.rb +++ b/lib/uh/wm/manager.rb @@ -16,6 +16,10 @@ module Uh @clients = [] end + def to_io + IO.new(@display.fileno) + end + def connect @events.emit :connecting, args: @display @display.open @@ -31,12 +35,20 @@ module Uh @events.emit :disconnected end + def flush + @display.flush + end + def grab_key keysym, mod = nil mod_mask = KEY_MODIFIERS[@modifier] mod_mask |= KEY_MODIFIERS[mod] if mod @display.grab_key keysym.to_s, mod_mask end + def handle_next_event + handle @display.next_event + end + def handle_pending_events handle @display.next_event while @display.pending? end diff --git a/lib/uh/wm/run_control.rb b/lib/uh/wm/run_control.rb index 2d511be..b3fc618 100644 --- a/lib/uh/wm/run_control.rb +++ b/lib/uh/wm/run_control.rb @@ -32,6 +32,10 @@ module Uh @env.keybinds[translate_keysym keysym] = block end + def worker type, **options + @env.worker = [type, options] + end + private diff --git a/lib/uh/wm/runner.rb b/lib/uh/wm/runner.rb index 2a36744..582869a 100644 --- a/lib/uh/wm/runner.rb +++ b/lib/uh/wm/runner.rb @@ -54,8 +54,28 @@ module Uh end end + def worker + @worker ||= Workers.build(*(@env.worker)).tap do |w| + w.on_read do + @env.log_debug 'Processing pending events' + @manager.handle_pending_events + end + w.on_read_next do + @env.log_debug 'Processing next event' + @manager.handle_next_event + end + w.on_timeout do |*args| + @env.log_debug "Worker timeout: #{args.inspect}" + @env.log_debug 'Flushing X output buffer' + @manager.flush + end + end + end + def run_until &block - manager.handle_pending_events until block.call + worker.watch @manager + @env.log "Working events with `#{worker.class}'" + worker.work_events until block.call end def terminate diff --git a/lib/uh/wm/testing/acceptance_helpers.rb b/lib/uh/wm/testing/acceptance_helpers.rb index bf5d05b..44612aa 100644 --- a/lib/uh/wm/testing/acceptance_helpers.rb +++ b/lib/uh/wm/testing/acceptance_helpers.rb @@ -11,7 +11,10 @@ module Uh end def uhwm_ensure_stop - @process and @process.terminate + if @process + x_key 'alt+q' + @process.terminate + end end def uhwm_pid diff --git a/lib/uh/wm/workers.rb b/lib/uh/wm/workers.rb new file mode 100644 index 0000000..c065293 --- /dev/null +++ b/lib/uh/wm/workers.rb @@ -0,0 +1,21 @@ +module Uh + module WM + module Workers + FACTORIES = { + block: ->(options) { Blocking.new(options) }, + mux: ->(options) { Mux.new(options) } + }.freeze + + class << self + def types + FACTORIES.keys + end + + def build type, **options + (FACTORIES[type] or fail ArgumentError, "unknown worker: `#{type}'") + .call options + end + end + end + end +end diff --git a/lib/uh/wm/workers/base.rb b/lib/uh/wm/workers/base.rb new file mode 100644 index 0000000..2c2e63b --- /dev/null +++ b/lib/uh/wm/workers/base.rb @@ -0,0 +1,31 @@ +module Uh + module WM + module Workers + class Base + def initialize **options + @ios = [] + end + + def watch io + @ios << io + end + + def before_wait &block + if block_given? then @before_wait = block else @before_wait end + end + + def on_timeout &block + if block_given? then @on_timeout = block else @on_timeout end + end + + def on_read &block + if block_given? then @on_read = block else @on_read end + end + + def on_read_next &block + if block_given? then @on_read_next = block else @on_read_next end + end + end + end + end +end diff --git a/lib/uh/wm/workers/blocking.rb b/lib/uh/wm/workers/blocking.rb new file mode 100644 index 0000000..d6aefc1 --- /dev/null +++ b/lib/uh/wm/workers/blocking.rb @@ -0,0 +1,15 @@ +module Uh + module WM + module Workers + class Blocking < Base + def work_events + #until yield + # @on_events_read_bang.call + #end + #@on_events_read_bang.call until yield + @on_read_next.call + end + end + end + end +end diff --git a/lib/uh/wm/workers/mux.rb b/lib/uh/wm/workers/mux.rb new file mode 100644 index 0000000..e940f4e --- /dev/null +++ b/lib/uh/wm/workers/mux.rb @@ -0,0 +1,18 @@ +module Uh + module WM + module Workers + class Mux < Base + def initialize timeout: 1 + super + @timeout = timeout + end + + def work_events + @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 + end + end + end +end diff --git a/spec/uh/wm/cli_spec.rb b/spec/uh/wm/cli_spec.rb index 6bcf551..027546c 100644 --- a/spec/uh/wm/cli_spec.rb +++ b/spec/uh/wm/cli_spec.rb @@ -176,6 +176,15 @@ module Uh end end + context 'with worker option' do + let(:arguments) { %w[-w mux] } + + it 'assigns the worker type in the env' do + cli.parse_arguments! + expect(cli.env.worker).to eq :mux + end + end + context 'with invalid option' do let(:arguments) { %w[--unknown-option] } diff --git a/spec/uh/wm/env_spec.rb b/spec/uh/wm/env_spec.rb index 1d157dd..03336a6 100644 --- a/spec/uh/wm/env_spec.rb +++ b/spec/uh/wm/env_spec.rb @@ -29,6 +29,10 @@ module Uh expect(env.keybinds.keys).to eq %i[q] end + it 'has the blocking worker by default' do + expect(env.worker).to eq :block + end + describe '#verbose?' do context 'when verbose mode is disabled' do before { env.verbose = false } diff --git a/spec/uh/wm/manager_spec.rb b/spec/uh/wm/manager_spec.rb index 4c498b3..f45d839 100644 --- a/spec/uh/wm/manager_spec.rb +++ b/spec/uh/wm/manager_spec.rb @@ -15,6 +15,18 @@ module Uh expect(manager.clients).to be_empty end + describe '#to_io', :xvfb do + context 'when connected' do + before { manager.connect } + + it 'returns an IO object wrapping the display file descriptor' do + expect(manager.to_io) + .to be_an(IO) + .and have_attributes(fileno: display.fileno) + end + end + end + describe '#connect', :xvfb do it 'opens the display' do expect(manager.display).to receive(:open).and_call_original @@ -70,6 +82,13 @@ module Uh end end + describe '#flush' do + it 'flushes the display' do + expect(display).to receive :flush + manager.flush + end + end + describe '#grab_key' do it 'grabs given key on the display' do expect(manager.display) @@ -87,6 +106,15 @@ module Uh end end + describe '#handle_next_event' do + it 'handles the next available event on display' do + event = double 'event' + allow(display).to receive(:next_event) { event } + expect(manager).to receive(:handle).with(event).once + manager.handle_next_event + end + end + describe '#handle_pending_events' do let(:event) { double 'event' } diff --git a/spec/uh/wm/run_control_spec.rb b/spec/uh/wm/run_control_spec.rb index 7684b8e..1afca27 100644 --- a/spec/uh/wm/run_control_spec.rb +++ b/spec/uh/wm/run_control_spec.rb @@ -80,6 +80,18 @@ module Uh expect(env.keybinds.keys).to include %i[f shift] end end + + describe '#worker' do + it 'sets the worker type in the env' do + rc.worker :some_worker + expect(env.worker[0]).to eq :some_worker + end + + it 'sets the worker options in the env' do + rc.worker :some_worker, some: :option + expect(env.worker[1]).to eq({ some: :option }) + end + end end end end diff --git a/spec/uh/wm/runner_spec.rb b/spec/uh/wm/runner_spec.rb index a2f82aa..bda4bef 100644 --- a/spec/uh/wm/runner_spec.rb +++ b/spec/uh/wm/runner_spec.rb @@ -122,11 +122,37 @@ module Uh end end + describe '#worker' do + it 'returns a worker' do + expect(runner.worker).to respond_to :work_events + end + + 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 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 to tell manager to flush the output' do + expect(runner.manager).to receive :flush + runner.worker.on_timeout.call + end + end + describe '#run_until' do - it 'tells the manager to handle events until given block is true' do + it 'tells the worker to watch the manager' do + expect(runner.worker).to receive(:watch).with runner.manager + runner.run_until { true } + end + + it 'tells the worker to work events until given block is true' do block = proc { } allow(block).to receive(:call).and_return(false, false, false, true) - expect(runner.manager).to receive(:handle_pending_events).exactly(3).times + expect(runner.worker).to receive(:work_events).exactly(3).times runner.run_until &block end end