rumai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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