wmctile 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/console +14 -0
- data/bin/wmctile +20 -2
- data/lib/wmctile.rb +36 -7
- data/lib/wmctile/errors.rb +12 -0
- data/lib/wmctile/router.rb +53 -187
- data/lib/wmctile/window.rb +120 -68
- metadata +129 -21
- data/lib/wmctile/class.rb +0 -13
- data/lib/wmctile/class_with_dmenu.rb +0 -24
- data/lib/wmctile/memory.rb +0 -64
- data/lib/wmctile/settings.rb +0 -56
- data/lib/wmctile/window_manager.rb +0 -174
- data/lib/wmctile/window_tiler.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cedbd5d2774446b458abd0a2606c1febd81115f4
|
4
|
+
data.tar.gz: d5749f69feaa8bff5410816fc0901fce8f52aad5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 586d18fa5307b49a679b5eef6700e634ee28d8e3f882a35542aff38339df9fce9f62cc153a94dddfb91bcaf18a9a866b52e234d62b7ba5c60f404ddfb8a57e60
|
7
|
+
data.tar.gz: d1800abad113bfb9e94786e289487b561adae921f8661235436065dce190aabc2d71d5706708697156de571bee78d9832e80e9043ff8ae421efdfabb0468e296
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pry'
|
3
|
+
require_relative '../lib/wmctile'
|
4
|
+
|
5
|
+
windows = []
|
6
|
+
|
7
|
+
`wmctrl -lx | grep -v ' -1 ' | awk '{ print $1 }'`.chomp.split("\n").map do |id|
|
8
|
+
windows << Wmctile::Window.new({}, id)
|
9
|
+
end
|
10
|
+
|
11
|
+
binding.pry
|
12
|
+
|
13
|
+
# Need for pry to work (see https://github.com/deivid-rodriguez/pry-byebug/issues/45)
|
14
|
+
puts 'Returning from console.'
|
data/bin/wmctile
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
2
3
|
require_relative '../lib/wmctile'
|
3
4
|
|
4
|
-
|
5
|
-
|
5
|
+
options = {}
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: wmctile [options] [window_strings]...'
|
8
|
+
|
9
|
+
opts.on('--switch-to', 'Switch to target window\'s workspace and focus it.') { |o| options[:switch_to] = o }
|
10
|
+
opts.on('--summon', 'Summon target window to current workspace and focus it.') { |o| options[:summon] = o }
|
11
|
+
opts.on('--shade', 'Shade (minimize) a window.') { |o| options[:shade] = o }
|
12
|
+
opts.on('--unshade', 'Unshade a window.') { |o| options[:unshade] = o }
|
13
|
+
|
14
|
+
opts.on('-a', '--active-window', 'Use the active window instead of [window_strings].') { |o| options[:use_active_window] = o }
|
15
|
+
opts.on('-c', '--current-workspace', 'Search for windows only in the current workspace.') { |o| options[:current_workspace] = o }
|
16
|
+
opts.on('-x [CMD]', '--exec [CMD]', 'Run a command when no window matching window_strings is found.') { |o| options[:exec] = o }
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
begin
|
20
|
+
Wmctile::Router.new options, ARGV
|
21
|
+
rescue StandardError => e
|
22
|
+
`if which notify-send; then notify-send -i 'error' 'wmctile' '#{e}'; else echo '#{e}'; fi`
|
23
|
+
end
|
data/lib/wmctile.rb
CHANGED
@@ -1,11 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# @author Petr Marek <contact@petrmarek.eu>
|
3
|
+
#
|
1
4
|
module Wmctile
|
5
|
+
#
|
6
|
+
# Fetches windows from wcmtrl.
|
7
|
+
#
|
8
|
+
# @return [Array] Array of windows.
|
9
|
+
#
|
10
|
+
def self.window_list
|
11
|
+
@window_list ||= `wmctrl -lx`.chomp.split(/\n/)
|
12
|
+
@window_list
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Fetches the current workspace number from wmctrl.
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# @return [String] Current workspace number.
|
20
|
+
#
|
21
|
+
def self.current_workspace
|
22
|
+
@current_workspace ||= `wmctrl -d | grep '\*' | cut -d' ' -f 1`.chomp
|
23
|
+
@current_workspace
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Fetches the current window id.
|
28
|
+
#
|
29
|
+
#
|
30
|
+
# @return [String] Current window id.
|
31
|
+
#
|
32
|
+
def self.current_window_id
|
33
|
+
@current_window_id ||= `wmctrl -a :ACTIVE: -v 2>&1`.chomp.split('Using window: ')[1]
|
34
|
+
@current_window_id
|
35
|
+
end
|
2
36
|
end
|
3
37
|
|
4
|
-
require_relative 'wmctile/
|
5
|
-
require_relative 'wmctile/class_with_dmenu'
|
6
|
-
require_relative 'wmctile/settings'
|
7
|
-
require_relative 'wmctile/memory'
|
8
|
-
require_relative 'wmctile/window'
|
9
|
-
require_relative 'wmctile/window_manager'
|
10
|
-
require_relative 'wmctile/window_tiler'
|
38
|
+
require_relative 'wmctile/errors'
|
11
39
|
require_relative 'wmctile/router'
|
40
|
+
require_relative 'wmctile/window'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Wmctile
|
2
|
+
module Errors
|
3
|
+
#
|
4
|
+
# Class WindowNotFound provides an error class for Window
|
5
|
+
#
|
6
|
+
class WindowNotFound < StandardError
|
7
|
+
def initialize(window_string)
|
8
|
+
super(%(No matching window found for: "#{window_string}".))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/wmctile/router.rb
CHANGED
@@ -1,187 +1,53 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
examples:
|
56
|
-
wmctile snap 'left' 'terminator'
|
57
|
-
wmctile summon --all-workspaces ':ACTIVE:'
|
58
|
-
|
59
|
-
options:
|
60
|
-
--all-workspaces, -a
|
61
|
-
Use all workspaces when searching for windows.
|
62
|
-
|
63
|
-
commands:
|
64
|
-
summon 'window_string'
|
65
|
-
Summons a window matching 'window_str'.
|
66
|
-
|
67
|
-
summon_or_run 'window_string' 'command_to_run'
|
68
|
-
Summons a window matching 'window_string'. If no window is found, the 'command_to_run' is run.
|
69
|
-
|
70
|
-
switch_to 'window_string'
|
71
|
-
Switches to a window matching 'window_string'.
|
72
|
-
|
73
|
-
switch_to_or_run 'window_string' 'command_to_run'
|
74
|
-
Switches to a window matching 'window_string'. If no window is found, the 'command_to_run' is run.
|
75
|
-
|
76
|
-
maximize 'window_string'
|
77
|
-
Maximizes a window matching 'window_string'.
|
78
|
-
|
79
|
-
unmaximize 'window_string'
|
80
|
-
Unmaximizes a window matching 'window_string'.
|
81
|
-
|
82
|
-
shade 'window_string'
|
83
|
-
Shades a window matching 'window_string'.
|
84
|
-
|
85
|
-
unshade 'window_string'
|
86
|
-
Unshades a window matching 'window_string'.
|
87
|
-
|
88
|
-
unshade_last_shaded
|
89
|
-
Unshades the last shaded window on active workspace.
|
90
|
-
|
91
|
-
snap 'where' 'window_string' ['portion']
|
92
|
-
Snaps a window matching 'window_string' to occupy the 'where' 'portion' of the screen.
|
93
|
-
'where' can be one of 'left', 'right', 'top', 'bottom'
|
94
|
-
'portion' is a float number with the default of 0.5
|
95
|
-
|
96
|
-
resize 'where' ['portion']
|
97
|
-
Resizes the last performed action (snap/tile etc.) on active workspace.
|
98
|
-
'where' can be one of 'left', 'right', 'top', 'bottom'
|
99
|
-
The action depends on the previously performed action. When you resize 'left' a previous snap 'left', you're shrinking the window. When you resize 'left' a previous snap 'right', you're increasing the size of the window.
|
100
|
-
'portion' is a float number with the default of 0.01 by which to edit the previous portion of the screen
|
101
|
-
|
102
|
-
resize_snap 'where' ['portion']
|
103
|
-
Resizes the last performed snap on active workspace. Arguments are the same as in resize command.
|
104
|
-
|
105
|
-
additional information:
|
106
|
-
To use the active window, pass ':ACTIVE:' as the 'window_string' argument.
|
107
|
-
eos
|
108
|
-
end
|
109
|
-
def summon window_str
|
110
|
-
window = self.wm.find_in_windows window_str, @all_workspaces
|
111
|
-
if window
|
112
|
-
window.summon
|
113
|
-
return true
|
114
|
-
else
|
115
|
-
return false
|
116
|
-
end
|
117
|
-
end
|
118
|
-
def summon_or_run window_str, cmd_to_run
|
119
|
-
unless self.summon window_str
|
120
|
-
self.cmd "#{ cmd_to_run } > /dev/null &"
|
121
|
-
end
|
122
|
-
end
|
123
|
-
def switch_to window_str
|
124
|
-
window = self.wm.find_in_windows window_str, @all_workspaces
|
125
|
-
if window
|
126
|
-
window.switch_to
|
127
|
-
return true
|
128
|
-
else
|
129
|
-
return false
|
130
|
-
end
|
131
|
-
end
|
132
|
-
def switch_to_or_run window_str, cmd_to_run
|
133
|
-
unless self.switch_to window_str
|
134
|
-
self.cmd "#{ cmd_to_run } > /dev/null &"
|
135
|
-
end
|
136
|
-
end
|
137
|
-
def maximize window_str
|
138
|
-
window = self.wm.get_window window_str
|
139
|
-
if window
|
140
|
-
window.maximize
|
141
|
-
end
|
142
|
-
end
|
143
|
-
def unmaximize window_str
|
144
|
-
window = self.wm.get_window window_str
|
145
|
-
if window
|
146
|
-
window.unmaximize
|
147
|
-
end
|
148
|
-
end
|
149
|
-
def shade window_str
|
150
|
-
window = self.wm.get_window window_str
|
151
|
-
if window
|
152
|
-
window.shade
|
153
|
-
self.memory.set self.wm.workspace, 'shade', {
|
154
|
-
'window_id' => window.id
|
155
|
-
}
|
156
|
-
end
|
157
|
-
end
|
158
|
-
def unshade window_str
|
159
|
-
window = self.wm.get_window window_str
|
160
|
-
if window
|
161
|
-
window.unshade
|
162
|
-
self.memory.set self.wm.workspace, 'unshade', {
|
163
|
-
'window_id' => window.id
|
164
|
-
}
|
165
|
-
end
|
166
|
-
end
|
167
|
-
def unshade_last_shaded
|
168
|
-
win_id = self.memory.get self.wm.workspace, 'shade', 'window_id'
|
169
|
-
window = Wmctile::Window.new win_id, @settings
|
170
|
-
if window
|
171
|
-
window.unshade
|
172
|
-
self.memory.set self.wm.workspace, 'unshade', {
|
173
|
-
'window_id' => window.id
|
174
|
-
}
|
175
|
-
end
|
176
|
-
end
|
177
|
-
def snap where = 'left', window_str = nil, portion = 0.5
|
178
|
-
self.wt.snap where, window_str, portion
|
179
|
-
end
|
180
|
-
def resize where = 'left', portion = 0.01
|
181
|
-
self.wt.resize where, portion
|
182
|
-
end
|
183
|
-
def resize_snap where = 'left', portion = 0.01
|
184
|
-
self.wt.resize_snap where, portion
|
185
|
-
end
|
186
|
-
|
187
|
-
end
|
1
|
+
module Wmctile
|
2
|
+
#
|
3
|
+
# Router is the backbone of wmctile.
|
4
|
+
# It dispatches relevant methods based on the command-line arguments.
|
5
|
+
#
|
6
|
+
class Router
|
7
|
+
#
|
8
|
+
# Starts wmctile, runs the required methods.
|
9
|
+
#
|
10
|
+
# @param [Hash] command line options
|
11
|
+
# @param [Array] window_strings ARGV array
|
12
|
+
#
|
13
|
+
def initialize(arguments, window_strings)
|
14
|
+
return unless window_strings.count
|
15
|
+
|
16
|
+
@arguments = arguments
|
17
|
+
@window_strings = window_strings
|
18
|
+
|
19
|
+
if arguments[:switch_to]
|
20
|
+
window.switch_to
|
21
|
+
elsif arguments[:summon]
|
22
|
+
window.summon
|
23
|
+
elsif arguments[:shade]
|
24
|
+
window.toggle_shaded(true)
|
25
|
+
elsif arguments[:unshade]
|
26
|
+
window.toggle_shaded(false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Creates a new window based on @arguments and @window_strings.
|
32
|
+
# If no window is found, checks for the -x/--exec argument. If present, executes it.
|
33
|
+
#
|
34
|
+
# @param [Integer] index index of the window from matching windows array
|
35
|
+
#
|
36
|
+
# @return [Window] window instance
|
37
|
+
#
|
38
|
+
def window(index = 0)
|
39
|
+
if @arguments[:use_active_window]
|
40
|
+
Window.new(@arguments, Wmctile.current_window_id)
|
41
|
+
else
|
42
|
+
Window.new(@arguments, @window_strings[index])
|
43
|
+
end
|
44
|
+
rescue Errors::WindowNotFound
|
45
|
+
if @arguments[:exec]
|
46
|
+
# Exec the command
|
47
|
+
puts "Executing command: #{@arguments[:exec]}"
|
48
|
+
`#{@arguments[:exec]} &`
|
49
|
+
end
|
50
|
+
raise Errors::WindowNotFound, @window_strings[index]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/wmctile/window.rb
CHANGED
@@ -1,69 +1,121 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Wmctile
|
2
|
+
#
|
3
|
+
# Window is the core element of wmctile. It is described by an id, which has to be fetched from a window_string.
|
4
|
+
#
|
5
|
+
class Window
|
6
|
+
#
|
7
|
+
# Window init function. Tries to find an id.
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# @param [Hash] arguments command line options
|
11
|
+
# @param [String] window_string command line string given
|
12
|
+
#
|
13
|
+
def initialize(arguments, window_string)
|
14
|
+
@window_string = window_string
|
15
|
+
@regexp_string = Regexp.new(window_string)
|
16
|
+
find_window(arguments[:current_workspace])
|
17
|
+
end
|
3
18
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
19
|
+
#
|
20
|
+
# Filters down the window_list to @matching_windows.
|
21
|
+
#
|
22
|
+
# @param [Boolean] current_workspace_only Should only the current workspace be used?
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
def find_window(current_workspace_only = false)
|
27
|
+
@matching_windows = Wmctile.window_list.grep(@regexp_string)
|
28
|
+
filter_out_workspaces if current_workspace_only
|
29
|
+
if @matching_windows.count > 1
|
30
|
+
filter_more_matching_windows
|
31
|
+
elsif @matching_windows.count == 1
|
32
|
+
@matching_line = @matching_windows[0]
|
33
|
+
else
|
34
|
+
fail Errors::WindowNotFound, @window_string
|
35
|
+
end
|
36
|
+
extract_matching_line_information
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Filters out @matching_windows so that only the ones on current_workspace remain.
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# @return [Array] @matching_windows
|
44
|
+
#
|
45
|
+
def filter_out_workspaces
|
46
|
+
@matching_windows = @matching_windows.grep(/^\w+\s+#{Wmctile.current_workspace}\s+/)
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Takes the @matching_line and extracts @id, @workspace and @wm_class
|
51
|
+
#
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
#
|
55
|
+
def extract_matching_line_information
|
56
|
+
parts = @matching_line.split(/\s+/)
|
57
|
+
@id = parts[0]
|
58
|
+
@workspace = parts[1]
|
59
|
+
@wm_class = parts[2]
|
60
|
+
end
|
61
|
+
|
62
|
+
def filter_more_matching_windows
|
63
|
+
ids = @matching_windows.map { |matching_window| matching_window.match(/^(\w+)\s/)[1] }
|
64
|
+
target_index = 0
|
65
|
+
if ids.include? Wmctile.current_window_id
|
66
|
+
# Hold on to your hat, we're gonna loop!
|
67
|
+
i = ids.index(Wmctile.current_window_id)
|
68
|
+
if i + 1 < ids.count
|
69
|
+
# Current window is not the last
|
70
|
+
target_index = i + 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
print(target_index)
|
74
|
+
@matching_line = @matching_windows[target_index]
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Switch to window's workspace and focus it.
|
79
|
+
#
|
80
|
+
#
|
81
|
+
# @return [void]
|
82
|
+
#
|
83
|
+
def switch_to
|
84
|
+
return unless @id
|
85
|
+
`wmctrl -ia #{@id}`
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Summon a window to current workspace and focus it.
|
90
|
+
#
|
91
|
+
#
|
92
|
+
# @return [void]
|
93
|
+
#
|
94
|
+
def summon
|
95
|
+
return unless @id
|
96
|
+
`wmctrl -iR #{@id}`
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Shade (minimize) or unshade a window.
|
101
|
+
#
|
102
|
+
# @param [Boolean] should_shade
|
103
|
+
#
|
104
|
+
# @return [void]
|
105
|
+
#
|
106
|
+
def toggle_shaded(should_shade = true)
|
107
|
+
return unless @id
|
108
|
+
|
109
|
+
if should_shade
|
110
|
+
begin
|
111
|
+
# Use xdotool if possible
|
112
|
+
`xdotool windowminimize #{@id}`
|
113
|
+
rescue
|
114
|
+
`wmctrl -ir #{@id} -b add,shaded`
|
115
|
+
end
|
116
|
+
else
|
117
|
+
`wmctrl -ir #{@id} -b remove,shaded`
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
metadata
CHANGED
@@ -1,66 +1,173 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wmctile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mreq
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: minitest
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0
|
20
|
-
type: :
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0
|
27
|
-
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest-reporters
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard-rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard-yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Window manager's best friend. In a gem.
|
28
140
|
email: contact@petrmarek.eu
|
29
141
|
executables:
|
30
142
|
- wmctile
|
31
143
|
extensions: []
|
32
144
|
extra_rdoc_files: []
|
33
145
|
files:
|
146
|
+
- bin/console
|
34
147
|
- bin/wmctile
|
35
148
|
- lib/wmctile.rb
|
36
|
-
- lib/wmctile/
|
37
|
-
- lib/wmctile/class_with_dmenu.rb
|
38
|
-
- lib/wmctile/memory.rb
|
149
|
+
- lib/wmctile/errors.rb
|
39
150
|
- lib/wmctile/router.rb
|
40
|
-
- lib/wmctile/settings.rb
|
41
151
|
- lib/wmctile/window.rb
|
42
|
-
- lib/wmctile/window_manager.rb
|
43
|
-
- lib/wmctile/window_tiler.rb
|
44
152
|
homepage: http://mreq.github.io/wmctile
|
45
153
|
licenses:
|
46
|
-
- GPL-
|
154
|
+
- GPL-3
|
47
155
|
metadata: {}
|
48
156
|
post_install_message: |
|
49
157
|
Thanks for installing wmctile. Make sure, you have the following dependencies installed on your system:
|
50
158
|
|
51
159
|
wmctrl
|
52
|
-
|
53
|
-
dmenu
|
160
|
+
xdotool
|
54
161
|
|
55
162
|
On Ubuntu it's as easy as running:
|
56
163
|
|
57
|
-
sudo apt-get install wmctrl
|
164
|
+
sudo apt-get install wmctrl xdotool
|
58
165
|
|
59
166
|
If you have that, run:
|
60
167
|
|
61
|
-
wmctile help
|
168
|
+
wmctile --help
|
62
169
|
|
63
|
-
to show the available commands.
|
170
|
+
to show the available commands.
|
64
171
|
rdoc_options: []
|
65
172
|
require_paths:
|
66
173
|
- lib
|
@@ -81,3 +188,4 @@ signing_key:
|
|
81
188
|
specification_version: 4
|
82
189
|
summary: wmctile
|
83
190
|
test_files: []
|
191
|
+
has_rdoc:
|
data/lib/wmctile/class.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
class Wmctile::Class
|
2
|
-
def cmd cmd
|
3
|
-
# [0..-2] to strip the last \n
|
4
|
-
`#{ cmd }`[0..-2]
|
5
|
-
end
|
6
|
-
def notify title, string, icon = nil
|
7
|
-
if icon
|
8
|
-
system "notify-send -i '#{ icon }' '#{title}' '#{string}'"
|
9
|
-
else
|
10
|
-
system "notify-send '#{title}' '#{string}'"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'dmenu'
|
2
|
-
|
3
|
-
class Wmctile::ClassWithDmenu < Wmctile::Class
|
4
|
-
def dmenu items
|
5
|
-
a = Dmenu.new
|
6
|
-
# override defaults
|
7
|
-
a.background = '#242424'
|
8
|
-
a.case_insensitive = true
|
9
|
-
a.font = 'Ubuntu Mono-12'
|
10
|
-
a.foreground = 'white'
|
11
|
-
a.lines = 10
|
12
|
-
a.position = :bottom
|
13
|
-
a.selected_background = '#2e557e'
|
14
|
-
# set items
|
15
|
-
a.items = items
|
16
|
-
# run
|
17
|
-
b = a.run()
|
18
|
-
if b.is_a? Dmenu::Item
|
19
|
-
return b.value
|
20
|
-
else
|
21
|
-
return b
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/wmctile/memory.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
class Wmctile::Memory < Wmctile::Class
|
4
|
-
##################################
|
5
|
-
## init ##########################
|
6
|
-
##################################
|
7
|
-
def initialize
|
8
|
-
@path = File.expand_path '~/.config/wmctile/wmctile-memory.yml'
|
9
|
-
if not File.exist? @path
|
10
|
-
raw_memory = self.create_new_memory
|
11
|
-
end
|
12
|
-
raw_memory ||= File.read @path
|
13
|
-
@memory = YAML.load(raw_memory)
|
14
|
-
end
|
15
|
-
def create_new_memory
|
16
|
-
dir_path = @path[/(.*)\/wmctile-memory.yml/, 1]
|
17
|
-
if not Dir.exists? dir_path
|
18
|
-
Dir.mkdir dir_path
|
19
|
-
end
|
20
|
-
out_file = File.new @path, 'w'
|
21
|
-
# 20 workspaces should suffice
|
22
|
-
20.times do |i|
|
23
|
-
out_file.puts "#{i}:"
|
24
|
-
end
|
25
|
-
out_file.close
|
26
|
-
end
|
27
|
-
def write_memory
|
28
|
-
out_file = File.new @path, 'w'
|
29
|
-
out_file.puts @memory.to_yaml
|
30
|
-
out_file.close
|
31
|
-
end
|
32
|
-
##################################
|
33
|
-
## getters/setters ###############
|
34
|
-
##################################
|
35
|
-
def get workspace = 0, key = nil, key_sub = nil
|
36
|
-
begin
|
37
|
-
a = @memory[workspace]
|
38
|
-
if key.nil?
|
39
|
-
return nil
|
40
|
-
else
|
41
|
-
a = a[key]
|
42
|
-
if key_sub.nil?
|
43
|
-
return a
|
44
|
-
else
|
45
|
-
return a[key_sub]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
rescue Exception => e
|
49
|
-
return nil
|
50
|
-
end
|
51
|
-
end
|
52
|
-
def set workspace = 0, key, hash
|
53
|
-
hash.merge! 'time' => Time.now.to_i
|
54
|
-
if @memory[workspace].nil?
|
55
|
-
@memory[workspace] = {}
|
56
|
-
end
|
57
|
-
if @memory[workspace][key]
|
58
|
-
@memory[workspace][key] = hash
|
59
|
-
else
|
60
|
-
@memory[workspace].merge! key => hash
|
61
|
-
end
|
62
|
-
self.write_memory
|
63
|
-
end
|
64
|
-
end
|
data/lib/wmctile/settings.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
class Wmctile::Settings < Wmctile::Class
|
4
|
-
def method_missing sym, *args, &block
|
5
|
-
return false
|
6
|
-
end
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
path = File.expand_path '~/.config/wmctile/wmctile-settings.yml'
|
10
|
-
if not File.exist? path
|
11
|
-
raw_settings = self.create_new_settings path
|
12
|
-
end
|
13
|
-
raw_settings ||= File.read path
|
14
|
-
settings = YAML.load(raw_settings)
|
15
|
-
if settings
|
16
|
-
settings.each { |name, value|
|
17
|
-
instance_variable_set("@#{name}", value)
|
18
|
-
self.class.class_eval { attr_reader name.intern }
|
19
|
-
}
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_requirements
|
24
|
-
req = ['xrandr', 'wmctrl', 'dmenu']
|
25
|
-
ret = req.reject { |r| self.cmd("which #{ r }").length > 0 }
|
26
|
-
return ret
|
27
|
-
end
|
28
|
-
def create_new_settings path
|
29
|
-
req = self.test_requirements
|
30
|
-
unless req.nil? or req.length == 0
|
31
|
-
puts <<-eos
|
32
|
-
You don't have #{ req.join(', ') } installed. Wmctile can't run without that.
|
33
|
-
|
34
|
-
To fix this on Ubuntu, run:
|
35
|
-
|
36
|
-
sudo apt-get install #{ req.join(' ') }
|
37
|
-
eos
|
38
|
-
exit
|
39
|
-
end
|
40
|
-
dir_path = path[/(.*)\/wmctile-settings.yml/, 1]
|
41
|
-
if not Dir.exists? dir_path
|
42
|
-
Dir.mkdir dir_path
|
43
|
-
end
|
44
|
-
out_file = File.new path, 'w'
|
45
|
-
out_file.puts self.default_settings.to_yaml
|
46
|
-
out_file.close
|
47
|
-
end
|
48
|
-
def default_settings
|
49
|
-
{
|
50
|
-
:window_border => 1,
|
51
|
-
:panel_height => 24,
|
52
|
-
:panel_width => 0,
|
53
|
-
:hostname => self.cmd('hostname')
|
54
|
-
}
|
55
|
-
end
|
56
|
-
end
|
@@ -1,174 +0,0 @@
|
|
1
|
-
class Wmctile::WindowManager < Wmctile::ClassWithDmenu
|
2
|
-
attr_accessor :w, :h, :workspace, :windows
|
3
|
-
##################################
|
4
|
-
## init ##########################
|
5
|
-
##################################
|
6
|
-
def initialize settings
|
7
|
-
@settings = settings
|
8
|
-
self.init_dimensions
|
9
|
-
end
|
10
|
-
def init_dimensions
|
11
|
-
# legacy (but fast) method via wmctrl
|
12
|
-
# dimensions = cmd("wmctrl -d | awk '{ print $9 }' | head -n1").split('x')
|
13
|
-
# xrandr is slower, but offers more information
|
14
|
-
dimensions = cmd("xrandr | grep -E '\sconnected\s[0-9]+x[0-9]+\+0' | awk '{print $3}' | awk -F'+' '{print $1}'").split('x')
|
15
|
-
@w = dimensions[0].to_i - 2*@settings.window_border
|
16
|
-
@h = dimensions[1].to_i - 2*@settings.window_border - @settings.panel_height
|
17
|
-
@workspace = cmd("wmctrl -d | grep '\*' | awk '{ print $1 }'").to_i
|
18
|
-
end
|
19
|
-
##################################
|
20
|
-
## dimension getters #############
|
21
|
-
##################################
|
22
|
-
def width portion = 1
|
23
|
-
@w * portion
|
24
|
-
end
|
25
|
-
def height portion = 1
|
26
|
-
@h * portion
|
27
|
-
end
|
28
|
-
##################################
|
29
|
-
## window getters ################
|
30
|
-
##################################
|
31
|
-
def get_window window_str = nil, all_workspaces = false
|
32
|
-
if window_str.nil?
|
33
|
-
window = self.ask_for_window all_workspaces
|
34
|
-
else
|
35
|
-
if window_str == ':ACTIVE:'
|
36
|
-
window = self.get_active_window
|
37
|
-
else
|
38
|
-
window = self.find_window window_str, all_workspaces
|
39
|
-
unless window
|
40
|
-
# does window_str have an icon bundle in it? (aka evince.Evince)
|
41
|
-
if window_str =~ /[a-z0-9]+\.[a-z0-9]+/i
|
42
|
-
icon = window_str.split('.').first
|
43
|
-
else
|
44
|
-
icon = nil
|
45
|
-
end
|
46
|
-
self.notify 'No window found', "#{ window_str }", icon
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
window
|
51
|
-
end
|
52
|
-
def get_active_window
|
53
|
-
win_id = self.cmd('wmctrl -a :ACTIVE: -v 2>&1').split('Using window: ').last
|
54
|
-
Wmctile::Window.new win_id, @settings
|
55
|
-
end
|
56
|
-
def find_window window_string, all_workspaces = false
|
57
|
-
cmd = "wmctrl -lx | grep -F #{ window_string }"
|
58
|
-
cmd += ' | grep -E \'0x\w+\s+' + @workspace.to_s + '\s+\'' unless all_workspaces
|
59
|
-
window_string = self.cmd cmd
|
60
|
-
window_string = window_string.split("\n").first
|
61
|
-
if window_string.nil?
|
62
|
-
return nil
|
63
|
-
else
|
64
|
-
return Wmctile::Window.new window_string, @settings
|
65
|
-
end
|
66
|
-
end
|
67
|
-
def find_windows window_string, all_workspaces = false
|
68
|
-
cmd = "wmctrl -lx | grep -F #{ window_string }"
|
69
|
-
cmd += ' | grep -E \'0x\w+\s+' + @workspace.to_s + '\s+\'' unless all_workspaces
|
70
|
-
window_strings = self.cmd cmd
|
71
|
-
window_strings = window_strings.split("\n")
|
72
|
-
if window_string.nil?
|
73
|
-
return nil
|
74
|
-
else
|
75
|
-
return window_strings.map { |w| Wmctile::Window.new w, @settings }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
def find_in_windows window_string, all_workspaces = false
|
79
|
-
if window_string.nil?
|
80
|
-
self.ask_for_window all_workspaces
|
81
|
-
else
|
82
|
-
windows = self.find_windows window_string, all_workspaces
|
83
|
-
if windows
|
84
|
-
ids = windows.collect(&:id)
|
85
|
-
active_win = self.get_active_window
|
86
|
-
if ids.include? active_win.id
|
87
|
-
# cycle through the windows
|
88
|
-
i = ids.index active_win.id
|
89
|
-
# try the next one
|
90
|
-
if ids[i+1]
|
91
|
-
window = windows[i+1]
|
92
|
-
# fallback to the first one
|
93
|
-
else
|
94
|
-
window = windows.first
|
95
|
-
end
|
96
|
-
else
|
97
|
-
# switch to the first one
|
98
|
-
window = windows.first
|
99
|
-
end
|
100
|
-
return window
|
101
|
-
end
|
102
|
-
return nil
|
103
|
-
end
|
104
|
-
end
|
105
|
-
def ask_for_window all_workspaces = false
|
106
|
-
self.dmenu self.windows.map(&:dmenu_item)
|
107
|
-
end
|
108
|
-
##################################
|
109
|
-
## window lists ##################
|
110
|
-
##################################
|
111
|
-
def build_win_list all_workspaces = false
|
112
|
-
unless all_workspaces
|
113
|
-
variable_name = '@windows_on_workspace'
|
114
|
-
else
|
115
|
-
variable_name = '@windows_all'
|
116
|
-
end
|
117
|
-
unless instance_variable_get(variable_name)
|
118
|
-
unless all_workspaces
|
119
|
-
cmd = "wmctrl -lx | grep \" #{ @workspace } \""
|
120
|
-
else
|
121
|
-
cmd = "wmctrl -lx"
|
122
|
-
end
|
123
|
-
arr = cmd(cmd).split("\n")
|
124
|
-
|
125
|
-
new_list = arr.map { |w| Wmctile::Window.new(w, @settings) }
|
126
|
-
name_length = new_list.map(&:get_name_length).max
|
127
|
-
new_list.each { |w| w.set_name_length(name_length) }
|
128
|
-
|
129
|
-
instance_variable_set(variable_name, new_list)
|
130
|
-
end
|
131
|
-
instance_variable_get(variable_name)
|
132
|
-
end
|
133
|
-
def windows all_workspaces = false
|
134
|
-
unless all_workspaces
|
135
|
-
variable_name = '@windows_on_workspace'
|
136
|
-
else
|
137
|
-
variable_name = '@windows_all'
|
138
|
-
end
|
139
|
-
unless instance_variable_get(variable_name)
|
140
|
-
self.build_win_list all_workspaces
|
141
|
-
else
|
142
|
-
instance_variable_get(variable_name)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
##################################
|
146
|
-
## window size calculators #######
|
147
|
-
##################################
|
148
|
-
def calculate_snap where, portion = 0.5
|
149
|
-
return case where
|
150
|
-
when 'left'
|
151
|
-
{
|
152
|
-
:x => @settings.panel_width, :y => @settings.panel_height,
|
153
|
-
:height => self.height, :width => self.width(portion)
|
154
|
-
}
|
155
|
-
when 'right'
|
156
|
-
{
|
157
|
-
:x => self.width(portion), :y => @settings.panel_height,
|
158
|
-
:height => self.height, :width => self.width(1-portion)
|
159
|
-
}
|
160
|
-
when 'top'
|
161
|
-
{
|
162
|
-
:x => @settings.panel_width, :y => @settings.panel_height,
|
163
|
-
:height => self.height(portion), :width => self.width
|
164
|
-
}
|
165
|
-
when 'bottom'
|
166
|
-
{
|
167
|
-
:x => @settings.panel_width, :y => @settings.panel_height + self.height(1-portion),
|
168
|
-
:height => self.height(portion), :width => self.width
|
169
|
-
}
|
170
|
-
else
|
171
|
-
nil
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
data/lib/wmctile/window_tiler.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
class Wmctile::WindowTiler < Wmctile::Class
|
2
|
-
##################################
|
3
|
-
## init ##########################
|
4
|
-
##################################
|
5
|
-
def initialize settings, memory, wm = nil
|
6
|
-
@settings = settings
|
7
|
-
@memory = memory
|
8
|
-
@wm = wm
|
9
|
-
end
|
10
|
-
##################################
|
11
|
-
## object getter methods #########
|
12
|
-
##################################
|
13
|
-
def wm
|
14
|
-
@wm || @wm = Wmctile::WindowManager.new(@settings)
|
15
|
-
end
|
16
|
-
def memory
|
17
|
-
@memory || @memory = Wmctile::Memory.new
|
18
|
-
end
|
19
|
-
##################################
|
20
|
-
## actual snapping methods #######
|
21
|
-
##################################
|
22
|
-
def snap where = 'left', window_str = nil, portion = 0.5
|
23
|
-
window = self.wm.get_window window_str
|
24
|
-
if window
|
25
|
-
how_to_move = self.wm.calculate_snap where, portion.to_f
|
26
|
-
if how_to_move
|
27
|
-
window.move how_to_move
|
28
|
-
self.memory.set self.wm.workspace, 'snap', {
|
29
|
-
'where' => where, 'portion' => portion, 'window_id' => window.id
|
30
|
-
}
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
def resize where = 'left', portion = 0.01
|
35
|
-
portion = portion.to_f
|
36
|
-
# what are we moving? the last one used from these:
|
37
|
-
methods = ['snap']
|
38
|
-
freshest_meth = nil
|
39
|
-
freshest_time = 0
|
40
|
-
methods.each do |meth|
|
41
|
-
time = self.memory.get self.wm.workspace, meth, 'time'
|
42
|
-
if time > freshest_time
|
43
|
-
freshest_time = time
|
44
|
-
freshest_meth = meth
|
45
|
-
end
|
46
|
-
end
|
47
|
-
# ok, got it, it's freshest_meth
|
48
|
-
self.send "resize_#{ freshest_meth }", where, portion
|
49
|
-
end
|
50
|
-
def resize_snap where = 'left', portion = 0.01
|
51
|
-
portion = portion.to_f
|
52
|
-
info = self.memory.get self.wm.workspace, 'snap'
|
53
|
-
if info.nil?
|
54
|
-
return nil
|
55
|
-
end
|
56
|
-
negative = case info['where']
|
57
|
-
when 'left'
|
58
|
-
if where == 'left'
|
59
|
-
true
|
60
|
-
elsif where == 'right'
|
61
|
-
false
|
62
|
-
else
|
63
|
-
nil
|
64
|
-
end
|
65
|
-
when 'right'
|
66
|
-
if where == 'left'
|
67
|
-
false
|
68
|
-
elsif where == 'right'
|
69
|
-
true
|
70
|
-
else
|
71
|
-
nil
|
72
|
-
end
|
73
|
-
when 'top'
|
74
|
-
if where == 'bottom'
|
75
|
-
false
|
76
|
-
elsif where == 'top'
|
77
|
-
true
|
78
|
-
else
|
79
|
-
nil
|
80
|
-
end
|
81
|
-
when 'bottom'
|
82
|
-
if where == 'top'
|
83
|
-
false
|
84
|
-
elsif where == 'bottom'
|
85
|
-
true
|
86
|
-
else
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
end
|
90
|
-
unless negative.nil?
|
91
|
-
portion = -portion if negative
|
92
|
-
self.snap info['where'], info['window_id'], info['portion']+portion
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|