windows 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +64 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.md +26 -0
- data/Rakefile +2 -0
- data/lib/windows.rb +9 -0
- data/lib/windows/core_ext/numeric.rb +5 -0
- data/lib/windows/engines/wmctrl.rb +68 -0
- data/lib/windows/engines/xwindow.rb +82 -0
- data/lib/windows/project.rb +31 -0
- data/lib/windows/structures.rb +7 -0
- data/lib/windows/structures/collection.rb +19 -0
- data/lib/windows/structures/desktop.rb +21 -0
- data/lib/windows/structures/geometry.rb +6 -0
- data/lib/windows/structures/window.rb +6 -0
- data/lib/windows/units/converter.rb +64 -0
- data/lib/windows/units/recognizer.rb +68 -0
- data/lib/windows/units/unit_converter.rb +73 -0
- data/lib/windows/version.rb +3 -0
- data/samples/moving_windows.rb +22 -0
- data/samples/project.rb +10 -0
- data/spec/core_ext/numeric.rb +14 -0
- data/spec/engines/wmctrl_spec.rb +110 -0
- data/spec/engines/xwindow_spec.rb +171 -0
- data/spec/project_spec.rb +68 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/structures/collection_spec.rb +28 -0
- data/spec/structures/desktop_spec.rb +13 -0
- data/spec/support/matchers/delegate.rb +40 -0
- data/spec/unit_converter_spec.rb +80 -0
- data/spec/window_spec.rb +9 -0
- data/windows.gemspec +27 -0
- metadata +219 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module Windows
|
2
|
+
module Units
|
3
|
+
module Recognizer
|
4
|
+
module Formats
|
5
|
+
class Base < Struct.new(:el)
|
6
|
+
attr_accessor :unit
|
7
|
+
|
8
|
+
def match
|
9
|
+
raise "you should implement your match"
|
10
|
+
end
|
11
|
+
|
12
|
+
def format
|
13
|
+
self.class.to_s.split("::").last.downcase.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def return_match(matcher, data,&block)
|
17
|
+
data = if data
|
18
|
+
matcher.instance_eval &block
|
19
|
+
matcher
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Pixel < Base
|
27
|
+
# match 50, 50.5, 50.02
|
28
|
+
def match
|
29
|
+
data = el.kind_of?(Numeric)
|
30
|
+
item = return_match(self, data) { @unit = el }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Percent < Base
|
35
|
+
# match 50px, 50PX, 50.5px, 50.1PX
|
36
|
+
def match
|
37
|
+
return false unless el.respond_to?(:match)
|
38
|
+
|
39
|
+
data = el.match(/(^[\d.]+)(%)/i)
|
40
|
+
return_match(self, data) { @unit = Float(data[1]) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
RECOGNIZERS = [Pixel, Percent]
|
45
|
+
end
|
46
|
+
|
47
|
+
class Finder < Struct.new(:el)
|
48
|
+
include Formats
|
49
|
+
|
50
|
+
def run
|
51
|
+
item = RECOGNIZERS.map do |klass|
|
52
|
+
recognizer = klass.new(el)
|
53
|
+
recognizer.match
|
54
|
+
end.compact.first
|
55
|
+
|
56
|
+
raise "I don't know how to recognize this unit #{el}" unless item
|
57
|
+
item
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def recognize_unit(el)
|
62
|
+
@recognize_unit_engine ||= Finder.new
|
63
|
+
@recognize_unit_engine.el = el
|
64
|
+
@recognize_unit_engine.run
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'windows/units/converter'
|
2
|
+
require 'windows/units/recognizer'
|
3
|
+
require 'windows/structures'
|
4
|
+
|
5
|
+
module Windows
|
6
|
+
module Units
|
7
|
+
class UnitConverter
|
8
|
+
include Units::Converter
|
9
|
+
include Units::Recognizer
|
10
|
+
include Structures
|
11
|
+
|
12
|
+
NAMED_GEOMETRY = {
|
13
|
+
left: [0, 0, '50%', '100%'],
|
14
|
+
right: ['50%', 0, '50%', '100%'],
|
15
|
+
bottom: [0, '50%', '100%', '50%'],
|
16
|
+
top: [0, 0, '100%', '50%'],
|
17
|
+
max: [0, 0, '100%', '100%'],
|
18
|
+
bottom_right: ['50%', '50%', '50%', '50%'],
|
19
|
+
bottom_left: [0, '50%', '50%', '50%'],
|
20
|
+
top_right: ['50%', 0, '50%', '50%'],
|
21
|
+
top_left: [0, 0, '50%', '50%']
|
22
|
+
}
|
23
|
+
|
24
|
+
def initialize(desktop, *args)
|
25
|
+
@width = desktop.width
|
26
|
+
@height = desktop.height
|
27
|
+
@geometry = create_geometry(args)
|
28
|
+
@x_offset = desktop.x_offset
|
29
|
+
@y_offset = desktop.y_offset
|
30
|
+
@x_axis = [@geometry.x, @geometry.w]
|
31
|
+
@y_axis = [@geometry.y, @geometry.h]
|
32
|
+
end
|
33
|
+
|
34
|
+
def convert
|
35
|
+
x, w = @x_axis.map do |el|
|
36
|
+
item = recognize_unit(el)
|
37
|
+
converter(item.unit).to(item.format, base: @width)
|
38
|
+
end
|
39
|
+
|
40
|
+
y, h = @y_axis.map do |el|
|
41
|
+
item = recognize_unit(el)
|
42
|
+
converter(item.unit).to(item.format, base: @height)
|
43
|
+
end
|
44
|
+
|
45
|
+
x = x + @x_offset
|
46
|
+
y = y + @y_offset
|
47
|
+
|
48
|
+
[x, y, w, h]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_geometry(args)
|
54
|
+
return Geometry.new(*args) if args.count > 1
|
55
|
+
|
56
|
+
if args[0].respond_to?(:to_sym) && geometry_exist?(args[0])
|
57
|
+
args = find_geometry(args[0].to_sym)
|
58
|
+
return Geometry.new(*args)
|
59
|
+
else
|
60
|
+
raise "Geometry with name #{args[0]} not exist. You can use #{NAMED_GEOMETRY.keys}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_geometry(name)
|
65
|
+
NAMED_GEOMETRY[name.to_sym]
|
66
|
+
end
|
67
|
+
|
68
|
+
def geometry_exist?(name)
|
69
|
+
NAMED_GEOMETRY.keys.include?(name.to_sym)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__),'../lib'))
|
2
|
+
require 'windows'
|
3
|
+
|
4
|
+
terminal1 = Windows::Window.new('x-www-browser')
|
5
|
+
terminal2 = Windows::Window.new('x-www-browser')
|
6
|
+
terminal3 = Windows::Window.new('x-www-browser')
|
7
|
+
windows = [terminal1, terminal2, terminal3]
|
8
|
+
|
9
|
+
# first create all windows
|
10
|
+
windows.each(&:create)
|
11
|
+
|
12
|
+
# move with percentage values
|
13
|
+
terminal1.move '50%', '50%', '50%', '50%'
|
14
|
+
|
15
|
+
# move with special named argument
|
16
|
+
terminal2.move :top_right
|
17
|
+
|
18
|
+
# move with pixels
|
19
|
+
terminal3.move 0, 0, 300, 300
|
20
|
+
|
21
|
+
sleep 5
|
22
|
+
windows.each(&:close)
|
data/samples/project.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$LOAD_PATH.unshift lib = File.expand_path(File.join(File.dirname(__FILE__),'../lib'))
|
2
|
+
require 'windows'
|
3
|
+
|
4
|
+
p = Windows::Project.new(:default,lib)
|
5
|
+
|
6
|
+
p.open_window('x-www-browser').move(:right)
|
7
|
+
p.open_window('x-terminal-emulator').move(:bottom)
|
8
|
+
|
9
|
+
sleep 5
|
10
|
+
p.close
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'windows/core_ext/numeric'
|
3
|
+
|
4
|
+
describe Numeric do
|
5
|
+
context "#percent" do
|
6
|
+
it 'return correct value' do
|
7
|
+
1000.percent(10).should == 100
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'floor result' do
|
11
|
+
999.percent(31.53).should == 314
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'windows/engines/wmctrl'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
describe WMCtrl do
|
6
|
+
let(:fake_window_id) { 1234 }
|
7
|
+
let(:window) { Windows::Structures::Window.new(fake_window_id) }
|
8
|
+
let(:time) { Time.parse("Sep 13 2011") }
|
9
|
+
let(:command) { 'any_command' }
|
10
|
+
|
11
|
+
before :each do
|
12
|
+
create_windows
|
13
|
+
create_desktops
|
14
|
+
end
|
15
|
+
|
16
|
+
context "spawn_window" do
|
17
|
+
it 'should raise exception when command not exist' do
|
18
|
+
Process.stub(:spawn).and_raise(Errno::ENOENT)
|
19
|
+
expect { subject.spawn_window(command) }.to raise_error "Failed to create window with command: #{command}. Maybe a typo?"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "#desktops" do
|
24
|
+
list = subject.desktops
|
25
|
+
desktops.zip(list).each do |desktop, desktop_window|
|
26
|
+
desktop[:id].should == desktop_window.id
|
27
|
+
desktop[:workarea].should == desktop_window.geometry
|
28
|
+
end
|
29
|
+
|
30
|
+
list.should be_instance_of(Windows::Structures::Collection)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "#find_desktops" do
|
34
|
+
desktop = subject.find_desktop(1)
|
35
|
+
desktop.id.should == 1
|
36
|
+
desktop.should be_instance_of(Windows::Structures::Desktop)
|
37
|
+
end
|
38
|
+
|
39
|
+
context "#windows" do
|
40
|
+
it 'return windows' do
|
41
|
+
list = subject.windows
|
42
|
+
|
43
|
+
windows(:sorted).zip(list).each do |stub, window|
|
44
|
+
desktop = subject.find_desktop(stub[:desktop])
|
45
|
+
|
46
|
+
stub[:id].should == window.id
|
47
|
+
stub[:title].should == window.title
|
48
|
+
stub[:desktop].should == desktop.id
|
49
|
+
stub[:geometry][0].should == window.x
|
50
|
+
stub[:geometry][1].should == window.y
|
51
|
+
stub[:geometry][2].should == window.width
|
52
|
+
stub[:geometry][3].should == window.height
|
53
|
+
end
|
54
|
+
|
55
|
+
list.should be_instance_of(Windows::Structures::Collection)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'return sorted by :id' do
|
59
|
+
expected_ids = windows(:sorted).map {|w| w[:id]}
|
60
|
+
|
61
|
+
subject.windows.ids.should == expected_ids
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "#find_windows" do
|
66
|
+
it 'should find window by id' do
|
67
|
+
subject.find_window(2).id.should == 2
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should return Window' do
|
71
|
+
subject.find_window(2).should be_instance_of Windows::Structures::Window
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it '#active_window' do
|
76
|
+
subject.active_window.active.should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def desktops
|
82
|
+
unless @desktops
|
83
|
+
klass = Windows::Structures::Desktop
|
84
|
+
@desktops = []
|
85
|
+
@desktops.push({id: 1, workarea: [0, 0, 800, 600]})
|
86
|
+
@desktops.push({id: 2, workarea: [0, 0, 1200, 1000]})
|
87
|
+
end
|
88
|
+
@desktops
|
89
|
+
end
|
90
|
+
|
91
|
+
def windows(sorted = false)
|
92
|
+
unless @windows
|
93
|
+
klass = Windows::Structures::Window
|
94
|
+
@windows = []
|
95
|
+
@windows.push({id: 1, title: 'bash', desktop: 1, geometry: [0,0,100,200]})
|
96
|
+
@windows.push({id: 3, title: 'torr', desktop: 2, geometry: [100,100,400,800]})
|
97
|
+
@windows.push({id: 2, title: 'list', desktop: 1, geometry: [50,50,200,400], active: true})
|
98
|
+
end
|
99
|
+
sorted ? @windows.sort_by{|w| w[:id]} : @windows
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_windows
|
103
|
+
subject.stub(:list_windows).and_return(windows)
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_desktops
|
107
|
+
subject.stub(:list_desktops).and_return(desktops)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'time'
|
3
|
+
require 'windows/engines/xwindow'
|
4
|
+
require 'windows/structures/window'
|
5
|
+
require 'windows/structures/desktop'
|
6
|
+
|
7
|
+
module Windows
|
8
|
+
module Engines
|
9
|
+
class WMCtrl
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DummyEngine
|
15
|
+
def action(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def spawn_window(command)
|
19
|
+
fake_window
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_window(id)
|
23
|
+
fake_window
|
24
|
+
end
|
25
|
+
|
26
|
+
def fake_window
|
27
|
+
Windows::Structures::Window.new(1234, 'fake window', fake_desktop)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def fake_desktop
|
33
|
+
Windows::Structures::Desktop.new(9876, [0,0,800,600])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
class DummyWindow < Struct.new(:id, :title);end
|
37
|
+
class DummyDesktop < Struct.new(:x_offset, :y_offset, :width, :height);end
|
38
|
+
|
39
|
+
describe Windows::Engines::XWindow do
|
40
|
+
subject { Windows::Engines::XWindow.new(command, options, engine)}
|
41
|
+
let(:engine) { DummyEngine.new }
|
42
|
+
let(:command) { 'ls' }
|
43
|
+
let(:time) { Time.parse("Sep 13 2011")}
|
44
|
+
let(:options) { Hash.new }
|
45
|
+
let(:window) { engine.fake_window }
|
46
|
+
let(:id) { window.id }
|
47
|
+
|
48
|
+
it { should delegate(:title).to(:window) }
|
49
|
+
it { should delegate(:desktop).to(:window) }
|
50
|
+
it { should delegate(:x).to(:window) }
|
51
|
+
it { should delegate(:y).to(:window) }
|
52
|
+
it { should delegate(:width).to(:window) }
|
53
|
+
it { should delegate(:height).to(:window) }
|
54
|
+
it { should respond_to :command= }
|
55
|
+
|
56
|
+
it '#move' do
|
57
|
+
args = [100, 200, 500, 400]
|
58
|
+
|
59
|
+
subject.should_receive(:undock).ordered
|
60
|
+
engine.should_receive(:action).with(id, :move_resize, 0, *args).ordered
|
61
|
+
subject.move(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'close' do
|
65
|
+
it 'should delegate to engine' do
|
66
|
+
engine.should_receive(:action).with(id, :close)
|
67
|
+
subject.close
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should :id, :created_at set to false' do
|
71
|
+
subject.create
|
72
|
+
subject.close
|
73
|
+
subject.id.should == false
|
74
|
+
subject.created_at.should == false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it '#focus' do
|
79
|
+
engine.should_receive(:action).with(id, :activate)
|
80
|
+
subject.focus
|
81
|
+
end
|
82
|
+
|
83
|
+
it '#undock' do
|
84
|
+
args = ["remove", "maximized_vert", "maximized_horz"]
|
85
|
+
|
86
|
+
engine.should_receive(:action).with(id, :change_state, *args)
|
87
|
+
subject.undock
|
88
|
+
end
|
89
|
+
|
90
|
+
context "#create" do
|
91
|
+
before(:each) do
|
92
|
+
Time.stub!(:now).and_return(time)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should assign id' do
|
96
|
+
subject.create
|
97
|
+
subject.id.should == engine.fake_window.id
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should assign created_at' do
|
101
|
+
subject.create
|
102
|
+
subject.created_at.should == time
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should return false when window is already created' do
|
106
|
+
subject.create
|
107
|
+
subject.create.should be_false
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should not spawn new window when it exist' do
|
111
|
+
engine.should_receive(:find_window).with(command).and_return(window)
|
112
|
+
engine.should_not_receive(:spawn_window).with(command)
|
113
|
+
subject.create
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context '#window' do
|
118
|
+
context "when window is not created" do
|
119
|
+
it 'should create window' do
|
120
|
+
subject.should_receive(:create)
|
121
|
+
subject.window
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when window is created" do
|
126
|
+
before do
|
127
|
+
subject.create
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should delegate to engine' do
|
131
|
+
engine.should_receive(:find_window).with(id)
|
132
|
+
subject.window
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it '#on_top' do
|
138
|
+
engine.should_receive(:action).with(id, :change_state, "add", "above")
|
139
|
+
subject.on_top
|
140
|
+
end
|
141
|
+
|
142
|
+
it '#not_on_top' do
|
143
|
+
engine.should_receive(:action).with(id, :change_state, "remove", "above")
|
144
|
+
subject.not_on_top
|
145
|
+
end
|
146
|
+
|
147
|
+
context "#action" do
|
148
|
+
it 'should delegate to engine' do
|
149
|
+
args = [0, 0, 0, 100, 200]
|
150
|
+
|
151
|
+
engine.should_receive(:action).with(id, *args)
|
152
|
+
subject.action(*args)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should return self' do
|
156
|
+
subject.action.should == subject
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it '#maximize' do
|
161
|
+
engine.should_receive(:action).with(id, :change_state, "add", "maximized_vert", "maximized_horz")
|
162
|
+
subject.maximize
|
163
|
+
end
|
164
|
+
|
165
|
+
context "initialize" do
|
166
|
+
it "should use WMCtrl as default engine" do
|
167
|
+
object = subject.class.new(command, options, nil)
|
168
|
+
object.engine.should be_instance_of(Windows::Engines::WMCtrl)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|