Writing sublets » History » Version 13
Christoph Kappel, 01/23/2018 12:22 PM
1 | 13 | Christoph Kappel | h1. Writing sublets |
---|---|---|---|
2 | 13 | Christoph Kappel | |
3 | 13 | Christoph Kappel | {{>toc}} |
4 | 13 | Christoph Kappel | |
5 | 13 | Christoph Kappel | h2. Hello world |
6 | 13 | Christoph Kappel | |
7 | 13 | Christoph Kappel | Starting a [[sublets|sublet]] from scratch is really easy, just create an empty [[sublets|sublet]] with [[sur]]: @sur template hello@ |
8 | 13 | Christoph Kappel | |
9 | 13 | Christoph Kappel | This will create a folder *hello* with two files: |
10 | 13 | Christoph Kappel | |
11 | 13 | Christoph Kappel | |_. Filename |_. Description | |
12 | 13 | Christoph Kappel | | *hello.rb* | The [[sublets|sublet]] file with the minimal required code | |
13 | 13 | Christoph Kappel | | *hello.spec* | The [[specification]] file with basic information for [[sur]] about the [[sublets|sublet]] | |
14 | 13 | Christoph Kappel | |
15 | 13 | Christoph Kappel | h3. Say hello |
16 | 13 | Christoph Kappel | |
17 | 13 | Christoph Kappel | Per default, the new [[sublets|sublet]] will do nothing and we need to add something do the data field: |
18 | 13 | Christoph Kappel | |
19 | 13 | Christoph Kappel | <pre><code class="ruby"># Hello sublet file |
20 | 13 | Christoph Kappel | # Created with sur-0.2 |
21 | 13 | Christoph Kappel | configure :hello do |s| |
22 | 13 | Christoph Kappel | s.interval = 60 |
23 | 13 | Christoph Kappel | end |
24 | 13 | Christoph Kappel | |
25 | 13 | Christoph Kappel | on :run do |s| |
26 | 13 | Christoph Kappel | s.data = "Hello, world!" |
27 | 13 | Christoph Kappel | end</code></pre> |
28 | 13 | Christoph Kappel | |
29 | 13 | Christoph Kappel | h3. Using variables |
30 | 13 | Christoph Kappel | |
31 | 13 | Christoph Kappel | Typically, a [[sublets|sublet]] just needs local and instance variables to store data across runs and/or to calculate intermediate values. Due to the "DSL":http://en.wikipedia.org/wiki/Domain-specific_language, instance variables that normally just need a to be prefixed with a just *@* now need an _owner_ to be tracked as instance variable. That can either be the first argument of every event block or self inside of a helper. |
32 | 13 | Christoph Kappel | |
33 | 13 | Christoph Kappel | <pre><code class="ruby"># Hello sublet file |
34 | 13 | Christoph Kappel | # Created with sur-0.2 |
35 | 13 | Christoph Kappel | configure :hello do |s| |
36 | 13 | Christoph Kappel | s.interval = 60 |
37 | 13 | Christoph Kappel | s.world = "variable world" |
38 | 13 | Christoph Kappel | end |
39 | 13 | Christoph Kappel | |
40 | 13 | Christoph Kappel | on :run do |s| |
41 | 13 | Christoph Kappel | s.data = "Hello, #{s.world}" |
42 | 13 | Christoph Kappel | end</code></pre> |
43 | 13 | Christoph Kappel | |
44 | 13 | Christoph Kappel | h3. Move code to helper |
45 | 13 | Christoph Kappel | |
46 | 13 | Christoph Kappel | Sometimes it can be useful to move code into own functions to call it on several events. Instead of just copying the code let's move the code into a helper. A helper is just another block that can be used to add methods to the [[sublets|sublet]] itself. |
47 | 13 | Christoph Kappel | |
48 | 13 | Christoph Kappel | <pre><code class="ruby"># Hello sublet file |
49 | 13 | Christoph Kappel | # Created with sur-0.2 |
50 | 13 | Christoph Kappel | configure :hello do |s| |
51 | 13 | Christoph Kappel | s.interval = 60 |
52 | 13 | Christoph Kappel | s.world = "variable world" |
53 | 13 | Christoph Kappel | end |
54 | 13 | Christoph Kappel | |
55 | 13 | Christoph Kappel | helper do |s| |
56 | 13 | Christoph Kappel | def hello_time |
57 | 13 | Christoph Kappel | case Time.now.strftime("%H").to_i |
58 | 13 | Christoph Kappel | when 6..9 then "Good morning" |
59 | 13 | Christoph Kappel | when 10..15 then "Good day" |
60 | 13 | Christoph Kappel | when 16..18 then "Good evening" |
61 | 13 | Christoph Kappel | when 19..5 then "Good night" |
62 | 13 | Christoph Kappel | end |
63 | 13 | Christoph Kappel | end |
64 | 13 | Christoph Kappel | end |
65 | 13 | Christoph Kappel | |
66 | 13 | Christoph Kappel | on :run do |s| |
67 | 13 | Christoph Kappel | s.data = "%s, %s" % [ s.hello_time, s.world ] |
68 | 13 | Christoph Kappel | end</code></pre> |
69 | 13 | Christoph Kappel | |
70 | 13 | Christoph Kappel | h3. Test your sublet |
71 | 13 | Christoph Kappel | |
72 | 13 | Christoph Kappel | [[sur]] comes with a tester for [[sublets|sublet]] to ease the whole thing: @sur test hello/hello.rb |
73 | 13 | Christoph Kappel | |
74 | 13 | Christoph Kappel | The output looks like this: |
75 | 13 | Christoph Kappel | |
76 | 13 | Christoph Kappel | <pre><code class="bash">------------------ |
77 | 13 | Christoph Kappel | hello |
78 | 13 | Christoph Kappel | ------------------ |
79 | 13 | Christoph Kappel | @visible = true |
80 | 13 | Christoph Kappel | @interval = 60 |
81 | 13 | Christoph Kappel | @config = {} |
82 | 13 | Christoph Kappel | @world = "variable world" |
83 | 13 | Christoph Kappel | @data = Hello, variable world! |
84 | 13 | Christoph Kappel | ------------------ |
85 | 13 | Christoph Kappel | (1) run |
86 | 13 | Christoph Kappel | (2) hello_time (helper) |
87 | 13 | Christoph Kappel | (0) Quit |
88 | 13 | Christoph Kappel | ------------------ |
89 | 13 | Christoph Kappel | >>> Select one method: |
90 | 13 | Christoph Kappel | >>> |
91 | 13 | Christoph Kappel | </code></pre> |
92 | 13 | Christoph Kappel | |
93 | 13 | Christoph Kappel | The lines starting with *@* show the defined instanced variables, the lines with *(1)* the callable methods. |
94 | 13 | Christoph Kappel | |
95 | 13 | Christoph Kappel | h3. Install and submit |
96 | 13 | Christoph Kappel | |
97 | 13 | Christoph Kappel | Once you are satisfied with your work you need to build your [[sublets|sublet]]: @sur build hello/hello.spec@ |
98 | 13 | Christoph Kappel | |
99 | 13 | Christoph Kappel | This will create a *hello-0.0.sublet* file that can either be installed locally @sur install ./hello-0.0.sublet@ or uploaded to the [[sur]] repository @sur submit ./hello-0.0.sublet@ and installed from there. |
100 | 13 | Christoph Kappel | |
101 | 13 | Christoph Kappel | h2. Components |
102 | 13 | Christoph Kappel | |
103 | 13 | Christoph Kappel | h3. Configure |
104 | 13 | Christoph Kappel | |
105 | 13 | Christoph Kappel | A [[sublets|sublet]] starts with the configure block, which tells [[subtle]] the name and does basic initialization like the interval time if it's used. Assignment of instance variables *must* be done like @s.value = "something"@ or otherwise the "DSL":http://en.wikipedia.org/wiki/Domain-specific_language can't keep track of it. |
106 | 13 | Christoph Kappel | |
107 | 13 | Christoph Kappel | <pre><code class="ruby">configure :sublet do |s| |
108 | 13 | Christoph Kappel | s.interval = 60 |
109 | 13 | Christoph Kappel | s.variable = "something" |
110 | 13 | Christoph Kappel | end</code></pre> |
111 | 13 | Christoph Kappel | |
112 | 13 | Christoph Kappel | Generally there are two different types of [[Sublets]]: |
113 | 13 | Christoph Kappel | |
114 | 13 | Christoph Kappel | * [[sublets]] that are updated by given interval in seconds (default 60s) |
115 | 13 | Christoph Kappel | * [[sublets]] that are updated when a file is modified (via "inotify":http://en.wikipedia.org/wiki/Inotify) or via socket |
116 | 13 | Christoph Kappel | * [[sublets]] that are updated via [[Hacking#SUBTLE_SUBLET_DATA|SUBTLE_SUBLET_DATA]] client message |
117 | 13 | Christoph Kappel | |
118 | 13 | Christoph Kappel | The [[sublets|sublet]] data *must* be of type "String":http://www.ruby-doc.org/core/classes/String.html, everything else will be ignored. |
119 | 13 | Christoph Kappel | |
120 | 13 | Christoph Kappel | h3. Helper |
121 | 13 | Christoph Kappel | |
122 | 13 | Christoph Kappel | Custom methods need to be written inside of a helper block to be used a instance method. Inside of this helpers, instance variables *must* be accessed via the *self* keyword: |
123 | 13 | Christoph Kappel | |
124 | 13 | Christoph Kappel | <pre><code class="ruby">helper do |
125 | 13 | Christoph Kappel | def something(test) |
126 | 13 | Christoph Kappel | self.variable = test |
127 | 13 | Christoph Kappel | self.data = test |
128 | 13 | Christoph Kappel | end |
129 | 13 | Christoph Kappel | end</code></pre> |
130 | 13 | Christoph Kappel | |
131 | 13 | Christoph Kappel | h3. Events |
132 | 13 | Christoph Kappel | |
133 | 13 | Christoph Kappel | [[Sublets]] are event driven, so there are some specific events only for [[sublets]]: |
134 | 13 | Christoph Kappel | |
135 | 13 | Christoph Kappel | |_. Name |_. Description |_. Arguments | |
136 | 13 | Christoph Kappel | | *:mouse_over* | Whenever the pointer is over the [[sublets|sublet]] | s | |
137 | 13 | Christoph Kappel | | *:mouse_down* | Whenever the pointer is pressed on the [[sublets|sublet]]. | s, x, y, button | |
138 | 13 | Christoph Kappel | | *:mouse_out* | Whenever the pointer leaves the [[sublets|sublet]] | s | |
139 | 13 | Christoph Kappel | | *:run* | Whenever either the interval time is expired | s | |
140 | 13 | Christoph Kappel | | *:watch* | Whenever the watched file is modified/socket has data ready | s | |
141 | 13 | Christoph Kappel | | *:data* | Whenever [[subtle]] receives a [[Hacking#SUBTLE_SUBLET_DATA|SUBTLE_SUBLET_DATA]] client message for this [[sublets|sublet]] | s | |
142 | 13 | Christoph Kappel | | *:unload* | Whenever the [[sublets|sublet]] is unloaded | s | |
143 | 13 | Christoph Kappel | |
144 | 13 | Christoph Kappel | <pre><code class="ruby">on :mouse_down do |s, x, y, button| |
145 | 13 | Christoph Kappel | puts "button %d at x=%d and y=%d" % [ x, y, button ] |
146 | 13 | Christoph Kappel | end</code></pre> |
147 | 13 | Christoph Kappel | |
148 | 13 | Christoph Kappel | h3. Hooks |
149 | 13 | Christoph Kappel | |
150 | 13 | Christoph Kappel | [[Sublets]] can use all [[hooks]] that are useable in the main config too, the syntax is almost the same: |
151 | 13 | Christoph Kappel | |
152 | 13 | Christoph Kappel | <pre><code class="ruby">on :client_create do |s, c| |
153 | 13 | Christoph Kappel | puts s.name #< Name of the sublet |
154 | 13 | Christoph Kappel | puts c.name #< Name of the client |
155 | 13 | Christoph Kappel | end</code></pre> |
156 | 13 | Christoph Kappel | |
157 | 13 | Christoph Kappel | h3. Grabs |
158 | 13 | Christoph Kappel | |
159 | 13 | Christoph Kappel | Since r2608 [[sublets]] can provide [[grabs]] identified by symbols, that can be used in the main config. |
160 | 13 | Christoph Kappel | |
161 | 13 | Christoph Kappel | <pre><code class="ruby"> |
162 | 13 | Christoph Kappel | configure :grabby do |s| |
163 | 13 | Christoph Kappel | s.interval = 5 |
164 | 13 | Christoph Kappel | end |
165 | 13 | Christoph Kappel | |
166 | 13 | Christoph Kappel | grab :GrabbyGrab do |s, c| |
167 | 13 | Christoph Kappel | puts "sublet name: %s" % [ s.name ] |
168 | 13 | Christoph Kappel | puts "pressed on : %s" % [ c.name ] |
169 | 13 | Christoph Kappel | end |
170 | 13 | Christoph Kappel | </code></pre> |
171 | 13 | Christoph Kappel | |
172 | 13 | Christoph Kappel | _Please use reasonable names for the [[grabs]], there is no check or enforcement of names._ |
173 | 13 | Christoph Kappel | |
174 | 13 | Christoph Kappel | h2. Configuration |
175 | 13 | Christoph Kappel | |
176 | 13 | Christoph Kappel | Configuration of a [[sublets|sublet]] can be done (since r2148) "Subtle::Subtle#config":http://api.subforge.org/classes/Subtle/Sublet.html#M000244, this returns a "hash":http://ruby-doc.org/core/classes/Hash.html and can be used as in the following example: |
177 | 13 | Christoph Kappel | |
178 | 13 | Christoph Kappel | <pre><code class="ruby"># Config: |
179 | 13 | Christoph Kappel | sublet :configured do |
180 | 13 | Christoph Kappel | interval 50 |
181 | 13 | Christoph Kappel | some_string "#00ff00" |
182 | 13 | Christoph Kappel | end |
183 | 13 | Christoph Kappel | |
184 | 13 | Christoph Kappel | # Sublet: |
185 | 13 | Christoph Kappel | configure :configured do |s| |
186 | 13 | Christoph Kappel | s.interval = s.config[:interval] || 30 |
187 | 13 | Christoph Kappel | s.some_string = s.config[:some_string] || "default" |
188 | 13 | Christoph Kappel | |
189 | 13 | Christoph Kappel | # Works with colors too |
190 | 13 | Christoph Kappel | s.red = Subtlext::Color.new(s.config[:red] || "#ff0000") |
191 | 13 | Christoph Kappel | end |
192 | 13 | Christoph Kappel | |
193 | 13 | Christoph Kappel | on :run do |s| |
194 | 13 | Christoph Kappel | s.data = s.red + s.some_string |
195 | 13 | Christoph Kappel | end</code></pre> |
196 | 13 | Christoph Kappel | |
197 | 13 | Christoph Kappel | The [[specification]] contains an info field that can be displayed via @sur config sublet@, it's basically an "array":http://ruby-doc.org/core/classes/Array.html of "hashes":http://ruby-doc.org/core/classes/Hash.html that contains information how to use it: |
198 | 13 | Christoph Kappel | |
199 | 13 | Christoph Kappel | <pre><code class="ruby"> |
200 | 13 | Christoph Kappel | spec.config = [ |
201 | 13 | Christoph Kappel | { :name => "format_string", :type => "string", :def_value => "%y/%m/%d %H:%M", :description => "Format of the clock (man date)" }, |
202 | 13 | Christoph Kappel | ] |
203 | 13 | Christoph Kappel | </code></pre> |
204 | 13 | Christoph Kappel | |
205 | 13 | Christoph Kappel | h2. Customization |
206 | 13 | Christoph Kappel | |
207 | 13 | Christoph Kappel | h3. Colors |
208 | 13 | Christoph Kappel | |
209 | 13 | Christoph Kappel | The color of the ouput of a [[sublet|Sublets]] can be changed in this way: |
210 | 13 | Christoph Kappel | |
211 | 13 | Christoph Kappel | <pre><code class="ruby">configure :colorful do |s| |
212 | 13 | Christoph Kappel | s.red = Subtlext::Color.new("#ff0000") |
213 | 13 | Christoph Kappel | s.green = Subtlext::Color.new("#00ff00") |
214 | 13 | Christoph Kappel | s.blue = Subtlext::Color.new("#0000ff") |
215 | 13 | Christoph Kappel | s.background = "#303030" |
216 | 13 | Christoph Kappel | end |
217 | 13 | Christoph Kappel | |
218 | 13 | Christoph Kappel | on :run do |s| |
219 | 13 | Christoph Kappel | s.data = s.red + "su" + s.green + "bt" + s.blue + "le" |
220 | 13 | Christoph Kappel | end</code></pre> |
221 | 13 | Christoph Kappel | |
222 | 13 | Christoph Kappel | h3. Icons |
223 | 13 | Christoph Kappel | |
224 | 13 | Christoph Kappel | There is also a way to add a "X bitmap":http://en.wikipedia.org/wiki/XBM to a [[Sublets|sublet]]: |
225 | 13 | Christoph Kappel | |
226 | 13 | Christoph Kappel | <pre><code class="ruby">configure :iconized do |s| |
227 | 13 | Christoph Kappel | s.icon = Subtlext::Icon.new("/usr/share/icons/subtle.xbm") |
228 | 13 | Christoph Kappel | end |
229 | 13 | Christoph Kappel | |
230 | 13 | Christoph Kappel | on :run do |s| |
231 | 13 | Christoph Kappel | s.data = @icon + "subtle" |
232 | 13 | Christoph Kappel | end</code></pre> |
233 | 13 | Christoph Kappel | |
234 | 13 | Christoph Kappel | A nice collection of this pixmap can be found "here":http://subforge.org/attachments/download/43/icons.xz |
235 | 13 | Christoph Kappel | |
236 | 13 | Christoph Kappel | _[[Subtle]] will add a padding of 3px left and right of the pixmap, so keep that in mind when using the click hooks._ |
237 | 13 | Christoph Kappel | |
238 | 13 | Christoph Kappel | h2. Examples |
239 | 13 | Christoph Kappel | |
240 | 13 | Christoph Kappel | Below is the code of a shipped [[sublet|sublets]] that displays the time. It should be really straight forward: |
241 | 13 | Christoph Kappel | |
242 | 13 | Christoph Kappel | <pre><code class="ruby">configure :clock do |s| |
243 | 13 | Christoph Kappel | s.interval = 60 #< Set interval time |
244 | 13 | Christoph Kappel | end |
245 | 13 | Christoph Kappel | |
246 | 13 | Christoph Kappel | on :run do |s| |
247 | 13 | Christoph Kappel | s.data = Time.now.strftime("%d%m%y%H%M") #< Set data |
248 | 13 | Christoph Kappel | end</code></pre> |
249 | 13 | Christoph Kappel | |
250 | 13 | Christoph Kappel | Another example for the "inotify":http://en.wikipedia.org/wiki/Inotify [[sublets|sublet]] which is also included within [[subtle]]: |
251 | 13 | Christoph Kappel | |
252 | 13 | Christoph Kappel | <pre><code class="ruby">configure :notify do |s| |
253 | 13 | Christoph Kappel | s.file = "/tmp/watch" |
254 | 13 | Christoph Kappel | s.watch(s.file) |
255 | 13 | Christoph Kappel | end |
256 | 13 | Christoph Kappel | |
257 | 13 | Christoph Kappel | on :watch do |
258 | 13 | Christoph Kappel | begin |
259 | 13 | Christoph Kappel | self.data = IO.readlines(@file).first.chop #< Read data and strip |
260 | 13 | Christoph Kappel | rescue => err #< Catch error |
261 | 13 | Christoph Kappel | puts err |
262 | 13 | Christoph Kappel | self.data = "subtle" |
263 | 13 | Christoph Kappel | end |
264 | 13 | Christoph Kappel | end</code></pre> |
265 | 13 | Christoph Kappel | |
266 | 13 | Christoph Kappel | The *watch* command also works with "Ruby":http://www.ruby-lang.org sockets, but be aware of blocking I/O: |
267 | 13 | Christoph Kappel | |
268 | 13 | Christoph Kappel | <pre><code class="ruby">configure :socket do |s| |
269 | 13 | Christoph Kappel | s.socket = TCPSocket.open("localhost", 6600) |
270 | 13 | Christoph Kappel | s.watch(s.socket) |
271 | 13 | Christoph Kappel | end |
272 | 13 | Christoph Kappel | |
273 | 13 | Christoph Kappel | on :watch do |s| |
274 | 13 | Christoph Kappel | begin |
275 | 13 | Christoph Kappel | s.data = s.socket.readline.chop #< Read data and strip |
276 | 13 | Christoph Kappel | rescue => err #< Catch error |
277 | 13 | Christoph Kappel | puts err |
278 | 13 | Christoph Kappel | s.data = "subtle" |
279 | 13 | Christoph Kappel | end |
280 | 13 | Christoph Kappel | end |
281 | 13 | Christoph Kappel | |
282 | 13 | Christoph Kappel | on :run do |s| |
283 | 13 | Christoph Kappel | # Do nothing |
284 | 13 | Christoph Kappel | end</code></pre> |
285 | 13 | Christoph Kappel | |
286 | 13 | Christoph Kappel | _[[Subtle]] will automatically set sockets to O_NONBLOCK._ |
287 | 13 | Christoph Kappel | |
288 | 13 | Christoph Kappel | And finally an example with a click callback: |
289 | 13 | Christoph Kappel | |
290 | 13 | Christoph Kappel | <pre><code class="ruby">configure :click do |s| |
291 | 13 | Christoph Kappel | s.interval = 999 #< Do nothing |
292 | 13 | Christoph Kappel | end |
293 | 13 | Christoph Kappel | |
294 | 13 | Christoph Kappel | on :mouse_down do |s, x, y, button| |
295 | 13 | Christoph Kappel | Subtlext::Client["xterm"].raise |
296 | 13 | Christoph Kappel | puts x, y, button |
297 | 13 | Christoph Kappel | end |
298 | 13 | Christoph Kappel | |
299 | 13 | Christoph Kappel | on :run do |s| |
300 | 13 | Christoph Kappel | # Do nothing here |
301 | 13 | Christoph Kappel | end</code></pre> |