Project

General

Profile

unexist.dev

subtle

Assorted tidbits and projects

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 *&#64;* 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 *&#64;* 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
| *&#58;mouse_over* | Whenever the pointer is over the [[sublets|sublet]]         | s               |
137 13 Christoph Kappel
| *&#58;mouse_down* | Whenever the pointer is pressed on the [[sublets|sublet]].  | s, x, y, button |
138 13 Christoph Kappel
| *&#58;mouse_out*  | Whenever the pointer leaves the [[sublets|sublet]]          | s               |
139 13 Christoph Kappel
| *&#58;run*        | Whenever either the interval time is expired                | s               |
140 13 Christoph Kappel
| *&#58;watch*      | Whenever the watched file is modified/socket has data ready | s               |
141 13 Christoph Kappel
| *&#58;data*       | Whenever [[subtle]] receives a [[Hacking#SUBTLE_SUBLET_DATA|SUBTLE_SUBLET_DATA]] client message for this [[sublets|sublet]] | s |
142 13 Christoph Kappel
| *&#58;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>