rumai 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. data/LICENSE +23 -0
  2. data/README +1 -0
  3. data/Rakefile +58 -0
  4. data/bin/rumai +38 -0
  5. data/doc/api/classes/IO.html +120 -0
  6. data/doc/api/classes/Integer.html +142 -0
  7. data/doc/api/classes/Integer.src/M000002.html +18 -0
  8. data/doc/api/classes/Object.html +113 -0
  9. data/doc/api/classes/Rumai.html +530 -0
  10. data/doc/api/classes/Rumai.src/M000007.html +18 -0
  11. data/doc/api/classes/Rumai.src/M000008.html +18 -0
  12. data/doc/api/classes/Rumai.src/M000009.html +18 -0
  13. data/doc/api/classes/Rumai.src/M000010.html +18 -0
  14. data/doc/api/classes/Rumai.src/M000011.html +18 -0
  15. data/doc/api/classes/Rumai.src/M000012.html +18 -0
  16. data/doc/api/classes/Rumai.src/M000013.html +18 -0
  17. data/doc/api/classes/Rumai.src/M000014.html +20 -0
  18. data/doc/api/classes/Rumai.src/M000015.html +18 -0
  19. data/doc/api/classes/Rumai.src/M000016.html +18 -0
  20. data/doc/api/classes/Rumai.src/M000017.html +18 -0
  21. data/doc/api/classes/Rumai.src/M000018.html +18 -0
  22. data/doc/api/classes/Rumai.src/M000019.html +18 -0
  23. data/doc/api/classes/Rumai.src/M000020.html +18 -0
  24. data/doc/api/classes/Rumai.src/M000021.html +18 -0
  25. data/doc/api/classes/Rumai.src/M000022.html +18 -0
  26. data/doc/api/classes/Rumai.src/M000023.html +18 -0
  27. data/doc/api/classes/Rumai.src/M000024.html +18 -0
  28. data/doc/api/classes/Rumai.src/M000025.html +18 -0
  29. data/doc/api/classes/Rumai.src/M000026.html +18 -0
  30. data/doc/api/classes/Rumai/Area.html +461 -0
  31. data/doc/api/classes/Rumai/Area.src/M000080.html +19 -0
  32. data/doc/api/classes/Rumai/Area.src/M000081.html +18 -0
  33. data/doc/api/classes/Rumai/Area.src/M000082.html +18 -0
  34. data/doc/api/classes/Rumai/Area.src/M000083.html +18 -0
  35. data/doc/api/classes/Rumai/Area.src/M000084.html +18 -0
  36. data/doc/api/classes/Rumai/Area.src/M000085.html +18 -0
  37. data/doc/api/classes/Rumai/Area.src/M000086.html +18 -0
  38. data/doc/api/classes/Rumai/Area.src/M000087.html +18 -0
  39. data/doc/api/classes/Rumai/Area.src/M000088.html +18 -0
  40. data/doc/api/classes/Rumai/Area.src/M000089.html +18 -0
  41. data/doc/api/classes/Rumai/Area.src/M000090.html +18 -0
  42. data/doc/api/classes/Rumai/Area.src/M000091.html +22 -0
  43. data/doc/api/classes/Rumai/Area.src/M000093.html +23 -0
  44. data/doc/api/classes/Rumai/Area.src/M000094.html +28 -0
  45. data/doc/api/classes/Rumai/Area.src/M000095.html +18 -0
  46. data/doc/api/classes/Rumai/Area.src/M000096.html +31 -0
  47. data/doc/api/classes/Rumai/Chain.html +179 -0
  48. data/doc/api/classes/Rumai/Chain.src/M000077.html +18 -0
  49. data/doc/api/classes/Rumai/Chain.src/M000078.html +18 -0
  50. data/doc/api/classes/Rumai/Chain.src/M000079.html +18 -0
  51. data/doc/api/classes/Rumai/Client.html +463 -0
  52. data/doc/api/classes/Rumai/Client.src/M000114.html +18 -0
  53. data/doc/api/classes/Rumai/Client.src/M000115.html +18 -0
  54. data/doc/api/classes/Rumai/Client.src/M000116.html +18 -0
  55. data/doc/api/classes/Rumai/Client.src/M000117.html +39 -0
  56. data/doc/api/classes/Rumai/Client.src/M000118.html +28 -0
  57. data/doc/api/classes/Rumai/Client.src/M000119.html +19 -0
  58. data/doc/api/classes/Rumai/Client.src/M000120.html +18 -0
  59. data/doc/api/classes/Rumai/Client.src/M000121.html +18 -0
  60. data/doc/api/classes/Rumai/Client.src/M000122.html +18 -0
  61. data/doc/api/classes/Rumai/Client.src/M000123.html +19 -0
  62. data/doc/api/classes/Rumai/Client.src/M000124.html +20 -0
  63. data/doc/api/classes/Rumai/Client.src/M000125.html +20 -0
  64. data/doc/api/classes/Rumai/Client.src/M000126.html +22 -0
  65. data/doc/api/classes/Rumai/Client.src/M000127.html +18 -0
  66. data/doc/api/classes/Rumai/Client.src/M000128.html +20 -0
  67. data/doc/api/classes/Rumai/Client.src/M000129.html +18 -0
  68. data/doc/api/classes/Rumai/Client.src/M000130.html +22 -0
  69. data/doc/api/classes/Rumai/ClientContainer.html +180 -0
  70. data/doc/api/classes/Rumai/ClientContainer.src/M000027.html +18 -0
  71. data/doc/api/classes/Rumai/ClientContainer.src/M000028.html +18 -0
  72. data/doc/api/classes/Rumai/ClientContainer.src/M000029.html +18 -0
  73. data/doc/api/classes/Rumai/ExportInstMethods.html +119 -0
  74. data/doc/api/classes/Rumai/IXP.html +149 -0
  75. data/doc/api/classes/Rumai/IXP/Agent.html +447 -0
  76. data/doc/api/classes/Rumai/IXP/Agent.src/M000046.html +47 -0
  77. data/doc/api/classes/Rumai/IXP/Agent.src/M000047.html +44 -0
  78. data/doc/api/classes/Rumai/IXP/Agent.src/M000048.html +39 -0
  79. data/doc/api/classes/Rumai/IXP/Agent.src/M000049.html +20 -0
  80. data/doc/api/classes/Rumai/IXP/Agent.src/M000050.html +22 -0
  81. data/doc/api/classes/Rumai/IXP/Agent.src/M000051.html +20 -0
  82. data/doc/api/classes/Rumai/IXP/Agent.src/M000052.html +33 -0
  83. data/doc/api/classes/Rumai/IXP/Agent.src/M000053.html +19 -0
  84. data/doc/api/classes/Rumai/IXP/Agent.src/M000054.html +18 -0
  85. data/doc/api/classes/Rumai/IXP/Agent.src/M000055.html +21 -0
  86. data/doc/api/classes/Rumai/IXP/Agent.src/M000056.html +20 -0
  87. data/doc/api/classes/Rumai/IXP/Agent.src/M000057.html +20 -0
  88. data/doc/api/classes/Rumai/IXP/Agent.src/M000058.html +22 -0
  89. data/doc/api/classes/Rumai/IXP/Agent.src/M000059.html +23 -0
  90. data/doc/api/classes/Rumai/IXP/Agent.src/M000060.html +19 -0
  91. data/doc/api/classes/Rumai/IXP/Agent/FidStream.html +254 -0
  92. data/doc/api/classes/Rumai/IXP/Agent/FidStream.src/M000062.html +22 -0
  93. data/doc/api/classes/Rumai/IXP/Agent/FidStream.src/M000063.html +21 -0
  94. data/doc/api/classes/Rumai/IXP/Agent/FidStream.src/M000064.html +42 -0
  95. data/doc/api/classes/Rumai/IXP/Agent/FidStream.src/M000065.html +26 -0
  96. data/doc/api/classes/Rumai/IXP/Agent/FidStream.src/M000066.html +36 -0
  97. data/doc/api/classes/Rumai/IXP/Agent/MODES.html +130 -0
  98. data/doc/api/classes/Rumai/IXP/Agent/MODES.src/M000061.html +22 -0
  99. data/doc/api/classes/Rumai/IXP/Agent/RangedPool.html +203 -0
  100. data/doc/api/classes/Rumai/IXP/Agent/RangedPool.src/M000068.html +23 -0
  101. data/doc/api/classes/Rumai/IXP/Agent/RangedPool.src/M000069.html +32 -0
  102. data/doc/api/classes/Rumai/IXP/Agent/RangedPool.src/M000070.html +20 -0
  103. data/doc/api/classes/Rumai/IXP/Error.html +117 -0
  104. data/doc/api/classes/Rumai/IXP/Fcall.html +261 -0
  105. data/doc/api/classes/Rumai/IXP/Fcall.src/M000071.html +20 -0
  106. data/doc/api/classes/Rumai/IXP/Fcall.src/M000072.html +25 -0
  107. data/doc/api/classes/Rumai/IXP/Fcall.src/M000073.html +18 -0
  108. data/doc/api/classes/Rumai/IXP/Fcall.src/M000074.html +18 -0
  109. data/doc/api/classes/Rumai/IXP/Qid.html +184 -0
  110. data/doc/api/classes/Rumai/IXP/Rattach.html +119 -0
  111. data/doc/api/classes/Rumai/IXP/Rauth.html +119 -0
  112. data/doc/api/classes/Rumai/IXP/Rclunk.html +119 -0
  113. data/doc/api/classes/Rumai/IXP/Rcreate.html +119 -0
  114. data/doc/api/classes/Rumai/IXP/Rerror.html +119 -0
  115. data/doc/api/classes/Rumai/IXP/Rflush.html +119 -0
  116. data/doc/api/classes/Rumai/IXP/Ropen.html +119 -0
  117. data/doc/api/classes/Rumai/IXP/Rread.html +119 -0
  118. data/doc/api/classes/Rumai/IXP/Rremove.html +119 -0
  119. data/doc/api/classes/Rumai/IXP/Rstat.html +119 -0
  120. data/doc/api/classes/Rumai/IXP/Rversion.html +119 -0
  121. data/doc/api/classes/Rumai/IXP/Rwalk.html +119 -0
  122. data/doc/api/classes/Rumai/IXP/Rwrite.html +119 -0
  123. data/doc/api/classes/Rumai/IXP/Rwstat.html +119 -0
  124. data/doc/api/classes/Rumai/IXP/Stat.html +248 -0
  125. data/doc/api/classes/Rumai/IXP/Stat.src/M000076.html +18 -0
  126. data/doc/api/classes/Rumai/IXP/Stream.html +158 -0
  127. data/doc/api/classes/Rumai/IXP/Stream.src/M000045.html +19 -0
  128. data/doc/api/classes/Rumai/IXP/Struct.html +257 -0
  129. data/doc/api/classes/Rumai/IXP/Struct.src/M000030.html +19 -0
  130. data/doc/api/classes/Rumai/IXP/Struct.src/M000031.html +18 -0
  131. data/doc/api/classes/Rumai/IXP/Struct.src/M000032.html +18 -0
  132. data/doc/api/classes/Rumai/IXP/Struct.src/M000033.html +18 -0
  133. data/doc/api/classes/Rumai/IXP/Struct.src/M000034.html +20 -0
  134. data/doc/api/classes/Rumai/IXP/Struct.src/M000035.html +57 -0
  135. data/doc/api/classes/Rumai/IXP/Struct/Field.html +304 -0
  136. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000036.html +21 -0
  137. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000037.html +21 -0
  138. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000038.html +20 -0
  139. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000039.html +29 -0
  140. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000040.html +18 -0
  141. data/doc/api/classes/Rumai/IXP/Struct/Field.src/M000041.html +18 -0
  142. data/doc/api/classes/Rumai/IXP/Struct/Field/CounteeField.html +152 -0
  143. data/doc/api/classes/Rumai/IXP/Struct/Field/CounteeField.src/M000043.html +24 -0
  144. data/doc/api/classes/Rumai/IXP/Struct/Field/CounteeField.src/M000044.html +25 -0
  145. data/doc/api/classes/Rumai/IXP/Struct/Field/CounterField.html +137 -0
  146. data/doc/api/classes/Rumai/IXP/Struct/Field/CounterField.src/M000042.html +18 -0
  147. data/doc/api/classes/Rumai/IXP/Tattach.html +120 -0
  148. data/doc/api/classes/Rumai/IXP/Tauth.html +119 -0
  149. data/doc/api/classes/Rumai/IXP/Tclunk.html +119 -0
  150. data/doc/api/classes/Rumai/IXP/Tcreate.html +120 -0
  151. data/doc/api/classes/Rumai/IXP/Terror.html +145 -0
  152. data/doc/api/classes/Rumai/IXP/Terror.src/M000075.html +18 -0
  153. data/doc/api/classes/Rumai/IXP/Tflush.html +119 -0
  154. data/doc/api/classes/Rumai/IXP/Topen.html +193 -0
  155. data/doc/api/classes/Rumai/IXP/Tread.html +119 -0
  156. data/doc/api/classes/Rumai/IXP/Tremove.html +119 -0
  157. data/doc/api/classes/Rumai/IXP/Tstat.html +119 -0
  158. data/doc/api/classes/Rumai/IXP/Tversion.html +137 -0
  159. data/doc/api/classes/Rumai/IXP/Twalk.html +120 -0
  160. data/doc/api/classes/Rumai/IXP/Twrite.html +120 -0
  161. data/doc/api/classes/Rumai/IXP/Twstat.html +119 -0
  162. data/doc/api/classes/Rumai/Node.html +461 -0
  163. data/doc/api/classes/Rumai/Node.src/M000097.html +18 -0
  164. data/doc/api/classes/Rumai/Node.src/M000098.html +18 -0
  165. data/doc/api/classes/Rumai/Node.src/M000099.html +22 -0
  166. data/doc/api/classes/Rumai/Node.src/M000100.html +18 -0
  167. data/doc/api/classes/Rumai/Node.src/M000101.html +18 -0
  168. data/doc/api/classes/Rumai/Node.src/M000102.html +18 -0
  169. data/doc/api/classes/Rumai/Node.src/M000103.html +18 -0
  170. data/doc/api/classes/Rumai/Node.src/M000104.html +22 -0
  171. data/doc/api/classes/Rumai/Node.src/M000105.html +18 -0
  172. data/doc/api/classes/Rumai/Node.src/M000106.html +18 -0
  173. data/doc/api/classes/Rumai/Node.src/M000107.html +18 -0
  174. data/doc/api/classes/Rumai/Node.src/M000108.html +18 -0
  175. data/doc/api/classes/Rumai/Node.src/M000109.html +18 -0
  176. data/doc/api/classes/Rumai/Node.src/M000110.html +18 -0
  177. data/doc/api/classes/Rumai/Node.src/M000111.html +18 -0
  178. data/doc/api/classes/Rumai/Node.src/M000112.html +20 -0
  179. data/doc/api/classes/Rumai/Node.src/M000113.html +27 -0
  180. data/doc/api/classes/Rumai/View.html +436 -0
  181. data/doc/api/classes/Rumai/View.src/M000131.html +18 -0
  182. data/doc/api/classes/Rumai/View.src/M000132.html +18 -0
  183. data/doc/api/classes/Rumai/View.src/M000133.html +18 -0
  184. data/doc/api/classes/Rumai/View.src/M000134.html +18 -0
  185. data/doc/api/classes/Rumai/View.src/M000135.html +18 -0
  186. data/doc/api/classes/Rumai/View.src/M000136.html +18 -0
  187. data/doc/api/classes/Rumai/View.src/M000137.html +18 -0
  188. data/doc/api/classes/Rumai/View.src/M000138.html +28 -0
  189. data/doc/api/classes/Rumai/View.src/M000139.html +18 -0
  190. data/doc/api/classes/Rumai/View.src/M000140.html +18 -0
  191. data/doc/api/classes/Rumai/View.src/M000141.html +18 -0
  192. data/doc/api/classes/Rumai/View.src/M000142.html +18 -0
  193. data/doc/api/classes/Rumai/View.src/M000143.html +29 -0
  194. data/doc/api/classes/Rumai/View.src/M000144.html +20 -0
  195. data/doc/api/classes/Rumai/View.src/M000145.html +33 -0
  196. data/doc/api/classes/Rumai/View.src/M000146.html +50 -0
  197. data/doc/api/classes/String.html +169 -0
  198. data/doc/api/classes/String.src/M000005.html +18 -0
  199. data/doc/api/classes/String.src/M000006.html +19 -0
  200. data/doc/api/classes/StringIO.html +120 -0
  201. data/doc/api/classes/Time.html +163 -0
  202. data/doc/api/classes/Time.src/M000003.html +18 -0
  203. data/doc/api/classes/Time.src/M000004.html +18 -0
  204. data/doc/api/created.rid +1 -0
  205. data/doc/api/files/lib/rumai/fs_rb.html +115 -0
  206. data/doc/api/files/lib/rumai/ixp/message_rb.html +120 -0
  207. data/doc/api/files/lib/rumai/ixp/message_spec_rb.html +175 -0
  208. data/doc/api/files/lib/rumai/ixp/message_spec_rb.src/M000001.html +37 -0
  209. data/doc/api/files/lib/rumai/ixp/transport_rb.html +115 -0
  210. data/doc/api/files/lib/rumai/ixp_rb.html +116 -0
  211. data/doc/api/files/lib/rumai/nfo_rb.html +107 -0
  212. data/doc/api/files/lib/rumai/wm_rb.html +115 -0
  213. data/doc/api/files/lib/rumai_rb.html +115 -0
  214. data/doc/api/fr_class_index.html +82 -0
  215. data/doc/api/fr_file_index.html +34 -0
  216. data/doc/api/fr_method_index.html +172 -0
  217. data/doc/api/index.html +24 -0
  218. data/doc/api/rdoc-style.css +208 -0
  219. data/doc/guide.erb +390 -0
  220. data/doc/guide.html +2035 -0
  221. data/doc/index.html +2035 -0
  222. data/lib/rumai.rb +9 -0
  223. data/lib/rumai/fs.rb +170 -0
  224. data/lib/rumai/ixp.rb +9 -0
  225. data/lib/rumai/ixp/message.rb +642 -0
  226. data/lib/rumai/ixp/message_spec.rb +247 -0
  227. data/lib/rumai/ixp/transport.rb +382 -0
  228. data/lib/rumai/nfo.rb +24 -0
  229. data/lib/rumai/wm.rb +780 -0
  230. metadata +320 -0
data/lib/rumai/nfo.rb ADDED
@@ -0,0 +1,24 @@
1
+ # Project information.
2
+ #--
3
+ # Copyright 2007 Suraj N. Kurapati
4
+ # See the file named LICENSE for details.
5
+
6
+ module Rumai
7
+ NFO = {
8
+ :name => 'Rumai',
9
+ :version => '1.0.0',
10
+ :release => '2008-01-26',
11
+ :website => 'http://rumai.rubyforge.org',
12
+ :home => File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
13
+ }
14
+
15
+ class << NFO
16
+ # Returns the name and version.
17
+ def to_s
18
+ self[:name] + ' ' + self[:version]
19
+ end
20
+
21
+ # throw an exception instead of returning nil
22
+ alias [] fetch
23
+ end
24
+ end
data/lib/rumai/wm.rb ADDED
@@ -0,0 +1,780 @@
1
+ # Abstractions for the window manager.
2
+ #--
3
+ # Copyright 2006 Suraj N. Kurapati
4
+ # See the file named LICENSE for details.
5
+
6
+ require 'fs'
7
+ require 'enumerator'
8
+
9
+ class Object
10
+ # prevent these deprecated properties from clashing with our usage below
11
+ undef id, type
12
+ end
13
+
14
+ module Rumai
15
+ ##
16
+ #
17
+ # access to global WM state
18
+ #
19
+ ##
20
+
21
+ ROOT = Node.new '/'
22
+
23
+ # Returns the root of IXP file system hierarchy.
24
+ def fs
25
+ ROOT
26
+ end
27
+
28
+ # Returns the current set of tags.
29
+ def tags
30
+ fs.tag.entries.sort - %w[sel]
31
+ end
32
+
33
+ # Returns the current set of views.
34
+ def views
35
+ tags.map! {|t| View.new t}
36
+ end
37
+
38
+ module ClientContainer
39
+ # see definition below!
40
+ end
41
+
42
+ include ClientContainer
43
+ # Returns the IDs of the current set of clients.
44
+ def client_ids
45
+ fs.client.entries - %w[sel]
46
+ end
47
+
48
+ # Returns the name of the currently focused tag.
49
+ def curr_tag
50
+ curr_view.id
51
+ end
52
+
53
+ # Returns the name of the next tag.
54
+ def next_tag
55
+ next_view.id
56
+ end
57
+
58
+ # Returns the name of the previous tag.
59
+ def prev_tag
60
+ prev_view.id
61
+ end
62
+
63
+
64
+ ##
65
+ #
66
+ # multiple client grouping: allows you to group a set of clients
67
+ # together and perform operations on all of them simultaneously.
68
+ #
69
+ ##
70
+
71
+ GROUPING_TAG = '@'
72
+
73
+ # Returns a list of all grouped clients in
74
+ # the currently focused view. If there are
75
+ # no grouped clients, then the currently
76
+ # focused client is returned in the list.
77
+ def grouping
78
+ list = curr_view.clients.select {|c| c.group? }
79
+ list << curr_client if list.empty? and curr_client.exist?
80
+ list
81
+ end
82
+
83
+
84
+ ##
85
+ #
86
+ # abstraction of WM components
87
+ #
88
+ ##
89
+
90
+ # NOTE: Inheritors must override the 'chain' method.
91
+ module Chain
92
+ # Returns an array of objects related to this one.
93
+ def chain
94
+ [self]
95
+ end
96
+
97
+ # Returns the object after this one in the chain.
98
+ def next
99
+ sibling(+1)
100
+ end
101
+
102
+ # Returns the object before this one in the chain.
103
+ def prev
104
+ sibling(-1)
105
+ end
106
+
107
+ private
108
+
109
+ def sibling aOffset
110
+ arr = chain
111
+
112
+ if pos = arr.index(self)
113
+ arr[(pos + aOffset) % arr.length]
114
+ end
115
+ end
116
+ end
117
+
118
+ # The basic building block of the WM hierarchy.
119
+ #
120
+ # NOTE: Inheritors must have a 'current' class method.
121
+ # NOTE: Inheritors must override the 'focus' method.
122
+ #
123
+ module WidgetImpl #:nodoc:
124
+ attr_reader :id
125
+
126
+ def == aOther
127
+ @id == aOther.id
128
+ end
129
+
130
+ # Checks if this widget currently has focus.
131
+ def current?
132
+ self == self.class.curr
133
+ end
134
+
135
+ alias focus? current?
136
+ end
137
+
138
+ # A widget that has a corresponding representation in the IXP file system.
139
+ class WidgetNode < Node #:nodoc:
140
+ include WidgetImpl
141
+
142
+ def initialize aId, aPathPrefix
143
+ super "#{aPathPrefix}/#{aId}"
144
+
145
+ if aId.to_s == 'sel' and ctl.exist?
146
+ @id = ctl.read
147
+ @path = File.join(File.dirname(@path), @id)
148
+ else
149
+ @id = File.basename(@path)
150
+ end
151
+ end
152
+ end
153
+
154
+ # A graphical program that is running in your current X Windows session.
155
+ class Client < WidgetNode
156
+ def initialize aClientId
157
+ super aClientId, '/client'
158
+ end
159
+
160
+ # Returns the currently focused client.
161
+ def self.curr
162
+ new :sel
163
+ end
164
+
165
+ include Chain
166
+ # Returns a list of clients in the current view.
167
+ def chain
168
+ View.curr.clients
169
+ end
170
+
171
+ ##
172
+ #
173
+ # WM operations
174
+ #
175
+ ##
176
+
177
+ # Focuses this client within the given view.
178
+ def focus aView = nil
179
+ if exist? and not focus?
180
+ (aView ? [aView] : self.views).each do |v|
181
+ if a = self.area(v)
182
+ v.focus
183
+ a.focus
184
+
185
+ # slide focus from the current client onto this client
186
+ arr = a.client_ids
187
+ src = arr.index Client.curr.id
188
+ dst = arr.index @id
189
+
190
+ distance = (src - dst).abs
191
+ direction = src < dst ? :down : :up
192
+
193
+ distance.times do
194
+ v.ctl.write "select #{direction}"
195
+ end
196
+
197
+ break
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # Sends this client to the given destination within the given view.
204
+ def send aAreaOrId, aView = View.curr
205
+ if aAreaOrId.to_s != 'toggle'
206
+ # XXX: it is an error to send a floating client directly to a
207
+ # managed area, so we gotta "ground" it first and then send it
208
+ # to the desired managed area. John-Galt will fix this someday.
209
+ if area(aView).float?
210
+ aView.ctl.write "send #{@id} toggle"
211
+ end
212
+ end
213
+
214
+ dst = area_to_id(aAreaOrId)
215
+ aView.ctl.write "send #{@id} #{dst}"
216
+ end
217
+
218
+ # Swaps this client with the given destination within the given view.
219
+ def swap aAreaOrId, aView = View.curr
220
+ dst = area_to_id(aAreaOrId)
221
+ aView.ctl.write "swap #{@id} #{dst}"
222
+ end
223
+
224
+ ##
225
+ #
226
+ # WM hierarchy
227
+ #
228
+ ##
229
+
230
+ # Returns the area that contains this client within the given view.
231
+ def area aView = View.curr
232
+ aView.area_of_client self
233
+ end
234
+
235
+ # Returns the views that contain this client.
236
+ def views
237
+ tags.map! {|t| View.new t}
238
+ end
239
+
240
+ ##
241
+ #
242
+ # tag manipulations
243
+ #
244
+ ##
245
+
246
+ TAG_DELIMITER = '+'.freeze
247
+
248
+ # Returns the tags associated with this client.
249
+ def tags
250
+ self[:tags].read.split TAG_DELIMITER
251
+ end
252
+
253
+ # Modifies the tags associated with this client.
254
+ def tags= *aTags
255
+ arr = aTags.flatten.compact.uniq
256
+ self[:tags].write arr.join(TAG_DELIMITER)
257
+ end
258
+
259
+ # Evaluates the given block within the
260
+ # context of this client's list of tags.
261
+ def with_tags &aBlock
262
+ arr = self.tags
263
+ arr.instance_eval(&aBlock)
264
+ self.tags = arr
265
+ end
266
+
267
+ # Adds the given tags to this client.
268
+ def tag *aTags
269
+ with_tags do
270
+ concat aTags
271
+ end
272
+ end
273
+
274
+ # Removes the given tags from this client.
275
+ def untag *aTags
276
+ with_tags do
277
+ aTags.flatten.each do |tag|
278
+ delete tag.to_s
279
+ end
280
+ end
281
+ end
282
+
283
+ ##
284
+ #
285
+ # multiple client grouping
286
+ #
287
+ ##
288
+
289
+ # Checks if this client is included in the current grouping.
290
+ def group?
291
+ tags.include? GROUPING_TAG
292
+ end
293
+
294
+ # Adds this client to the current grouping.
295
+ def group
296
+ with_tags do
297
+ push GROUPING_TAG
298
+ end
299
+ end
300
+
301
+ # Removes this client to the current grouping.
302
+ def ungroup
303
+ untag GROUPING_TAG
304
+ end
305
+
306
+ # Toggles the presence of this client in the current grouping.
307
+ def toggle_group
308
+ if group?
309
+ ungroup
310
+ else
311
+ group
312
+ end
313
+ end
314
+
315
+ private
316
+
317
+ def area_to_id aAreaOrId
318
+ if aAreaOrId.respond_to? :id
319
+ # XXX: +1 until John-Galt fixes this: right now, index 1
320
+ # is floating area; but ~ (0) should be floating area.
321
+ aAreaOrId.id+1
322
+ else
323
+ aAreaOrId
324
+ end
325
+ end
326
+ end
327
+
328
+ # NOTE: Inheritors should override the 'client_ids' method.
329
+ module ClientContainer
330
+ # Returns the IDs of the clients in this container.
331
+ def client_ids
332
+ []
333
+ end
334
+
335
+ # Returns the clients contained in this container.
336
+ def clients
337
+ client_ids.map! {|i| Client.new i}
338
+ end
339
+
340
+ # multiple client grouping
341
+ %w[group ungroup toggle_group].each do |meth|
342
+ define_method meth do
343
+ clients.each do |c|
344
+ c.__send__ meth
345
+ end
346
+ end
347
+ end
348
+
349
+ # Returns all grouped clients in this container.
350
+ def grouping
351
+ clients.select {|c| c.group? }
352
+ end
353
+ end
354
+
355
+ # A region that contains clients. This can be either
356
+ # the floating area or a column in the managed area.
357
+ class Area
358
+ attr_reader :view
359
+
360
+ # aView:: the view which contains this area.
361
+ def initialize aAreaId, aView = View.curr
362
+ @id = aAreaId.to_i
363
+ @view = aView
364
+ end
365
+
366
+ # Checks if this area is the floating area.
367
+ def float?
368
+ @id == 0
369
+ end
370
+
371
+ # Checks if this area is a column in the managed area.
372
+ def column?
373
+ not float?
374
+ end
375
+
376
+ include WidgetImpl
377
+ # Returns the currently focused area.
378
+ def self.curr
379
+ View.curr.area_of_client Client.curr
380
+ end
381
+
382
+ include Chain
383
+ def chain
384
+ @view.areas
385
+ end
386
+
387
+ # Checks if this object exists in the chain.
388
+ def exist?
389
+ chain.include? self
390
+ end
391
+
392
+ include ClientContainer
393
+ # Returns the IDs of the clients in this area.
394
+ def client_ids
395
+ @view.client_ids ctl_id
396
+ end
397
+
398
+ include Enumerable
399
+ # Iterates through each client in this container.
400
+ def each &aBlock
401
+ clients.each(&aBlock)
402
+ end
403
+
404
+ # Sets the layout of clients in this column.
405
+ def layout= aMode
406
+ @view.ctl.write "colmode #{ctl_id} #{aMode}"
407
+ end
408
+
409
+ ##
410
+ #
411
+ # WM operations
412
+ #
413
+ ##
414
+
415
+ # Puts focus on this area.
416
+ def focus
417
+ @view.ctl.write "select #{ctl_id}"
418
+ end
419
+
420
+ ##
421
+ #
422
+ # array abstraction: area is an array of clients
423
+ #
424
+ ##
425
+
426
+ # Returns the number of clients in this area.
427
+ def length
428
+ client_ids.length
429
+ end
430
+
431
+ # Inserts the given clients at the bottom of this area.
432
+ def push *aClients
433
+ if target = clients.last
434
+ target.focus
435
+ end
436
+
437
+ insert aClients
438
+ end
439
+
440
+ alias << push
441
+
442
+ # Inserts the given clients after the currently focused client in this area.
443
+ def insert *aClients
444
+ aClients.flatten!
445
+ return if aClients.empty?
446
+
447
+ aClients.each do |c|
448
+ import_client c
449
+ end
450
+ end
451
+
452
+ # Inserts the given clients at the top of this area.
453
+ def unshift *aClients
454
+ aClients.flatten!
455
+ return if aClients.empty?
456
+
457
+ if target = clients.first
458
+ target.focus
459
+ end
460
+
461
+ aClients.each do |c|
462
+ import_client c
463
+ c.send :up if target
464
+ end
465
+ end
466
+
467
+ # Concatenates the given area to the bottom of this area.
468
+ def concat aArea
469
+ push aArea.clients
470
+ end
471
+
472
+ # Ensures that this area has at most the given number of clients.
473
+ # Areas to the right of this one serve as a buffer into which excess
474
+ # clients are evicted and from which deficit clients are imported.
475
+ def length= aMaxClients
476
+ return unless aMaxClients > 0
477
+ len, out = length, fringe
478
+
479
+ if len > aMaxClients
480
+ out.unshift clients[aMaxClients..-1].reverse
481
+
482
+ elsif len < aMaxClients
483
+ until (diff = aMaxClients - length) == 0
484
+ immigrants = out.clients.first(diff)
485
+ break if immigrants.empty?
486
+
487
+ push immigrants
488
+ end
489
+ end
490
+ end
491
+
492
+ private
493
+
494
+ # Makes the ID usable in wmii's /ctl commands.
495
+ def ctl_id
496
+ float? ? '~' : @id
497
+ end
498
+
499
+ # Moves the given client into this area.
500
+ def import_client c
501
+ if exist?
502
+ c.send self
503
+
504
+ else
505
+ # move the client to the nearest existing column
506
+ src = c.area
507
+ dst = chain.last
508
+
509
+ c.send dst unless src == dst
510
+
511
+ # slide the client over to this column
512
+ c.send :right
513
+ @id = dst.id.next
514
+
515
+ raise 'column should exist now' unless exist?
516
+ end
517
+ end
518
+
519
+ # Returns the next area, which may or may not exist.
520
+ def fringe
521
+ Area.new @id.next, @view
522
+ end
523
+ end
524
+
525
+ # The visualization of a tag.
526
+ class View < WidgetNode
527
+ include WidgetImpl
528
+ # Returns the currently focused view.
529
+ def self.curr
530
+ new :sel
531
+ end
532
+
533
+ # Focuses this view.
534
+ def focus
535
+ Rumai.fs.ctl.write "view #{@id}"
536
+ end
537
+
538
+ include Chain
539
+ def chain
540
+ Rumai.views
541
+ end
542
+
543
+ include ClientContainer
544
+ # Returns the IDs of the clients contained
545
+ # in the given area within this view.
546
+ def client_ids aAreaId = '\S+'
547
+ manifest.scan(/^#{aAreaId} (0x\S+)/).flatten
548
+ end
549
+
550
+ include Enumerable
551
+ # Iterates through each area in this view.
552
+ def each &aBlock
553
+ areas.each(&aBlock)
554
+ end
555
+
556
+ def initialize aViewId
557
+ super aViewId, '/tag'
558
+ end
559
+
560
+ # Returns the manifest of all areas and clients in this view.
561
+ def manifest
562
+ index.read || ''
563
+ end
564
+
565
+ ##
566
+ #
567
+ # WM hierarchy
568
+ #
569
+ ##
570
+
571
+ # Returns the area which contains the given client in this view.
572
+ def area_of_client aClientOrId
573
+ arg =
574
+ if aClientOrId.respond_to? :id
575
+ aClientOrId.id
576
+ else
577
+ aClientOrId
578
+ end
579
+
580
+ manifest =~ /^(\S+) #{arg}/
581
+ if areaId = $1
582
+ Area.new areaId, self
583
+ end
584
+ end
585
+
586
+ # Returns the IDs of all areas in this view.
587
+ def area_ids
588
+ manifest.scan(/^# (\S+)/).flatten
589
+ end
590
+
591
+ # Returns all areas in this view.
592
+ def areas
593
+ area_ids.map! {|i| Area.new i, self}
594
+ end
595
+
596
+ # Returns the floating area of this view.
597
+ def floating_area
598
+ areas.first
599
+ end
600
+
601
+ # Returns all columns (managed areas) in this view.
602
+ def columns
603
+ areas[1..-1]
604
+ end
605
+
606
+ # Resiliently iterates through possibly destructive changes to
607
+ # each column. That is, if the given block creates new
608
+ # columns, then those will also be processed in the iteration.
609
+ def each_column aStartingColumnId = 1
610
+ i = aStartingColumnId
611
+ loop do
612
+ a = Area.new i, self
613
+
614
+ if a.exist?
615
+ yield a
616
+ else
617
+ break
618
+ end
619
+
620
+ i += 1
621
+ end
622
+ end
623
+
624
+ ##
625
+ #
626
+ # visual arrangement of clients
627
+ #
628
+ ##
629
+
630
+ # Arranges the clients in this view, while maintaining
631
+ # their relative order, in the tiling fashion of
632
+ # LarsWM. Only the first client in the primary column
633
+ # is kept; all others are evicted to the *top* of the
634
+ # secondary column. Any subsequent columns are
635
+ # squeezed into the *bottom* of the secondary column.
636
+ def arrange_as_larswm
637
+ float, main, *extra = areas
638
+ main.length = 1
639
+ squeeze extra
640
+ end
641
+
642
+ # Arranges the clients in this view, while maintaining
643
+ # their relative order, in a (at best) square grid.
644
+ def arrange_in_grid aMaxClientsPerColumn = nil
645
+ # compute client distribution
646
+ unless aMaxClientsPerColumn
647
+ numClients = num_managed_clients
648
+ return unless numClients > 0
649
+
650
+ numColumns = Math.sqrt(numClients)
651
+ aMaxClientsPerColumn = (numClients / numColumns).round
652
+ end
653
+
654
+ return unless aMaxClientsPerColumn > 1
655
+
656
+ # apply the distribution
657
+ each_column do |a|
658
+ a.length = aMaxClientsPerColumn
659
+ a.layout = :default
660
+ end
661
+ end
662
+
663
+ # Arranges the clients in this view, while maintaining their relative order,
664
+ # in a (at best) equilateral triangle. However, the resulting arrangement
665
+ # appears like a diamond because wmii does not waste screen space.
666
+ def arrange_in_diamond
667
+ numClients = num_managed_clients
668
+ return unless numClients > 1
669
+
670
+ # determine dimensions of the rising sub-triangle
671
+ rise = numClients / 2
672
+
673
+ span = sum = 0
674
+ 1.upto rise do |h|
675
+ if sum + h > rise
676
+ break
677
+ else
678
+ sum += h
679
+ span += 1
680
+ end
681
+ end
682
+
683
+ peak = numClients - (sum * 2)
684
+
685
+ # describe the overall triangle as a sequence of heights
686
+ riseSeq = (1..span).to_a
687
+ fallSeq = riseSeq.reverse
688
+
689
+ heights = riseSeq
690
+ heights << peak if peak > 0
691
+ heights.concat fallSeq
692
+
693
+ # apply the heights
694
+ each_column do |col|
695
+ if h = heights.shift
696
+ col.length = h
697
+ col.layout = :default
698
+ end
699
+ end
700
+ end
701
+
702
+ private
703
+
704
+ # Returns the number of clients in the non-floating areas of this view.
705
+ def num_managed_clients
706
+ manifest.scan(/^\d+ 0x/).length
707
+ end
708
+
709
+ # Smashes the given list of areas into the first one.
710
+ # The relative ordering of clients is preserved.
711
+ def squeeze aAreas
712
+ aAreas.reverse.each_cons(2) do |src, dst|
713
+ dst.concat src
714
+ end
715
+ end
716
+ end
717
+
718
+
719
+ ##
720
+ #
721
+ # shortcuts for interactive WM manipulation (via IRB)
722
+ #
723
+ ##
724
+
725
+ # provide easy access to container state information
726
+ [Client, Area, View].each do |c|
727
+ c.extend ExportInstMethods
728
+ end
729
+
730
+ def curr_client
731
+ Client.curr
732
+ end
733
+
734
+ def next_client
735
+ curr_client.next
736
+ end
737
+
738
+ def prev_client
739
+ curr_client.prev
740
+ end
741
+
742
+ def curr_area
743
+ Area.curr
744
+ end
745
+
746
+ def next_area
747
+ curr_area.next
748
+ end
749
+
750
+ def prev_area
751
+ curr_area.prev
752
+ end
753
+
754
+ def curr_view
755
+ View.curr
756
+ end
757
+
758
+ def next_view
759
+ curr_view.next
760
+ end
761
+
762
+ def prev_view
763
+ curr_view.prev
764
+ end
765
+
766
+ def focus_client aId
767
+ Client.focus(aId)
768
+ end
769
+
770
+ def focus_area aId
771
+ Area.focus(aId)
772
+ end
773
+
774
+ def focus_view aId
775
+ View.focus(aId)
776
+ end
777
+
778
+ # provide easy access to this module's instance methods
779
+ module_function(*instance_methods)
780
+ end