diff --git a/features/layout/unmanage.feature b/features/layout/unmanage.feature new file mode 100644 index 0000000..313853c --- /dev/null +++ b/features/layout/unmanage.feature @@ -0,0 +1,10 @@ +Feature: layout client unmanagement + + Background: + Given uhwm is running + And a first window is mapped + And a second window is mapped + + Scenario: maps another window + When the second window requests to be unmapped + Then the first window must be mapped diff --git a/features/steps/x_steps.rb b/features/steps/x_steps.rb index 32465bd..d06e819 100644 --- a/features/steps/x_steps.rb +++ b/features/steps/x_steps.rb @@ -1,3 +1,7 @@ +Given /^a (\w+) window is mapped$/ do |ident| + x_window_map ident: ident +end + When /^I press the ([^ ]+) keys?$/ do |keys| x_key keys end @@ -6,6 +10,10 @@ When /^a window requests to be mapped$/ do x_window_map end +When /^the (\w+) window requests to be unmapped$/ do |ident| + x_window_unmap ident: ident +end + When /^a window requests to be mapped (\d+) times$/ do |times| x_window_map times: times.to_i end @@ -19,6 +27,10 @@ Then /^the window must be mapped$/ do expect(x_window_map_state).to eq 'IsViewable' end +Then /^the (\w+) window must be mapped$/ do |ident| + expect(x_window_map_state ident: ident).to eq 'IsViewable' +end + Then /^the window must be focused$/ do expect(x_focused_window_id).to eq x_window_id end diff --git a/lib/uh/wm/client.rb b/lib/uh/wm/client.rb index e20225f..1eea9b9 100644 --- a/lib/uh/wm/client.rb +++ b/lib/uh/wm/client.rb @@ -1,8 +1,8 @@ module Uh module WM class Client - attr_reader :window, :unmap_count - attr_accessor :geo + attr_reader :window + attr_accessor :geo, :unmap_count def initialize window, geo = nil @window = window diff --git a/lib/uh/wm/manager.rb b/lib/uh/wm/manager.rb index 8192a93..ab09703 100644 --- a/lib/uh/wm/manager.rb +++ b/lib/uh/wm/manager.rb @@ -61,6 +61,16 @@ module Uh @events.emit :manage, args: client end + def unmap window + return unless client = client_for(window) + if client.unmap_count > 0 + client.unmap_count -= 1 + else + @clients.delete client + @events.emit :unmanage, args: client + end + end + def handle_next_event handle @display.next_event end @@ -97,6 +107,10 @@ module Uh map event.window end + def handle_unmap_notify event + unmap event.window + end + def client_for window @clients.find { |e| e.window == window } end diff --git a/lib/uh/wm/runner.rb b/lib/uh/wm/runner.rb index 22625a7..6e6aab3 100644 --- a/lib/uh/wm/runner.rb +++ b/lib/uh/wm/runner.rb @@ -115,6 +115,10 @@ module Uh log "Managing client #{client}" layout << client end + @events.on :unmanage do |client| + log "Unmanaging client #{client}" + layout.remove client + end end def register_keybinds_hooks diff --git a/lib/uh/wm/testing/acceptance_helpers.rb b/lib/uh/wm/testing/acceptance_helpers.rb index 838d05f..a1324b3 100644 --- a/lib/uh/wm/testing/acceptance_helpers.rb +++ b/lib/uh/wm/testing/acceptance_helpers.rb @@ -56,8 +56,9 @@ module Uh @other_wm end - def x_client - @x_client ||= XClient.new + def x_client ident: :default + @x_clients ||= {} + @x_clients[ident] ||= XClient.new(ident) end def x_focused_window_id @@ -81,25 +82,30 @@ module Uh end.any? end - def x_window_id - @x_client.window_id + def x_window_id **options + x_client(options).window_id end def x_window_name - @x_client.window_name + x_client.window_name end - def x_window_map times: 1 - times.times { x_client.map } - x_client.sync + def x_window_map times: 1, **options + times.times { x_client(options).map } + x_client(options).sync end - def x_window_map_state - `xwininfo -id #{x_window_id}`[/Map State: (\w+)/, 1] + def x_window_map_state **options + `xwininfo -id #{x_window_id options}`[/Map State: (\w+)/, 1] + end + + def x_window_unmap options + x_client(options).unmap + x_client(options).sync end def x_clients_ensure_stop - @x_client and @x_client.terminate + @x_clients and @x_clients.any? and @x_clients.values.each &:terminate end @@ -132,8 +138,8 @@ module Uh class XClient attr_reader :name - def initialize - @name = "#{self.class.name.split('::').last}/#{object_id}" + def initialize name = object_id + @name = "#{self.class.name.split('::').last}/#{name}" @geo = Geo.new(0, 0, 640, 480) @display = Display.new.tap { |o| o.open } end @@ -164,6 +170,11 @@ module Uh window.map self end + + def unmap + window.unmap + self + end end end end diff --git a/spec/uh/wm/manager_spec.rb b/spec/uh/wm/manager_spec.rb index 372b008..3238b09 100644 --- a/spec/uh/wm/manager_spec.rb +++ b/spec/uh/wm/manager_spec.rb @@ -164,6 +164,56 @@ module Uh end end + describe '#unmap' do + before { manager.map window } + + context 'when client unmap count is 0 or less' do + it 'preserves the client unmap count' do + client = manager.clients[0] + expect { manager.unmap window } + .not_to change { client.unmap_count } + end + + it 'unregisters the client' do + expect { manager.unmap window } + .to change { manager.clients.size }.from(1).to 0 + end + + it 'emits :unmanage event with the client' do + events.on :unmanage, &block + expect(block).to receive(:call).with manager.clients[0] + manager.unmap window + end + end + + context 'when client unmap count is strictly positive' do + before { manager.clients[0].unmap_count += 1 } + + it 'does not unregister the client' do + expect { manager.unmap window }.not_to change { manager.clients } + end + + it 'decrements the unmap count' do + manager.unmap window + expect(manager.clients[0].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 '#handle_next_event' do it 'handles the next available event on display' do event = double 'event' @@ -252,6 +302,15 @@ module Uh manager.handle event end end + + context 'when unmap_notify event is given' do + let(:event) { double 'event', type: :unmap_notify, window: :window } + + it 'unmap the event window' do + expect(manager).to receive(:unmap).with :window + manager.handle event + end + end end end end diff --git a/spec/uh/wm/runner_spec.rb b/spec/uh/wm/runner_spec.rb index 88a259f..f91c81f 100644 --- a/spec/uh/wm/runner_spec.rb +++ b/spec/uh/wm/runner_spec.rb @@ -2,6 +2,7 @@ SomeLayout = Class.new do define_method(:register) { |*args| } define_method(:suggest_geo) { Uh::Geo.new(0, 0, 42, 42) } define_method(:<<) { |*args| } + define_method(:remove) { |*args| } end module Uh @@ -93,6 +94,12 @@ module Uh 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 end context 'keys hooks' do