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
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
windows (0.0.1)
|
5
|
+
ruby-wmctrl
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
coderay (1.0.7)
|
11
|
+
diff-lcs (1.1.3)
|
12
|
+
ffi (1.0.11)
|
13
|
+
fuubar (1.0.0)
|
14
|
+
rspec (~> 2.0)
|
15
|
+
rspec-instafail (~> 0.2.0)
|
16
|
+
ruby-progressbar (~> 0.0.10)
|
17
|
+
guard (1.3.2)
|
18
|
+
listen (>= 0.4.2)
|
19
|
+
thor (>= 0.14.6)
|
20
|
+
guard-rspec (1.2.1)
|
21
|
+
guard (>= 1.1)
|
22
|
+
libnotify (0.7.2)
|
23
|
+
ffi (~> 1.0.0)
|
24
|
+
listen (0.5.0)
|
25
|
+
method_source (0.8)
|
26
|
+
multi_json (1.3.6)
|
27
|
+
pkg-config (1.1.4)
|
28
|
+
pry (0.9.10)
|
29
|
+
coderay (~> 1.0.5)
|
30
|
+
method_source (~> 0.8)
|
31
|
+
slop (~> 3.3.1)
|
32
|
+
rb-inotify (0.8.8)
|
33
|
+
ffi (>= 0.5.0)
|
34
|
+
rspec (2.11.0)
|
35
|
+
rspec-core (~> 2.11.0)
|
36
|
+
rspec-expectations (~> 2.11.0)
|
37
|
+
rspec-mocks (~> 2.11.0)
|
38
|
+
rspec-core (2.11.1)
|
39
|
+
rspec-expectations (2.11.3)
|
40
|
+
diff-lcs (~> 1.1.3)
|
41
|
+
rspec-instafail (0.2.4)
|
42
|
+
rspec-mocks (2.11.2)
|
43
|
+
ruby-progressbar (0.0.10)
|
44
|
+
ruby-wmctrl (0.0.4)
|
45
|
+
pkg-config
|
46
|
+
simplecov (0.6.4)
|
47
|
+
multi_json (~> 1.0)
|
48
|
+
simplecov-html (~> 0.5.3)
|
49
|
+
simplecov-html (0.5.3)
|
50
|
+
slop (3.3.3)
|
51
|
+
thor (0.16.0)
|
52
|
+
|
53
|
+
PLATFORMS
|
54
|
+
ruby
|
55
|
+
|
56
|
+
DEPENDENCIES
|
57
|
+
fuubar
|
58
|
+
guard-rspec
|
59
|
+
libnotify
|
60
|
+
pry
|
61
|
+
rb-inotify (~> 0.8.8)
|
62
|
+
rspec
|
63
|
+
simplecov
|
64
|
+
windows!
|
data/Guardfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', version: 2, cli: '--color --format Fuubar' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch(%r{^lib/windows/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/spec_helper.rb') { "spec" }
|
9
|
+
watch('lib/windows.rb') { "spec" }
|
10
|
+
end
|
11
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 lefty313
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
windows
|
2
|
+
====
|
3
|
+
|
4
|
+
Ruby framework for manipulating Xwindow windows
|
5
|
+
|
6
|
+
### Installation for unix system
|
7
|
+
```bash
|
8
|
+
sudo apt-get install libx11-dev libglib2.0-dev libxmu-dev
|
9
|
+
```
|
10
|
+
``` ruby
|
11
|
+
gem install windows
|
12
|
+
```
|
13
|
+
|
14
|
+
### Usage
|
15
|
+
```ruby
|
16
|
+
require 'windows'
|
17
|
+
|
18
|
+
p = Windows::Project.new(:default,'~')
|
19
|
+
|
20
|
+
p.open_window('chromium-browser').move(:left)
|
21
|
+
p.open_window('sublime-text-2 .').move(:right)
|
22
|
+
p.open_window('gnome-terminal').move(:bottom_right).on_top
|
23
|
+
|
24
|
+
sleep 5
|
25
|
+
p.close
|
26
|
+
```
|
data/Rakefile
ADDED
data/lib/windows.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'wmctrl'
|
2
|
+
require 'windows/structures'
|
3
|
+
|
4
|
+
class WMCtrl
|
5
|
+
include Windows::Structures
|
6
|
+
|
7
|
+
def action(*args)
|
8
|
+
action_window(*args)
|
9
|
+
pause
|
10
|
+
end
|
11
|
+
|
12
|
+
def register_window(&block)
|
13
|
+
before = windows
|
14
|
+
block.call
|
15
|
+
|
16
|
+
# wait until windows list refresh
|
17
|
+
after = loop do
|
18
|
+
pause
|
19
|
+
after = windows
|
20
|
+
if after.ids != before.ids
|
21
|
+
break after - before
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def desktops
|
27
|
+
list = list_desktops.map {|d| Desktop.new(d[:id], d[:workarea]) }
|
28
|
+
Collection.new(list)
|
29
|
+
end
|
30
|
+
|
31
|
+
def windows
|
32
|
+
list = list_windows(true).map do |w|
|
33
|
+
# wmctrl mark default desktop as -1
|
34
|
+
desktop_id = w[:desktop].abs
|
35
|
+
desktop = find_desktop(desktop_id)
|
36
|
+
Window.new(w[:id], w[:title], desktop, *w[:geometry], w[:active])
|
37
|
+
end.sort_by(&:id)
|
38
|
+
Collection.new(list)
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_window(id)
|
42
|
+
windows.find{ |w| w.id == id }
|
43
|
+
end
|
44
|
+
|
45
|
+
def active_window
|
46
|
+
windows.find(&:active)
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_desktop(id)
|
50
|
+
desktops.find {|d| d.id == id}
|
51
|
+
end
|
52
|
+
|
53
|
+
def spawn_window(command)
|
54
|
+
register_window do
|
55
|
+
pid = Process.spawn(command, :out => :close, :err => :close)
|
56
|
+
Process.detach(pid)
|
57
|
+
end
|
58
|
+
rescue Errno::ENOENT
|
59
|
+
raise "Failed to create window with command: #{command}. Maybe a typo?"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def pause
|
65
|
+
sleep 0.05
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'windows/engines/wmctrl'
|
3
|
+
require 'windows/units/unit_converter'
|
4
|
+
|
5
|
+
module Windows
|
6
|
+
module Engines
|
7
|
+
class XWindow
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :window, :title, :x, :y, :width, :height, :desktop
|
11
|
+
|
12
|
+
attr_reader :engine, :created_at, :id
|
13
|
+
attr_accessor :command
|
14
|
+
|
15
|
+
def initialize(command, options = {}, engine = nil)
|
16
|
+
@engine = engine || WMCtrl.new
|
17
|
+
@command = command
|
18
|
+
end
|
19
|
+
|
20
|
+
def move(*args)
|
21
|
+
args = convert_units(args)
|
22
|
+
undock
|
23
|
+
action(:move_resize, 0, *args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
action(:close)
|
28
|
+
@id = false
|
29
|
+
@created_at = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def focus
|
33
|
+
action(:activate)
|
34
|
+
end
|
35
|
+
|
36
|
+
def undock
|
37
|
+
action(:change_state, "remove", "maximized_vert", "maximized_horz")
|
38
|
+
end
|
39
|
+
|
40
|
+
def create
|
41
|
+
return false if created_at
|
42
|
+
|
43
|
+
window = engine.find_window(command)
|
44
|
+
window ||= engine.spawn_window(command)
|
45
|
+
|
46
|
+
@id = window.id
|
47
|
+
@created_at = Time.now
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def window
|
52
|
+
create
|
53
|
+
engine.find_window(id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_top
|
57
|
+
action(:change_state, "add", "above")
|
58
|
+
end
|
59
|
+
|
60
|
+
def not_on_top
|
61
|
+
action(:change_state, "remove", "above")
|
62
|
+
end
|
63
|
+
|
64
|
+
def action(*args)
|
65
|
+
create
|
66
|
+
engine.action(id, *args)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def maximize
|
71
|
+
action(:change_state, "add", "maximized_vert", "maximized_horz")
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def convert_units(args)
|
77
|
+
converter = Units::UnitConverter.new(desktop, *args)
|
78
|
+
converter.convert
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'windows'
|
3
|
+
|
4
|
+
module Windows
|
5
|
+
class Project
|
6
|
+
attr_reader :root, :name, :windows
|
7
|
+
|
8
|
+
def initialize(name, path)
|
9
|
+
@windows = Array.new
|
10
|
+
@root = Pathname.new(path).expand_path
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
windows.each(&:close)
|
16
|
+
end
|
17
|
+
|
18
|
+
def open_window(command, opts = {}, &block)
|
19
|
+
window = Windows::Window.new(command, opts).create
|
20
|
+
yield window if block_given?
|
21
|
+
windows.push(window)
|
22
|
+
window
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_terminal
|
26
|
+
open_window(Windows::ActiveWindowKey)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Windows
|
4
|
+
module Structures
|
5
|
+
class Collection < SimpleDelegator
|
6
|
+
def ids
|
7
|
+
collect(&:id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def -(collection)
|
11
|
+
diff_id = ids - collection.ids
|
12
|
+
return nil if diff_id.empty?
|
13
|
+
|
14
|
+
raise "I don't know which item is correct #{diff_id}" if diff_id.count != 1
|
15
|
+
find { |r| r.id == diff_id[0] }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Windows
|
2
|
+
module Structures
|
3
|
+
class Desktop < Struct.new(:id, :geometry)
|
4
|
+
def width
|
5
|
+
geometry[2]
|
6
|
+
end
|
7
|
+
|
8
|
+
def height
|
9
|
+
geometry[3]
|
10
|
+
end
|
11
|
+
|
12
|
+
def x_offset
|
13
|
+
geometry[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
def y_offset
|
17
|
+
geometry[1]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Windows
|
2
|
+
module Units
|
3
|
+
module Converter
|
4
|
+
require 'windows/core_ext/numeric'
|
5
|
+
|
6
|
+
class Base
|
7
|
+
attr_reader :el, :options
|
8
|
+
|
9
|
+
def initialize(el, options = {})
|
10
|
+
@el = el
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
raise "you must implement this method"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Formats
|
20
|
+
class Percent < Base
|
21
|
+
def run
|
22
|
+
raise "you must specify options[:base] to change number to percent" unless options[:base]
|
23
|
+
options[:base].percent(el)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Pixel < Base
|
28
|
+
def run
|
29
|
+
el
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
SUPPORTED_FORMATS = {
|
34
|
+
:percent => Percent,
|
35
|
+
:pixel => Pixel
|
36
|
+
}
|
37
|
+
|
38
|
+
def supported_formats
|
39
|
+
SUPPORTED_FORMATS.keys
|
40
|
+
end
|
41
|
+
|
42
|
+
def format_for(format, *args)
|
43
|
+
klass = SUPPORTED_FORMATS[format]
|
44
|
+
raise "I only support this formats #{supported_formats}" unless klass
|
45
|
+
klass.new(*args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Finder < Struct.new(:el)
|
50
|
+
include Formats
|
51
|
+
|
52
|
+
def to(format, *args)
|
53
|
+
format_for(format, el, *args).run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def converter(el)
|
58
|
+
@unit_converter ||= Finder.new(el)
|
59
|
+
@unit_converter.el = el
|
60
|
+
@unit_converter
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|