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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16b4da5f2f8892f738465972730909f76079953b
4
- data.tar.gz: 3d729136ffae9de4207c4c98bf22535520c43ca3
3
+ metadata.gz: cedbd5d2774446b458abd0a2606c1febd81115f4
4
+ data.tar.gz: d5749f69feaa8bff5410816fc0901fce8f52aad5
5
5
  SHA512:
6
- metadata.gz: 5058b584b245cc4723496cb5169804e051b99a67dd7b5086c3769b388ac521b11237d7c933c22d40d353f1d25c1be7aff7d84492481673eb771b841f73f408b6
7
- data.tar.gz: e70823a836615baabb513342a5804f904309c1c337876962f143ddd5dae239a8703f6e2be6cd245018fdc1f201c058a9bdc1e31aed39bf55468e7946f8589e72
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
- router = Wmctile::Router.new
5
- router.dispatch ARGV
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/class'
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
@@ -1,187 +1,53 @@
1
- class Wmctile::Router < Wmctile::Class
2
- ##################################
3
- ## init ##########################
4
- ##################################
5
- def initialize
6
- @settings = Wmctile::Settings.new
7
- @all_workspaces = false
8
- end
9
- ##################################
10
- ## main dispatch method ##########
11
- ##################################
12
- def dispatch args = []
13
- if args.length
14
- main_arg = args[0]
15
- if ['--all-workspaces', '-a'].include? main_arg
16
- @all_workspaces = true
17
- drop = 2
18
- main_arg = args[1]
19
- else
20
- @all_workspaces = false
21
- drop = 1
22
- end
23
- if main_arg and !['dispatch', 'initialize', 'wm', 'wt', 'memory'].include? main_arg and self.respond_to? main_arg
24
- self.send main_arg, *args.drop(drop)
25
- else
26
- self.help
27
- end
28
- else
29
- self.help
30
- end
31
- end
32
- ##################################
33
- ## object getter methods #########
34
- ##################################
35
- def wm
36
- @wm || @wm = Wmctile::WindowManager.new(@settings)
37
- end
38
- def wt
39
- # @wm might be nil
40
- @wt || @wt = Wmctile::WindowTiler.new(@settings, self.memory, @wm)
41
- end
42
- def memory
43
- @memory || @memory = Wmctile::Memory.new
44
- end
45
- ##################################
46
- ## actual command-line methods ###
47
- ##################################
48
- def help args = nil
49
- puts <<-eos
50
- wmctile version 0.1.2
51
-
52
- usage:
53
- wmctile [--option1, --option2, ...] <command> ['argument1', 'argument2', ...]
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
@@ -1,69 +1,121 @@
1
- class Wmctile::Window < Wmctile::Class
2
- attr_accessor :id, :name, :title
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
- def initialize win_string, settings
5
- @default_movement = { :x => 0, :y => 0, :width => '-1', :height => '-1' }
6
- @settings = settings
7
- @id = win_string[/0x[\d\w]{8}/]
8
- self.get_name_and_title win_string
9
- end
10
- def get_name_and_title win_string
11
- if win_string == @id
12
- @name = ''
13
- @title = ''
14
- else
15
- after_id_and_workspace = win_string[14..-1].split(/\s+#{ @settings.hostname }\s+/, 2)
16
- @name = after_id_and_workspace[0]
17
- @title = after_id_and_workspace[1]
18
- end
19
- end
20
- def dmenu_item
21
- unless @dmenu_item
22
- str = "#{ @id } #{ @name } #{ @title }"
23
- @dmenu_item = Dmenu::Item.new str, self
24
- end
25
- @dmenu_item
26
- end
27
- def get_name
28
- if @name == ''
29
- self.get_name_and_title self.cmd('wmctrl -lx | grep ' + @id)
30
- end
31
- @name
32
- end
33
- def get_name_length
34
- @name.length
35
- end
36
- def set_name_length name_length
37
- @name += ' '*(name_length - @name.length)
38
- end
39
- def wmctrl wm_cmd = '', summon = false
40
- self.cmd "wmctrl -i#{ summon ? 'R' : 'r' } #{ @id } #{ wm_cmd }"
41
- return self # return self so that commands can be chained
42
- end
43
- def move how_to_move = {}
44
- how_to_move = @default_movement.merge! how_to_move
45
- cmd = "-e 0,#{ how_to_move[:x].to_i },#{ how_to_move[:y].to_i },#{ how_to_move[:width].to_i },#{ how_to_move[:height].to_i }"
46
- self.unshade
47
- self.unmaximize
48
- self.wmctrl cmd
49
- end
50
- def shade
51
- self.wmctrl '-b add,shaded'
52
- end
53
- def unshade
54
- self.wmctrl '-b remove,shaded'
55
- end
56
- def summon
57
- self.wmctrl '', true
58
- end
59
- def switch_to
60
- self.cmd "wmctrl -ia #{ @id }"
61
- return self
62
- end
63
- def maximize
64
- self.wmctrl '-b add,maximized_vert,maximized_horz'
65
- end
66
- def unmaximize
67
- self.wmctrl '-b remove,maximized_vert,maximized_horz'
68
- end
69
- end
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.1.2
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: 2014-05-09 00:00:00.000000000 Z
11
+ date: 2016-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: dmenu
14
+ name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.0'
20
- type: :runtime
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.0'
27
- description: Window manager's best friend in a gem.
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/class.rb
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-2
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
- xrandr
53
- dmenu
160
+ xdotool
54
161
 
55
162
  On Ubuntu it's as easy as running:
56
163
 
57
- sudo apt-get install wmctrl, xrandr, dmenu
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. Also be sure to check the detailed docs at http://mreq.github.io/wmctile/build/docs.
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
@@ -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
@@ -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
@@ -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