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 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