wmctile 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|