rumai 3.2.4 → 3.3.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.
data/CREDITS CHANGED
@@ -9,17 +9,12 @@ Suraj N. Kurapati
9
9
  %#----------------------------------------------------------------------------
10
10
 
11
11
  Christoph Blank,
12
- Simon Hafner,
12
+ Kenneth De Winter,
13
+ Mattia Gheda,
13
14
  Michael Andrus,
14
15
  Nathan Neff,
15
- [Gigamo],
16
- [ghedamat],
17
- [skirge]
18
-
19
- %# XXX: only link to these contributors because they do not have real names
20
- [Gigamo]: http://github.com/gigamo
21
- [ghedamat]: http://github.com/ghedamat
22
- [skirge]: http://github.com/skirge
16
+ Sebastian Chmielewski,
17
+ Simon Hafner
23
18
 
24
19
  %#----------------------------------------------------------------------------
25
20
  ## LICENSE
data/lib/rumai/fs.rb CHANGED
@@ -15,12 +15,11 @@ module Rumai
15
15
  IXP_AGENT = IXP::Agent.new(UNIXSocket.new(IXP_SOCK_ADDR))
16
16
 
17
17
  rescue => error
18
- error.message << %{
19
- Ensure that (1) the WMII_ADDRESS environment variable is set and that (2)
20
- it correctly specifies the filesystem path of wmii's IXP socket file,
21
- which is typically located at "/tmp/ns.$USER.:$DISPLAY/wmii".
22
- }.gsub(/^ +/, '').gsub(/\A|\z/, "\n")
23
-
18
+ error.message <<EOF
19
+ Ensure that (1) the WMII_ADDRESS environment variable is set and that (2) it
20
+ correctly specifies the absolute filesystem path to wmii's IXP socket file,
21
+ which is typically located at "/tmp/ns.$USER.:$DISPLAY/wmii".
22
+ EOF
24
23
  raise error
25
24
  end
26
25
 
@@ -28,8 +27,6 @@ module Rumai
28
27
  # An entry in the IXP file system.
29
28
  #
30
29
  class Node
31
- @@cache = Hash.new {|h,k| h[k] = Node.new(k) }
32
-
33
30
  attr_reader :path
34
31
 
35
32
  def initialize path
@@ -59,6 +56,8 @@ module Rumai
59
56
  ##
60
57
  # Tests if this node is a directory.
61
58
  #
59
+ # @see Rumai::IXP::Agent#stat
60
+ #
62
61
  def directory?
63
62
  exist? and stat.directory?
64
63
  end
@@ -66,6 +65,8 @@ module Rumai
66
65
  ##
67
66
  # Returns the names of all files in this directory.
68
67
  #
68
+ # @see Rumai::IXP::Agent#entries
69
+ #
69
70
  def entries
70
71
  begin
71
72
  IXP_AGENT.entries @path
@@ -98,6 +99,7 @@ module Rumai
98
99
  # @yieldparam [String] line
99
100
  #
100
101
  def each_line &block
102
+ raise ArgumentError unless block_given?
101
103
  open do |file|
102
104
  until (chunk = file.read(true)).empty?
103
105
  chunk.each_line(&block)
@@ -108,6 +110,8 @@ module Rumai
108
110
  ##
109
111
  # Writes the given content to this node.
110
112
  #
113
+ # @see Rumai::IXP::Agent#write
114
+ #
111
115
  def write content
112
116
  IXP_AGENT.write @path, content
113
117
  end
@@ -124,10 +128,14 @@ module Rumai
124
128
  ##
125
129
  # Deletes the file corresponding to this node on the IXP server.
126
130
  #
131
+ # @see Rumai::IXP::Agent#remove
132
+ #
127
133
  def remove
128
134
  IXP_AGENT.remove @path
129
135
  end
130
136
 
137
+ @@cache = Hash.new {|h,k| h[k] = Node.new(k) }
138
+
131
139
  ##
132
140
  # Returns the given sub-path as a Node object.
133
141
  #
data/lib/rumai/inochi.rb CHANGED
@@ -3,27 +3,27 @@ module Rumai
3
3
  ##
4
4
  # Official name of this project.
5
5
  #
6
- PROJECT = "Rumai"
6
+ PROJECT = 'Rumai'
7
7
 
8
8
  ##
9
9
  # Short single-line description of this project.
10
10
  #
11
- TAGLINE = "Ruby interface to the wmii window manager"
11
+ TAGLINE = 'Ruby interface to the wmii window manager'
12
12
 
13
13
  ##
14
14
  # Address of this project's official home page.
15
15
  #
16
- WEBSITE = "http://snk.tuxfamily.org/lib/rumai/"
16
+ WEBSITE = 'http://snk.tuxfamily.org/lib/rumai/'
17
17
 
18
18
  ##
19
19
  # Number of this release of this project.
20
20
  #
21
- VERSION = "3.2.4"
21
+ VERSION = '3.3.0'
22
22
 
23
23
  ##
24
24
  # Date of this release of this project.
25
25
  #
26
- RELDATE = "2010-06-06"
26
+ RELDATE = '2010-07-16'
27
27
 
28
28
  ##
29
29
  # Description of this release of this project.
@@ -74,8 +74,8 @@ module Rumai
74
74
  # }
75
75
  #
76
76
  DEVTIME = {
77
- "inochi" => [ ">= 3.0.0", "< 4" ],
78
- "dfect" => [ "~> 2" ], # for unit testing
77
+ 'inochi' => [ '>= 3.0.0', '< 4' ],
78
+ 'dfect' => [ '~> 2' ], # for unit testing
79
79
  }
80
80
 
81
81
  # establish gem version dependencies
@@ -76,8 +76,8 @@ module Rumai
76
76
  rescue ThreadError
77
77
  # pool is empty, so fill it
78
78
  FILL_RATE.times do
79
- if @pos != @lim
80
- @pool << @pos
79
+ if @pos != @lim then
80
+ @pool.enq @pos
81
81
  @pos = @pos.succ
82
82
  else
83
83
  # range is exhausted, so give other threads
@@ -96,7 +96,7 @@ module Rumai
96
96
  # that it may be occupied again in the future.
97
97
  #
98
98
  def release member
99
- @pool << member
99
+ @pool.enq member
100
100
  end
101
101
  end
102
102
 
@@ -192,7 +192,7 @@ module Rumai
192
192
  #
193
193
  def MODES.parse mode
194
194
  if mode.respond_to? :split
195
- mode.split(//).inject(0) {|m,c| m | self[c].to_i }
195
+ mode.split(//).inject(0) {|acc,chr| acc | self[chr].to_i }
196
196
  else
197
197
  mode.to_i
198
198
  end
@@ -206,16 +206,12 @@ module Rumai
206
206
  # @see File::open
207
207
  #
208
208
  def open path, mode = 'r'
209
- mode = MODES.parse(mode)
210
-
211
209
  # open the file
212
210
  path_fid = walk(path)
213
-
214
211
  talk Topen.new(
215
212
  :fid => path_fid,
216
- :mode => mode
213
+ :mode => MODES.parse(mode)
217
214
  )
218
-
219
215
  stream = FidStream.new(self, path_fid, @msize)
220
216
 
221
217
  # return the file stream
@@ -355,9 +351,7 @@ module Rumai
355
351
  # Returns the content of the file/directory at the given path.
356
352
  #
357
353
  def read path, *args
358
- open path do |f|
359
- f.read(*args)
360
- end
354
+ open(path) {|f| f.read(*args) }
361
355
  end
362
356
 
363
357
  ##
@@ -371,7 +365,7 @@ module Rumai
371
365
  raise ArgumentError, "#{path.inspect} is not a directory"
372
366
  end
373
367
 
374
- read(path).map! {|t| t.name}
368
+ read(path).map! {|t| t.name }
375
369
  end
376
370
 
377
371
  ##
@@ -379,9 +373,7 @@ module Rumai
379
373
  # the file at the given path.
380
374
  #
381
375
  def write path, content
382
- open path, 'w' do |f|
383
- f << content
384
- end
376
+ open(path, 'w') {|f| f.write content }
385
377
  end
386
378
 
387
379
  ##
@@ -392,8 +384,6 @@ module Rumai
392
384
  prefix = File.dirname(path)
393
385
  target = File.basename(path)
394
386
 
395
- mode = MODES.parse(mode)
396
-
397
387
  with_fid do |prefix_fid|
398
388
  walk_fid prefix_fid, prefix
399
389
 
@@ -402,7 +392,7 @@ module Rumai
402
392
  :fid => prefix_fid,
403
393
  :name => target,
404
394
  :perm => perm,
405
- :mode => mode
395
+ :mode => MODES.parse(mode)
406
396
  )
407
397
  end
408
398
  end
data/lib/rumai/wm.rb CHANGED
@@ -21,1079 +21,1179 @@ module Rumai
21
21
  # abstraction of WM components
22
22
  #---------------------------------------------------------------------------
23
23
 
24
+ ##
25
+ # @note Inheritors must override the {Chain#chain} method.
26
+ #
27
+ module Chain
24
28
  ##
25
- # @note Inheritors must override the {Chain#chain} method.
29
+ # Returns an array of objects related to this one.
26
30
  #
27
- module Chain
28
- ##
29
- # Returns an array of objects related to this one.
30
- #
31
- def chain
32
- [self]
33
- end
31
+ def chain
32
+ [self]
33
+ end
34
34
 
35
- ##
36
- # Returns the object after this one in the chain.
37
- #
38
- def next
39
- sibling(+1)
40
- end
35
+ ##
36
+ # Returns the object after this one in the chain.
37
+ #
38
+ def next
39
+ sibling(+1)
40
+ end
41
41
 
42
- ##
43
- # Returns the object before this one in the chain.
44
- #
45
- def prev
46
- sibling(-1)
47
- end
42
+ ##
43
+ # Returns the object before this one in the chain.
44
+ #
45
+ def prev
46
+ sibling(-1)
47
+ end
48
48
 
49
- private
49
+ private
50
50
 
51
- def sibling offset
52
- arr = chain
51
+ def sibling offset
52
+ arr = chain
53
53
 
54
- if pos = arr.index(self)
55
- arr[(pos + offset) % arr.length]
56
- end
54
+ if pos = arr.index(self)
55
+ arr[(pos + offset) % arr.length]
57
56
  end
58
57
  end
58
+ end
59
+
60
+ ##
61
+ # The basic building block of the WM hierarchy.
62
+ #
63
+ # @note Inheritors must define a {curr} class method.
64
+ # @note Inheritors must override the {focus} method.
65
+ #
66
+ module WidgetImpl
67
+ attr_reader :id
68
+
69
+ def == other
70
+ @id == other.id
71
+ end
59
72
 
60
73
  ##
61
- # The basic building block of the WM hierarchy.
62
- #
63
- # @note Inheritors must define a {curr} class method.
64
- # @note Inheritors must override the {focus} method.
74
+ # Checks if this widget currently has focus.
65
75
  #
66
- module WidgetImpl
67
- attr_reader :id
76
+ def current?
77
+ self == self.class.curr
78
+ end
68
79
 
69
- def == other
70
- @id == other.id
71
- end
80
+ alias focus? current?
81
+ end
72
82
 
73
- ##
74
- # Checks if this widget currently has focus.
75
- #
76
- def current?
77
- self == self.class.curr
83
+ ##
84
+ # A widget that has a corresponding representation in the IXP file system.
85
+ #
86
+ class WidgetNode < Node
87
+ include WidgetImpl
88
+
89
+ def initialize id, path_prefix
90
+ super "#{path_prefix}/#{id}"
91
+
92
+ if id == FOCUSED_WIDGET_ID and ctl.exist?
93
+ @id = ctl.read.split.first
94
+ super "#{path_prefix}/#{@id}"
95
+ else
96
+ @id = id.to_s
78
97
  end
98
+ end
99
+ end
100
+
101
+ ##
102
+ # A graphical program that is running in your current X Windows session.
103
+ #
104
+ class Client < WidgetNode
105
+ def initialize client_id
106
+ super client_id, '/client'
107
+ end
79
108
 
80
- alias focus? current?
109
+ ##
110
+ # Returns the currently focused client.
111
+ #
112
+ def self.curr
113
+ new FOCUSED_WIDGET_ID
81
114
  end
82
115
 
116
+ #-------------------------------------------------------------------------
117
+ include Chain
118
+ #-------------------------------------------------------------------------
119
+
83
120
  ##
84
- # A widget that has a corresponding representation in the IXP file system.
121
+ # Returns a list of all clients in the current view.
85
122
  #
86
- class WidgetNode < Node
87
- include WidgetImpl
123
+ def chain
124
+ View.curr.clients
125
+ end
88
126
 
89
- def initialize id, path_prefix
90
- super "#{path_prefix}/#{id}"
127
+ #-------------------------------------------------------------------------
128
+ # WM operations
129
+ #-------------------------------------------------------------------------
91
130
 
92
- if id == FOCUSED_WIDGET_ID and ctl.exist?
93
- @id = ctl.read.split.first
94
- super "#{path_prefix}/#{@id}"
95
- else
96
- @id = id.to_s
131
+ ##
132
+ # Focuses this client within the given view.
133
+ #
134
+ def focus view = nil
135
+ if exist? and not focus?
136
+ (view ? [view] : self.views).each do |v|
137
+ if a = self.area(v) and a.exist?
138
+ v.focus
139
+ a.focus
140
+
141
+ # slide focus from the current client onto this client
142
+ arr = a.client_ids
143
+ src = arr.index Client.curr.id
144
+ dst = arr.index @id
145
+
146
+ distance = (src - dst).abs
147
+ direction = src < dst ? :down : :up
148
+
149
+ distance.times { v.select direction }
150
+ break
151
+ end
97
152
  end
98
153
  end
99
154
  end
100
155
 
101
156
  ##
102
- # A graphical program that is running in your current X Windows session.
157
+ # Sends this client to the given destination within the given view.
103
158
  #
104
- class Client < WidgetNode
105
- def initialize client_id
106
- super client_id, '/client'
107
- end
108
-
109
- ##
110
- # Returns the currently focused client.
111
- #
112
- def self.curr
113
- new FOCUSED_WIDGET_ID
114
- end
115
-
116
- include Chain
159
+ def send area_or_id, view = View.curr
160
+ dst = area_to_id(area_or_id)
161
+ view.ctl.write "send #{@id} #{dst}"
162
+ end
117
163
 
118
- ##
119
- # Returns a list of all clients in the current view.
120
- #
121
- def chain
122
- View.curr.clients
123
- end
164
+ alias move send
124
165
 
125
- #-----------------------------------------------------------------------
126
- # WM operations
127
- #-----------------------------------------------------------------------
128
-
129
- ##
130
- # Focuses this client within the given view.
131
- #
132
- def focus view = nil
133
- if exist? and not focus?
134
- (view ? [view] : self.views).each do |v|
135
- if a = self.area(v) and a.exist?
136
- v.focus
137
- a.focus
138
-
139
- # slide focus from the current client onto this client
140
- arr = a.client_ids
141
- src = arr.index Client.curr.id
142
- dst = arr.index @id
143
-
144
- distance = (src - dst).abs
145
- direction = src < dst ? :down : :up
146
-
147
- distance.times { v.select direction }
148
- break
149
- end
150
- end
151
- end
152
- end
166
+ ##
167
+ # Swaps this client with the given destination within the given view.
168
+ #
169
+ def swap area_or_id, view = View.curr
170
+ dst = area_to_id(area_or_id)
171
+ view.ctl.write "swap #{@id} #{dst}"
172
+ end
153
173
 
154
- ##
155
- # Sends this client to the given destination within the given view.
156
- #
157
- def send area_or_id, view = View.curr
158
- dst = area_to_id(area_or_id)
159
- view.ctl.write "send #{@id} #{dst}"
160
- end
174
+ ##
175
+ # Moves this client in the given direction on the given view.
176
+ #
177
+ def nudge direction, view = View.curr
178
+ reshape :nudge, direction, view
179
+ end
161
180
 
162
- alias move send
181
+ ##
182
+ # Grows this client in the given direction on the given view.
183
+ #
184
+ def grow direction, view = View.curr
185
+ reshape :grow, direction, view
186
+ end
163
187
 
164
- ##
165
- # Swaps this client with the given destination within the given view.
166
- #
167
- def swap area_or_id, view = View.curr
168
- dst = area_to_id(area_or_id)
169
- view.ctl.write "swap #{@id} #{dst}"
170
- end
188
+ ##
189
+ # Terminates this client nicely (requests this window to be closed).
190
+ #
191
+ def kill
192
+ ctl.write :kill
193
+ end
171
194
 
172
- ##
173
- # Terminates this client nicely (requests this window to be closed).
174
- #
175
- def kill
176
- ctl.write :kill
177
- end
195
+ ##
196
+ # Terminates this client forcefully.
197
+ #
198
+ def slay
199
+ ctl.write :slay
200
+ end
178
201
 
179
- ##
180
- # Terminates this client forcefully.
181
- #
182
- def slay
183
- ctl.write :slay
184
- end
202
+ ##
203
+ # Maximizes this client to occupy the
204
+ # entire screen on the current view.
205
+ #
206
+ def fullscreen
207
+ ctl.write 'Fullscreen on'
208
+ end
185
209
 
186
- ##
187
- # Maximizes this client to occupy the
188
- # entire screen on the current view.
189
- #
190
- def fullscreen
191
- ctl.write 'Fullscreen on'
192
- end
210
+ ##
211
+ # Restores this client back to its original size on the current view.
212
+ #
213
+ def unfullscreen
214
+ ctl.write 'Fullscreen off'
215
+ end
193
216
 
194
- ##
195
- # Restores this client back to its original size on the current view.
196
- #
197
- def unfullscreen
198
- ctl.write 'Fullscreen off'
199
- end
217
+ ##
218
+ # Toggles the fullscreen status of this client on the current view.
219
+ #
220
+ def fullscreen!
221
+ ctl.write 'Fullscreen toggle'
222
+ end
200
223
 
201
- ##
202
- # Toggles the fullscreen status of this client on the current view.
203
- #
204
- def fullscreen!
205
- ctl.write 'Fullscreen toggle'
206
- end
224
+ ##
225
+ # Checks if this client is currently fullscreen on the current view.
226
+ #
227
+ def fullscreen?
228
+ #
229
+ # If the client's dimensions match those of the
230
+ # floating area, then we know it is fullscreen.
231
+ #
232
+ View.curr.manifest =~ /^# #{FLOATING_AREA_ID} (\d+) (\d+)\n.*^#{FLOATING_AREA_ID} #{@id} \d+ \d+ \1 \2 /m
233
+ end
207
234
 
208
- ##
209
- # Checks if this client is currently fullscreen on the current view.
210
- #
211
- def fullscreen?
212
- #
213
- # If the client's dimensions match those of the
214
- # floating area, then we know it is fullscreen.
215
- #
216
- View.curr.manifest =~ /^# #{FLOATING_AREA_ID} (\d+) (\d+)\n.*^#{FLOATING_AREA_ID} #{@id} \d+ \d+ \1 \2 /m
217
- end
235
+ ##
236
+ # Checks if this client is sticky (appears in all views).
237
+ #
238
+ def stick?
239
+ tags.include? CLIENT_STICKY_TAG
240
+ end
218
241
 
219
- ##
220
- # Checks if this client is sticky (appears in all views).
221
- #
222
- def stick?
223
- tags.include? CLIENT_STICKY_TAG
224
- end
242
+ ##
243
+ # Makes this client sticky (appears in all views).
244
+ #
245
+ def stick
246
+ tag CLIENT_STICKY_TAG
247
+ end
225
248
 
226
- ##
227
- # Makes this client sticky (appears in all views).
228
- #
229
- def stick
230
- tag CLIENT_STICKY_TAG
231
- end
249
+ ##
250
+ # Makes this client unsticky (does not appear in all views).
251
+ #
252
+ def unstick
253
+ untag CLIENT_STICKY_TAG
254
+ end
232
255
 
233
- ##
234
- # Makes this client unsticky (does not appear in all views).
235
- #
236
- def unstick
237
- untag CLIENT_STICKY_TAG
238
- end
256
+ ##
257
+ # Toggles the stickyness of this client.
258
+ #
259
+ def stick!
260
+ if stick?
261
+ unstick
262
+ else
263
+ stick
264
+ end
265
+ end
239
266
 
240
- ##
241
- # Toggles the stickyness of this client.
242
- #
243
- def stick!
244
- if stick?
245
- unstick
246
- else
247
- stick
248
- end
249
- end
267
+ ##
268
+ # Checks if this client is in the floating area of the given view.
269
+ #
270
+ def float? view = View.curr
271
+ area(view).floating?
272
+ end
250
273
 
251
- ##
252
- # Checks if this client is in the floating area of the given view.
253
- #
254
- def float? view = View.curr
255
- area(view).floating?
256
- end
274
+ ##
275
+ # Puts this client into the floating area of the given view.
276
+ #
277
+ def float view = View.curr
278
+ send :toggle, view unless float? view
279
+ end
257
280
 
258
- ##
259
- # Puts this client into the floating area of the given view.
260
- #
261
- def float view = View.curr
262
- send :toggle, view unless float? view
263
- end
281
+ ##
282
+ # Puts this client into the managed area of the given view.
283
+ #
284
+ def unfloat view = View.curr
285
+ send :toggle, view if float? view
286
+ end
264
287
 
265
- ##
266
- # Puts this client into the managed area of the given view.
267
- #
268
- def unfloat view = View.curr
269
- send :toggle, view if float? view
270
- end
288
+ ##
289
+ # Toggles the floating status of this client in the given view.
290
+ #
291
+ def float! view = View.curr
292
+ send :toggle, view
293
+ end
271
294
 
272
- ##
273
- # Toggles the floating status of this client in the given view.
274
- #
275
- def float! view = View.curr
276
- send :toggle, view
277
- end
295
+ ##
296
+ # Checks if this client is in the managed area of the given view.
297
+ def manage? view = View.curr
298
+ not float? view
299
+ end
278
300
 
279
- ##
280
- # Checks if this client is in the managed area of the given view.
281
- def manage? view = View.curr
282
- not float? view
283
- end
301
+ alias manage unfloat
284
302
 
285
- alias manage unfloat
303
+ alias unmanage float
286
304
 
287
- alias unmanage float
305
+ alias manage! float!
288
306
 
289
- alias manage! float!
307
+ #-------------------------------------------------------------------------
308
+ # WM hierarchy
309
+ #-------------------------------------------------------------------------
290
310
 
291
- #-----------------------------------------------------------------------
292
- # WM hierarchy
293
- #-----------------------------------------------------------------------
311
+ ##
312
+ # Returns the area that contains this client within the given view.
313
+ #
314
+ def area view = View.curr
315
+ view.area_of_client self
316
+ end
294
317
 
295
- ##
296
- # Returns the area that contains this client within the given view.
297
- #
298
- def area view = View.curr
299
- view.area_of_client self
300
- end
318
+ ##
319
+ # Returns the views that contain this client.
320
+ #
321
+ def views
322
+ tags.map! {|t| View.new t }
323
+ end
301
324
 
302
- ##
303
- # Returns the views that contain this client.
304
- #
305
- def views
306
- tags.map! {|t| View.new t }
307
- end
325
+ #-------------------------------------------------------------------------
326
+ # tag manipulations
327
+ #-------------------------------------------------------------------------
308
328
 
309
- #-----------------------------------------------------------------------
310
- # tag manipulations
311
- #-----------------------------------------------------------------------
329
+ TAG_DELIMITER = '+'.freeze
312
330
 
313
- TAG_DELIMITER = '+'.freeze
331
+ ##
332
+ # Returns the tags associated with this client.
333
+ #
334
+ def tags
335
+ self[:tags].read.split TAG_DELIMITER
336
+ end
314
337
 
315
- ##
316
- # Returns the tags associated with this client.
317
- #
318
- def tags
319
- self[:tags].read.split TAG_DELIMITER
338
+ ##
339
+ # Modifies the tags associated with this client.
340
+ #
341
+ # If a tag name is '~', this client is placed
342
+ # into the floating layer of the current view.
343
+ #
344
+ # If a tag name begins with '~', then this
345
+ # client is placed into the floating layer
346
+ # of the view corresponding to that tag.
347
+ #
348
+ # If a tag name is '!', this client is placed
349
+ # into the managed layer of the current view.
350
+ #
351
+ # If a tag name begins with '!', then this
352
+ # client is placed into the managed layer
353
+ # of the view corresponding to that tag.
354
+ #
355
+ def tags= *tags
356
+ float = []
357
+ manage = []
358
+ inherit = []
359
+
360
+ tags.join(TAG_DELIMITER).split(TAG_DELIMITER).each do |tag|
361
+ case tag
362
+ when '~' then float << Rumai.curr_tag
363
+ when /^~/ then float << $'
364
+ when '!' then manage << Rumai.curr_tag
365
+ when /^!/ then manage << $'
366
+ else inherit << tag
320
367
  end
368
+ end
321
369
 
322
- ##
323
- # Modifies the tags associated with this client.
324
- #
325
- # If a tag name is '~', this client is placed
326
- # into the floating layer of the current view.
327
- #
328
- # If a tag name begins with '~', then this
329
- # client is placed into the floating layer
330
- # of the view corresponding to that tag.
331
- #
332
- # If a tag name is '!', this client is placed
333
- # into the managed layer of the current view.
334
- #
335
- # If a tag name begins with '!', then this
336
- # client is placed into the managed layer
337
- # of the view corresponding to that tag.
338
- #
339
- def tags= *tags
340
- float = []
341
- manage = []
342
- inherit = []
343
-
344
- tags.join(TAG_DELIMITER).split(TAG_DELIMITER).each do |tag|
345
- case tag
346
- when '~' then float << Rumai.curr_tag
347
- when /^~/ then float << $'
348
- when '!' then manage << Rumai.curr_tag
349
- when /^!/ then manage << $'
350
- else inherit << tag
351
- end
352
- end
370
+ self[:tags].write((float + manage + inherit).uniq.join(TAG_DELIMITER))
353
371
 
354
- self[:tags].write((float + manage + inherit).uniq.join(TAG_DELIMITER))
372
+ float.each do |tag|
373
+ self.float View.new(tag)
374
+ end
355
375
 
356
- float.each do |tag|
357
- self.float View.new(tag)
358
- end
376
+ manage.each do |tag|
377
+ self.manage View.new(tag)
378
+ end
379
+ end
359
380
 
360
- manage.each do |tag|
361
- self.manage View.new(tag)
362
- end
363
- end
381
+ ##
382
+ # Evaluates the given block within the
383
+ # context of this client's list of tags.
384
+ #
385
+ def with_tags &block
386
+ arr = self.tags
387
+ arr.instance_eval(&block)
388
+ self.tags = arr
389
+ end
364
390
 
365
- ##
366
- # Evaluates the given block within the
367
- # context of this client's list of tags.
368
- #
369
- def with_tags &block
370
- arr = self.tags
371
- arr.instance_eval(&block)
372
- self.tags = arr
373
- end
391
+ ##
392
+ # Adds the given tags to this client.
393
+ #
394
+ def tag *tags
395
+ with_tags do
396
+ concat tags
397
+ end
398
+ end
374
399
 
375
- ##
376
- # Adds the given tags to this client.
377
- #
378
- def tag *tags
379
- with_tags do
380
- concat tags
381
- end
400
+ ##
401
+ # Removes the given tags from this client.
402
+ #
403
+ def untag *tags
404
+ with_tags do
405
+ tags.flatten.each do |tag|
406
+ delete tag.to_s
382
407
  end
408
+ end
409
+ end
383
410
 
384
- ##
385
- # Removes the given tags from this client.
386
- #
387
- def untag *tags
388
- with_tags do
389
- tags.flatten.each do |tag|
390
- delete tag.to_s
391
- end
392
- end
393
- end
411
+ #-------------------------------------------------------------------------
412
+ # multiple client grouping
413
+ #-------------------------------------------------------------------------
394
414
 
395
- #-----------------------------------------------------------------------
396
- # multiple client grouping
397
- #-----------------------------------------------------------------------
415
+ ##
416
+ # Checks if this client is included in the current grouping.
417
+ #
418
+ def group?
419
+ tags.include? CLIENT_GROUPING_TAG
420
+ end
398
421
 
399
- ##
400
- # Checks if this client is included in the current grouping.
401
- #
402
- def group?
403
- tags.include? CLIENT_GROUPING_TAG
404
- end
422
+ ##
423
+ # Adds this client to the current grouping.
424
+ #
425
+ def group
426
+ with_tags do
427
+ push CLIENT_GROUPING_TAG
428
+ end
429
+ end
405
430
 
406
- ##
407
- # Adds this client to the current grouping.
408
- #
409
- def group
410
- with_tags do
411
- push CLIENT_GROUPING_TAG
412
- end
413
- end
431
+ ##
432
+ # Removes this client to the current grouping.
433
+ #
434
+ def ungroup
435
+ untag CLIENT_GROUPING_TAG
436
+ end
414
437
 
415
- ##
416
- # Removes this client to the current grouping.
417
- #
418
- def ungroup
419
- untag CLIENT_GROUPING_TAG
420
- end
438
+ ##
439
+ # Toggles the presence of this client in the current grouping.
440
+ #
441
+ def group!
442
+ if group?
443
+ ungroup
444
+ else
445
+ group
446
+ end
447
+ end
421
448
 
422
- ##
423
- # Toggles the presence of this client in the current grouping.
424
- #
425
- def group!
426
- if group?
427
- ungroup
428
- else
429
- group
430
- end
431
- end
449
+ private
432
450
 
433
- private
451
+ def reshape method, direction, view
452
+ area = self.area(view)
453
+ index = area.client_ids.index(@id) + 1 # numbered as 1..N
454
+ view.ctl.write "#{method} #{area.id} #{index} #{direction}"
455
+ end
434
456
 
435
- ##
436
- # Returns the wmii ID of the given area.
437
- #
438
- def area_to_id area_or_id
439
- if area_or_id.respond_to? :id
440
- id = area_or_id.id
441
- id == FLOATING_AREA_ID ? :toggle : id
442
- else
443
- area_or_id
444
- end
457
+ ##
458
+ # Returns the wmii ID of the given area.
459
+ #
460
+ def area_to_id area_or_id
461
+ if area_or_id.respond_to? :id
462
+ id = area_or_id.id
463
+ id == FLOATING_AREA_ID ? :toggle : id
464
+ else
465
+ area_or_id
445
466
  end
446
467
  end
468
+ end
447
469
 
470
+ ##
471
+ # @note Inheritors should override the {client_ids} method.
472
+ #
473
+ module ClientContainer
448
474
  ##
449
- # @note Inheritors should override the {ClientContainer#client_ids} method.
475
+ # Returns the IDs of the clients in this container.
450
476
  #
451
- module ClientContainer
452
- ##
453
- # Returns the IDs of the clients in this container.
454
- #
455
- def client_ids
456
- []
457
- end
477
+ def client_ids
478
+ []
479
+ end
458
480
 
459
- ##
460
- # Returns the clients contained in this container.
461
- #
462
- def clients
463
- client_ids.map! {|i| Client.new i }
464
- end
481
+ ##
482
+ # Returns the clients contained in this container.
483
+ #
484
+ def clients
485
+ client_ids.map! {|i| Client.new i }
486
+ end
465
487
 
466
- # multiple client grouping
467
- %w[group ungroup group!].each do |meth|
468
- define_method meth do
469
- clients.each do |c|
470
- c.__send__ meth
471
- end
488
+ # multiple client grouping
489
+ %w[group ungroup group!].each do |meth|
490
+ define_method meth do
491
+ clients.each do |c|
492
+ c.__send__ meth
472
493
  end
473
494
  end
495
+ end
474
496
 
475
- ##
476
- # Returns all grouped clients in this container.
477
- #
478
- def grouping
479
- clients.select {|c| c.group? }
480
- end
497
+ ##
498
+ # Returns all grouped clients in this container.
499
+ #
500
+ def grouping
501
+ clients.select {|c| c.group? }
481
502
  end
503
+ end
504
+
505
+ ##
506
+ # A region that contains clients. This can be either
507
+ # the floating area or a column in the managed area.
508
+ #
509
+ class Area
510
+ attr_reader :view
482
511
 
483
512
  ##
484
- # A region that contains clients. This can be either
485
- # the floating area or a column in the managed area.
513
+ # @param [Rumai::View] view
514
+ # the view object which contains this area
486
515
  #
487
- class Area
488
- attr_reader :view
516
+ def initialize area_id, view = View.curr
517
+ @id = Integer(area_id) rescue area_id
518
+ @view = view
519
+ end
489
520
 
490
- ##
491
- # @param [Rumai::View] view
492
- # the view object which contains this area
493
- #
494
- def initialize area_id, view = View.curr
495
- @id = Integer(area_id) rescue area_id
496
- @view = view
497
- end
521
+ ##
522
+ # Checks if this area is the floating area.
523
+ #
524
+ def floating?
525
+ @id == FLOATING_AREA_ID
526
+ end
498
527
 
499
- ##
500
- # Checks if this area is the floating area.
501
- #
502
- def floating?
503
- @id == FLOATING_AREA_ID
504
- end
528
+ ##
529
+ # Checks if this is a managed area (a column).
530
+ #
531
+ def column?
532
+ not floating?
533
+ end
505
534
 
506
- ##
507
- # Checks if this is a managed area (a column).
508
- #
509
- def column?
510
- not floating?
511
- end
535
+ alias managed? column?
512
536
 
513
- alias managed? column?
537
+ #-------------------------------------------------------------------------
538
+ include WidgetImpl
539
+ #-------------------------------------------------------------------------
514
540
 
515
- include WidgetImpl
541
+ ##
542
+ # Returns the currently focused area.
543
+ #
544
+ def self.curr
545
+ View.curr.area_of_client Client.curr
546
+ end
516
547
 
517
- ##
518
- # Returns the currently focused area.
519
- #
520
- def self.curr
521
- View.curr.area_of_client Client.curr
522
- end
548
+ ##
549
+ # Returns the floating area in the given view.
550
+ #
551
+ def self.floating view = View.curr
552
+ new FLOATING_AREA_ID, view
553
+ end
523
554
 
524
- ##
525
- # Returns the floating area in the given view.
526
- #
527
- def self.floating view = View.curr
528
- new FLOATING_AREA_ID, view
529
- end
555
+ #-------------------------------------------------------------------------
556
+ include Chain
557
+ #-------------------------------------------------------------------------
530
558
 
531
- include Chain
559
+ ##
560
+ # Returns a list of all areas in the current view.
561
+ #
562
+ def chain
563
+ @view.areas
564
+ end
532
565
 
533
- ##
534
- # Returns a list of all areas in the current view.
535
- #
536
- def chain
537
- @view.areas
538
- end
566
+ ##
567
+ # Checks if this object exists in the chain.
568
+ #
569
+ def exist?
570
+ chain.include? self
571
+ end
539
572
 
540
- ##
541
- # Checks if this object exists in the chain.
542
- #
543
- def exist?
544
- chain.include? self
545
- end
573
+ #-------------------------------------------------------------------------
574
+ include ClientContainer
575
+ #-------------------------------------------------------------------------
546
576
 
547
- include ClientContainer
577
+ ##
578
+ # Returns the IDs of the clients in this area.
579
+ #
580
+ def client_ids
581
+ @view.client_ids @id
582
+ end
548
583
 
549
- ##
550
- # Returns the IDs of the clients in this area.
551
- #
552
- def client_ids
553
- @view.client_ids @id
554
- end
584
+ #-------------------------------------------------------------------------
585
+ include Enumerable
586
+ #-------------------------------------------------------------------------
555
587
 
556
- include Enumerable
588
+ ##
589
+ # Iterates through each client in this container.
590
+ #
591
+ def each &block
592
+ clients.each(&block)
593
+ end
557
594
 
558
- ##
559
- # Iterates through each client in this container.
560
- #
561
- def each &block
562
- clients.each(&block)
563
- end
595
+ #-------------------------------------------------------------------------
596
+ # WM operations
597
+ #-------------------------------------------------------------------------
564
598
 
565
- ##
566
- # Sets the layout of clients in this column.
567
- #
568
- def layout= mode
569
- case mode
570
- when :stack then mode = 'stack-max'
571
- when :max then mode = 'stack+max'
572
- end
599
+ ##
600
+ # Puts focus on this area.
601
+ #
602
+ def focus
603
+ @view.ctl.write "select #{@id}"
604
+ end
573
605
 
574
- @view.ctl.write "colmode #{@id} #{mode}"
606
+ ##
607
+ # Sets the layout of clients in this column.
608
+ #
609
+ def layout= mode
610
+ case mode
611
+ when :stack then mode = 'stack-max'
612
+ when :max then mode = 'stack+max'
575
613
  end
576
614
 
577
- #-----------------------------------------------------------------------
578
- # WM operations
579
- #-----------------------------------------------------------------------
615
+ @view.ctl.write "colmode #{@id} #{mode}"
616
+ end
580
617
 
581
- ##
582
- # Puts focus on this area.
583
- #
584
- def focus
585
- @view.ctl.write "select #{@id}"
586
- end
618
+ #-------------------------------------------------------------------------
619
+ # array abstraction: area is an array of clients
620
+ #-------------------------------------------------------------------------
621
+
622
+ ##
623
+ # Returns the number of clients in this area.
624
+ #
625
+ def length
626
+ client_ids.length
627
+ end
587
628
 
588
- #-----------------------------------------------------------------------
589
- # array abstraction: area is an array of clients
590
- #-----------------------------------------------------------------------
629
+ ##
630
+ # Inserts the given clients at the bottom of this area.
631
+ #
632
+ def push *clients
633
+ clients.flatten!
634
+ return if clients.empty?
591
635
 
592
- ##
593
- # Returns the number of clients in this area.
594
- #
595
- def length
596
- client_ids.length
636
+ insert clients
637
+
638
+ # move inserted clients to bottom
639
+ clients.reverse.each_with_index do |c, i|
640
+ until c.id == self.client_ids[-i.succ]
641
+ c.send :down
597
642
  end
643
+ end
644
+ end
598
645
 
599
- ##
600
- # Inserts the given clients at the bottom of this area.
601
- #
602
- def push *clients
603
- clients.flatten!
604
- return if clients.empty?
646
+ alias << push
605
647
 
606
- insert clients
648
+ ##
649
+ # Inserts the given clients after the
650
+ # currently focused client in this area.
651
+ #
652
+ def insert *clients
653
+ clients.flatten!
654
+ return if clients.empty?
607
655
 
608
- # move inserted clients to bottom
609
- clients.reverse.each_with_index do |c, i|
610
- until c.id == self.client_ids[-i.succ]
611
- c.send :down
612
- end
613
- end
614
- end
656
+ clients.each do |c|
657
+ import_client c
658
+ end
659
+ end
615
660
 
616
- alias << push
661
+ ##
662
+ # Inserts the given clients at the top of this area.
663
+ #
664
+ def unshift *clients
665
+ clients.flatten!
666
+ return if clients.empty?
617
667
 
618
- ##
619
- # Inserts the given clients after the
620
- # currently focused client in this area.
621
- #
622
- def insert *clients
623
- clients.flatten!
624
- return if clients.empty?
668
+ insert clients
625
669
 
626
- clients.each do |c|
627
- import_client c
628
- end
670
+ # move inserted clients to top
671
+ clients.each_with_index do |c, i|
672
+ until c.id == self.client_ids[i]
673
+ c.send :up
629
674
  end
675
+ end
676
+ end
630
677
 
631
- ##
632
- # Inserts the given clients at the top of this area.
633
- #
634
- def unshift *clients
635
- clients.flatten!
636
- return if clients.empty?
678
+ ##
679
+ # Concatenates the given area to the bottom of this area.
680
+ #
681
+ def concat area
682
+ push area.clients
683
+ end
637
684
 
638
- insert clients
685
+ ##
686
+ # Ensures that this area has at most the given number of clients.
687
+ #
688
+ # Areas to the right of this one serve as a buffer into which excess
689
+ # clients are evicted and from which deficit clients are imported.
690
+ #
691
+ def length= max_clients
692
+ return unless max_clients > 0
693
+ len, out = length, fringe
639
694
 
640
- # move inserted clients to top
641
- clients.each_with_index do |c, i|
642
- until c.id == self.client_ids[i]
643
- c.send :up
644
- end
645
- end
646
- end
695
+ if len > max_clients
696
+ out.unshift clients[max_clients..-1]
647
697
 
648
- ##
649
- # Concatenates the given area to the bottom of this area.
650
- #
651
- def concat area
652
- push area.clients
653
- end
698
+ elsif len < max_clients
699
+ until (diff = max_clients - length) == 0
700
+ importable = out.clients[0, diff]
701
+ break if importable.empty?
654
702
 
655
- ##
656
- # Ensures that this area has at most the given number of clients.
657
- #
658
- # Areas to the right of this one serve as a buffer into which excess
659
- # clients are evicted and from which deficit clients are imported.
660
- #
661
- def length= max_clients
662
- return unless max_clients > 0
663
- len, out = length, fringe
664
-
665
- if len > max_clients
666
- out.unshift clients[max_clients..-1]
667
-
668
- elsif len < max_clients
669
- until (diff = max_clients - length) == 0
670
- importable = out.clients[0, diff]
671
- break if importable.empty?
672
-
673
- push importable
674
- end
675
- end
703
+ push importable
676
704
  end
705
+ end
706
+ end
677
707
 
678
- private
708
+ private
679
709
 
680
- ##
681
- # Moves the given client into this area.
682
- #
683
- def import_client c
684
- if exist?
685
- c.send self
686
-
687
- else
688
- # move the client to the nearest existing column
689
- src = c.area
690
- dst = chain.last
710
+ ##
711
+ # Moves the given client into this area.
712
+ #
713
+ def import_client c
714
+ if exist?
715
+ c.send self
691
716
 
692
- c.send dst unless src == dst
717
+ else
718
+ # move the client to the nearest existing column
719
+ src = c.area
720
+ dst = chain.last
693
721
 
694
- # slide the client over to this column
695
- c.send :right
696
- @id = dst.id.next
722
+ c.send dst unless src == dst
697
723
 
698
- raise 'column should exist now' unless exist?
699
- end
700
- end
724
+ # slide the client over to this column
725
+ c.send :right
726
+ @id = dst.id.next
701
727
 
702
- ##
703
- # Returns the next area, which may or may not exist.
704
- #
705
- def fringe
706
- Area.new @id.next, @view
728
+ raise 'column should exist now' unless exist?
707
729
  end
708
730
  end
709
731
 
710
732
  ##
711
- # The visualization of a tag.
733
+ # Returns the next area, which may or may not exist.
712
734
  #
713
- class View < WidgetNode
714
- include WidgetImpl
735
+ def fringe
736
+ Area.new @id.next, @view
737
+ end
738
+ end
739
+
740
+ ##
741
+ # The visualization of a tag.
742
+ #
743
+ class View < WidgetNode
744
+ def initialize view_id
745
+ super view_id, '/tag'
746
+ end
715
747
 
716
- ##
717
- # Returns the currently focused view.
718
- #
719
- def self.curr
720
- new FOCUSED_WIDGET_ID
721
- end
748
+ #-------------------------------------------------------------------------
749
+ include WidgetImpl
750
+ #-------------------------------------------------------------------------
722
751
 
723
- ##
724
- # Focuses this view.
725
- #
726
- def focus
727
- IXP_FS_ROOT.ctl.write "view #{@id}"
728
- end
752
+ ##
753
+ # Returns the currently focused view.
754
+ #
755
+ def self.curr
756
+ new FOCUSED_WIDGET_ID
757
+ end
729
758
 
730
- include Chain
759
+ ##
760
+ # Focuses this view.
761
+ #
762
+ def focus
763
+ IXP_FS_ROOT.ctl.write "view #{@id}"
764
+ end
731
765
 
732
- ##
733
- # Returns a list of all views.
734
- #
735
- def chain
736
- Rumai.views
737
- end
766
+ #-------------------------------------------------------------------------
767
+ include Chain
768
+ #-------------------------------------------------------------------------
738
769
 
739
- include ClientContainer
770
+ ##
771
+ # Returns a list of all views.
772
+ #
773
+ def chain
774
+ Rumai.views
775
+ end
740
776
 
741
- ##
742
- # Returns the IDs of the clients contained
743
- # in the given area within this view.
744
- #
745
- def client_ids area_id = '\S+'
746
- manifest.scan(/^#{area_id} (0x\S+)/).flatten
747
- end
777
+ #-------------------------------------------------------------------------
778
+ include ClientContainer
779
+ #-------------------------------------------------------------------------
748
780
 
749
- include Enumerable
781
+ ##
782
+ # Returns the IDs of the clients contained
783
+ # in the given area within this view.
784
+ #
785
+ def client_ids area_id = '\S+'
786
+ manifest.scan(/^#{area_id} (0x\S+)/).flatten
787
+ end
750
788
 
751
- ##
752
- # Iterates through each area in this view.
753
- #
754
- def each &block
755
- areas.each(&block)
756
- end
789
+ #-------------------------------------------------------------------------
790
+ include Enumerable
791
+ #-------------------------------------------------------------------------
757
792
 
758
- def initialize view_id
759
- super view_id, '/tag'
760
- end
793
+ ##
794
+ # Iterates through each area in this view.
795
+ #
796
+ def each &block
797
+ areas.each(&block)
798
+ end
761
799
 
762
- #-----------------------------------------------------------------------
763
- # WM operations
764
- #-----------------------------------------------------------------------
800
+ #-----------------------------------------------------------------------
801
+ # WM operations
802
+ #-----------------------------------------------------------------------
765
803
 
766
- ##
767
- # Returns the manifest of all areas and clients in this view.
768
- #
769
- def manifest
770
- index.read || ''
771
- end
804
+ ##
805
+ # Returns the manifest of all areas and clients in this view.
806
+ #
807
+ def manifest
808
+ index.read || ''
809
+ end
772
810
 
773
- ##
774
- # Moves the focus from the current client in the given direction.
775
- #
776
- def select direction
777
- ctl.write "select #{direction}"
778
- end
811
+ ##
812
+ # Moves the focus from the current client in the given direction.
813
+ #
814
+ def select direction
815
+ ctl.write "select #{direction}"
816
+ end
779
817
 
780
- #-----------------------------------------------------------------------
781
- # WM hierarchy
782
- #-----------------------------------------------------------------------
783
-
784
- ##
785
- # Returns the area which contains the given client in this view.
786
- #
787
- def area_of_client client_or_id
788
- arg =
789
- if client_or_id.respond_to? :id
790
- client_or_id.id
791
- else
792
- client_or_id
793
- end
794
-
795
- manifest =~ /^(\S+) #{arg}/
796
- if area_id = $1
797
- Area.new area_id, self
798
- end
799
- end
818
+ #-----------------------------------------------------------------------
819
+ # WM hierarchy
820
+ #-----------------------------------------------------------------------
800
821
 
801
- ##
802
- # Returns the IDs of all areas in this view.
803
- #
804
- def area_ids
805
- manifest.scan(/^# (\d+)/).flatten.unshift(FLOATING_AREA_ID)
822
+ ##
823
+ # Returns the area which contains the given client in this view.
824
+ #
825
+ def area_of_client client_or_id
826
+ arg =
827
+ if client_or_id.respond_to? :id
828
+ client_or_id.id
829
+ else
830
+ client_or_id
806
831
  end
807
832
 
808
- ##
809
- # Returns all areas in this view.
810
- #
811
- def areas
812
- area_ids.map! {|i| Area.new i, self }
813
- end
833
+ manifest =~ /^(\S+) #{arg}/
834
+ if area_id = $1
835
+ Area.new area_id, self
836
+ end
837
+ end
814
838
 
815
- ##
816
- # Returns the floating area of this view.
817
- #
818
- def floating_area
819
- Area.floating self
820
- end
839
+ ##
840
+ # Returns the IDs of all areas in this view.
841
+ #
842
+ def area_ids
843
+ manifest.scan(/^# (\d+)/).flatten.unshift(FLOATING_AREA_ID)
844
+ end
821
845
 
822
- ##
823
- # Returns all columns (managed areas) in this view.
824
- #
825
- def columns
826
- areas[1..-1]
827
- end
846
+ ##
847
+ # Returns all areas in this view.
848
+ #
849
+ def areas
850
+ area_ids.map! {|i| Area.new i, self }
851
+ end
828
852
 
829
- alias managed_areas columns
830
-
831
- ##
832
- # Resiliently iterates through possibly destructive changes to
833
- # each column. That is, if the given block creates new
834
- # columns, then those will also be processed in the iteration.
835
- #
836
- def each_column starting_column_id = 1
837
- i = starting_column_id
838
- loop do
839
- a = Area.new i, self
840
-
841
- if a.exist?
842
- yield a
843
- else
844
- break
845
- end
846
-
847
- i += 1
848
- end
849
- end
853
+ ##
854
+ # Returns the floating area of this view.
855
+ #
856
+ def floating_area
857
+ Area.floating self
858
+ end
850
859
 
851
- alias each_managed_area each_column
852
-
853
- #-----------------------------------------------------------------------
854
- # visual arrangement of clients
855
- #-----------------------------------------------------------------------
856
-
857
- ##
858
- # Arranges the clients in this view, while maintaining
859
- # their relative order, in the tiling fashion of LarsWM.
860
- #
861
- # Only the first client in the primary column is kept; all others
862
- # are evicted to the *top* of the secondary column. Any subsequent
863
- # columns are squeezed into the *bottom* of the secondary column.
864
- #
865
- def arrange_as_larswm
866
- maintain_focus do
867
- # keep only one client in the primary column
868
- main = Area.new(1, self)
869
- main.length = 1
870
- main.layout = :default
871
-
872
- # collapse remaining areas into secondary column
873
- extra = squeeze_columns(1..-1)
874
-
875
- if dock = extra.first
876
- dock.layout = :default
877
- end
878
- end
879
- end
860
+ ##
861
+ # Returns all columns (managed areas) in this view.
862
+ #
863
+ def columns
864
+ areas[1..-1]
865
+ end
880
866
 
881
- ##
882
- # Arranges the clients in this view, while maintaining
883
- # their relative order, in a (at best) square grid.
884
- #
885
- def arrange_in_grid max_clients_per_column = nil
886
- # compute client distribution
887
- unless max_clients_per_column
888
- num_clients = num_managed_clients
889
- return unless num_clients > 0
890
-
891
- num_columns = Math.sqrt(num_clients)
892
- max_clients_per_column = (num_clients / num_columns).round
893
- end
867
+ alias managed_areas columns
894
868
 
895
- return if max_clients_per_column < 1
869
+ ##
870
+ # Resiliently iterates through possibly destructive changes to
871
+ # each column. That is, if the given block creates new
872
+ # columns, then those will also be processed in the iteration.
873
+ #
874
+ def each_column starting_column_id = 1
875
+ i = starting_column_id
876
+ loop do
877
+ a = Area.new i, self
896
878
 
897
- # apply the distribution
898
- maintain_focus do
899
- each_column do |a|
900
- a.length = max_clients_per_column
901
- a.layout = :default
902
- end
903
- end
879
+ if a.exist?
880
+ yield a
881
+ else
882
+ break
904
883
  end
905
884
 
906
- ##
907
- # Arranges the clients in this view, while maintaining
908
- # their relative order, in the given number of columns.
909
- #
910
- def arrange_in_stacks num_stacks
911
- return if num_stacks < 1
912
-
913
- # compute client distribution
914
- num_clients = num_managed_clients
915
- return unless num_clients > 0
885
+ i += 1
886
+ end
887
+ end
916
888
 
917
- stack_length = num_clients / num_stacks
918
- return if stack_length < 1
889
+ alias each_managed_area each_column
919
890
 
920
- # apply the distribution
921
- maintain_focus do
922
- each_column do |a|
923
- a.length = stack_length
924
- a.layout = :stack
925
- end
891
+ #-------------------------------------------------------------------------
892
+ # visual arrangement of clients
893
+ #-----------------------------------------------------------------------
926
894
 
927
- squeeze_columns num_stacks-1..-1
928
- end
929
- end
895
+ ##
896
+ # Arranges the clients in this view, while maintaining
897
+ # their relative order, in the tiling fashion of LarsWM.
898
+ #
899
+ # Only the first client in the primary column is kept; all others
900
+ # are evicted to the *top* of the secondary column. Any subsequent
901
+ # columns are squeezed into the *bottom* of the secondary column.
902
+ #
903
+ def arrange_as_larswm
904
+ maintain_focus do
905
+ # keep only one client in the primary column
906
+ main = Area.new(1, self)
907
+ main.length = 1
908
+ main.layout = :default
930
909
 
931
- ##
932
- # Arranges the clients in this view, while
933
- # maintaining their relative order, in a (at
934
- # best) equilateral triangle. However, the
935
- # resulting arrangement appears like a diamond
936
- # because wmii does not waste screen space.
937
- #
938
- def arrange_in_diamond
939
- num_clients = num_managed_clients
940
- return unless num_clients > 1
941
-
942
- # determine dimensions of the rising sub-triangle
943
- rise = num_clients / 2
944
-
945
- span = sum = 0
946
- 1.upto rise do |h|
947
- if sum + h > rise
948
- break
949
- else
950
- sum += h
951
- span += 1
952
- end
953
- end
910
+ # collapse remaining areas into secondary column
911
+ extra = squeeze_columns(1..-1)
954
912
 
955
- peak = num_clients - (sum * 2)
913
+ if dock = extra.first
914
+ dock.layout = :default
915
+ end
916
+ end
917
+ end
956
918
 
957
- # describe the overall triangle as a sequence of heights
958
- rise_seq = (1..span).to_a
959
- fall_seq = rise_seq.reverse
919
+ ##
920
+ # Arranges the clients in this view, while maintaining
921
+ # their relative order, in a (at best) square grid.
922
+ #
923
+ def arrange_in_grid max_clients_per_column = nil
924
+ # compute client distribution
925
+ unless max_clients_per_column
926
+ num_clients = num_managed_clients
927
+ return unless num_clients > 0
928
+
929
+ num_columns = Math.sqrt(num_clients)
930
+ max_clients_per_column = (num_clients / num_columns).round
931
+ end
960
932
 
961
- heights = rise_seq
962
- heights << peak if peak > 0
963
- heights.concat fall_seq
933
+ return if max_clients_per_column < 1
964
934
 
965
- # apply the heights
966
- maintain_focus do
967
- each_column do |col|
968
- if h = heights.shift
969
- col.length = h
970
- col.layout = :default
971
- end
972
- end
973
- end
935
+ # apply the distribution
936
+ maintain_focus do
937
+ each_column do |a|
938
+ a.length = max_clients_per_column
939
+ a.layout = :default
974
940
  end
941
+ end
942
+ end
975
943
 
976
- private
944
+ ##
945
+ # Arranges the clients in this view, while maintaining
946
+ # their relative order, in the given number of columns.
947
+ #
948
+ def arrange_in_stacks num_stacks
949
+ return if num_stacks < 1
977
950
 
978
- ##
979
- # Squeezes all columns in the given index range into a single one.
980
- #
981
- def squeeze_columns range
982
- extra = columns[range]
951
+ # compute client distribution
952
+ num_clients = num_managed_clients
953
+ return unless num_clients > 0
983
954
 
984
- if extra.length > 1
985
- extra.reverse.each_cons(2) do |src, dst|
986
- dst.concat src
987
- end
955
+ stack_length = num_clients / num_stacks
956
+ return if stack_length < 1
957
+
958
+ # apply the distribution
959
+ maintain_focus do
960
+ each_column do |a|
961
+ a.length = stack_length
962
+ a.layout = :stack
988
963
  end
989
964
 
990
- extra
965
+ squeeze_columns num_stacks-1..-1
991
966
  end
967
+ end
992
968
 
993
- ##
994
- # Executes the given block and restores
995
- # focus to the client that had focus
996
- # before the given block was executed.
997
- #
998
- def maintain_focus
999
- c, v = Client.curr, View.curr
1000
- yield
1001
- c.focus v
969
+ ##
970
+ # Arranges the clients in this view, while
971
+ # maintaining their relative order, in a (at
972
+ # best) equilateral triangle. However, the
973
+ # resulting arrangement appears like a diamond
974
+ # because wmii does not waste screen space.
975
+ #
976
+ def arrange_in_diamond
977
+ num_clients = num_managed_clients
978
+ return unless num_clients > 1
979
+
980
+ # determine dimensions of the rising sub-triangle
981
+ rise = num_clients / 2
982
+
983
+ span = sum = 0
984
+ 1.upto rise do |h|
985
+ if sum + h > rise
986
+ break
987
+ else
988
+ sum += h
989
+ span += 1
990
+ end
1002
991
  end
1003
992
 
1004
- ##
1005
- # Returns the number of clients in the non-floating areas of this view.
1006
- #
1007
- def num_managed_clients
1008
- manifest.scan(/^\d+ 0x/).length
993
+ peak = num_clients - (sum * 2)
994
+
995
+ # quantify overall triangle as a sequence of heights
996
+ rise_seq = (1..span).to_a
997
+ fall_seq = rise_seq.reverse
998
+
999
+ heights = rise_seq
1000
+ heights << peak if peak > 0
1001
+ heights.concat fall_seq
1002
+
1003
+ # apply the heights
1004
+ maintain_focus do
1005
+ each_column do |col|
1006
+ if h = heights.shift
1007
+ col.length = h
1008
+ col.layout = :default
1009
+ end
1010
+ end
1009
1011
  end
1010
1012
  end
1011
1013
 
1012
- #---------------------------------------------------------------------------
1013
- # access to global WM state
1014
- #---------------------------------------------------------------------------
1014
+ private
1015
1015
 
1016
1016
  ##
1017
- # Returns the root of IXP file system hierarchy.
1017
+ # Squeezes all columns in the given index range into a single one.
1018
1018
  #
1019
- def fs
1020
- IXP_FS_ROOT
1019
+ def squeeze_columns range
1020
+ extra = columns[range]
1021
+
1022
+ if extra.length > 1
1023
+ extra.reverse.each_cons(2) do |src, dst|
1024
+ dst.concat src
1025
+ end
1026
+ end
1027
+
1028
+ extra
1021
1029
  end
1022
1030
 
1023
1031
  ##
1024
- # Returns the current set of tags.
1032
+ # Executes the given block and restores
1033
+ # focus to the client that had focus
1034
+ # before the given block was executed.
1025
1035
  #
1026
- def tags
1027
- ary = IXP_FS_ROOT.tag.entries.sort
1028
- ary.delete FOCUSED_WIDGET_ID
1029
- ary
1036
+ def maintain_focus
1037
+ c, v = Client.curr, View.curr
1038
+ yield
1039
+ c.focus v
1030
1040
  end
1031
1041
 
1032
1042
  ##
1033
- # Returns the current set of views.
1043
+ # Returns the number of clients in the non-floating areas of this view.
1034
1044
  #
1035
- def views
1036
- tags.map! {|t| View.new t }
1045
+ def num_managed_clients
1046
+ manifest.scan(/^\d+ 0x/).length
1037
1047
  end
1048
+ end
1038
1049
 
1039
- include ClientContainer
1050
+ ##
1051
+ # Subdivision of the bar---the thing that spans the width of the
1052
+ # screen---useful for displaying information and system controls.
1053
+ #
1054
+ class Barlet < Node
1055
+ attr_reader :side
1040
1056
 
1041
- ##
1042
- # Returns the IDs of the current set of clients.
1043
- #
1044
- def client_ids
1045
- ary = IXP_FS_ROOT.client.entries
1046
- ary.delete FOCUSED_WIDGET_ID
1047
- ary
1057
+ def initialize file_name, side
1058
+ prefix =
1059
+ case @side = side
1060
+ when :left then '/lbar'
1061
+ when :right then '/rbar'
1062
+ else raise ArgumentError, side
1063
+ end
1064
+
1065
+ super "#{prefix}/#{file_name}"
1066
+ end
1067
+
1068
+ COLORS_REGEXP = /^\S+ \S+ \S+/
1069
+
1070
+ def label
1071
+ case read
1072
+ when /^label (.*)$/ then $1
1073
+ when /#{COLORS_REGEXP} (.*)$/o then $1
1048
1074
  end
1075
+ end
1049
1076
 
1050
- ##
1051
- # Returns a list of all grouped clients in
1052
- # the currently focused view. If there are
1053
- # no grouped clients, then the currently
1054
- # focused client is returned in the list.
1055
- #
1056
- def grouping
1057
- list = curr_view.clients.select {|c| c.group? }
1058
- list << curr_client if list.empty? and curr_client.exist?
1059
- list
1077
+ def colors
1078
+ case read
1079
+ when /^colors (.*)$/ then $1
1080
+ when COLORS_REGEXP then $&
1081
+ end
1082
+ end
1083
+
1084
+ # detect the new bar file format introduced in wmii-hg2743
1085
+ temp_barlet = IXP_FS_ROOT.rbar["temp_barlet_#{object_id}"]
1086
+ begin
1087
+ temp_barlet.create
1088
+ SPLIT_FILE_FORMAT = temp_barlet.read =~ /\Acolors/
1089
+ ensure
1090
+ temp_barlet.remove
1091
+ end
1092
+
1093
+ def label= label
1094
+ if SPLIT_FILE_FORMAT
1095
+ write "label #{label}"
1096
+ else
1097
+ write "#{colors} #{label}"
1098
+ end
1099
+ end
1100
+
1101
+ def colors= colors
1102
+ if SPLIT_FILE_FORMAT
1103
+ write "colors #{colors}"
1104
+ else
1105
+ write "#{colors} #{label}"
1106
+ end
1060
1107
  end
1108
+ end
1109
+
1110
+ #---------------------------------------------------------------------------
1111
+ # access to global WM state
1112
+ #---------------------------------------------------------------------------
1113
+
1114
+ ##
1115
+ # Returns the root of IXP file system hierarchy.
1116
+ #
1117
+ def fs
1118
+ IXP_FS_ROOT
1119
+ end
1120
+
1121
+ ##
1122
+ # Returns the current set of tags.
1123
+ #
1124
+ def tags
1125
+ ary = IXP_FS_ROOT.tag.entries.sort
1126
+ ary.delete FOCUSED_WIDGET_ID
1127
+ ary
1128
+ end
1129
+
1130
+ ##
1131
+ # Returns the current set of views.
1132
+ #
1133
+ def views
1134
+ tags.map! {|t| View.new t }
1135
+ end
1136
+
1137
+ ##
1138
+ # Returns a list of all grouped clients in
1139
+ # the currently focused view. If there are
1140
+ # no grouped clients, then the currently
1141
+ # focused client is returned in the list.
1142
+ #
1143
+ def grouping
1144
+ list = curr_view.clients.select {|c| c.group? }
1145
+ list << curr_client if list.empty? and curr_client.exist?
1146
+ list
1147
+ end
1148
+
1149
+ #---------------------------------------------------------------------------
1150
+ include ClientContainer
1151
+ #---------------------------------------------------------------------------
1152
+
1153
+ ##
1154
+ # Returns the IDs of the current set of clients.
1155
+ #
1156
+ def client_ids
1157
+ ary = IXP_FS_ROOT.client.entries
1158
+ ary.delete FOCUSED_WIDGET_ID
1159
+ ary
1160
+ end
1061
1161
 
1062
1162
  #---------------------------------------------------------------------------
1063
1163
  # shortcuts for interactive WM manipulation (via IRB)
1064
1164
  #---------------------------------------------------------------------------
1065
1165
 
1066
- def curr_client ; Client.curr ; end
1067
- def next_client ; curr_client.next ; end
1068
- def prev_client ; curr_client.prev ; end
1166
+ def curr_client ; Client.curr ; end
1167
+ def next_client ; curr_client.next ; end
1168
+ def prev_client ; curr_client.prev ; end
1069
1169
 
1070
- def curr_area ; Area.curr ; end
1071
- def next_area ; curr_area.next ; end
1072
- def prev_area ; curr_area.prev ; end
1170
+ def curr_area ; Area.curr ; end
1171
+ def next_area ; curr_area.next ; end
1172
+ def prev_area ; curr_area.prev ; end
1073
1173
 
1074
- def curr_view ; View.curr ; end
1075
- def next_view ; curr_view.next ; end
1076
- def prev_view ; curr_view.prev ; end
1174
+ def curr_view ; View.curr ; end
1175
+ def next_view ; curr_view.next ; end
1176
+ def prev_view ; curr_view.prev ; end
1077
1177
 
1078
- def curr_tag ; curr_view.id ; end
1079
- def next_tag ; next_view.id ; end
1080
- def prev_tag ; prev_view.id ; end
1178
+ def curr_tag ; curr_view.id ; end
1179
+ def next_tag ; next_view.id ; end
1180
+ def prev_tag ; prev_view.id ; end
1081
1181
 
1082
- # provide easy access to container state information
1083
- [Client, Area, View].each {|c| c.extend ExportInstanceMethods }
1182
+ # provide easy access to container state information
1183
+ [Client, Area, View].each {|c| c.extend ExportInstanceMethods }
1084
1184
 
1085
- def focus_client id
1086
- Client.focus id
1087
- end
1185
+ def focus_client id
1186
+ Client.focus id
1187
+ end
1088
1188
 
1089
- def focus_area id
1090
- Area.focus id
1091
- end
1189
+ def focus_area id
1190
+ Area.focus id
1191
+ end
1092
1192
 
1093
- def focus_view id
1094
- View.focus id
1095
- end
1193
+ def focus_view id
1194
+ View.focus id
1195
+ end
1096
1196
 
1097
- # provide easy access to this module's instance methods
1098
- module_function(*instance_methods(false))
1197
+ # provide easy access to this module's instance methods
1198
+ extend self
1099
1199
  end