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> |