rumai 3.2.4 → 3.3.0

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