Project

General

Profile

unexist.dev

subtle

Assorted tidbits and projects

Writing sublets » History » Version 12

Anonymous, 08/02/2011 03:50 PM

1 12
h1. Writing sublets\015\012\015\012{{>toc}}\015\012\015\012h2. Hello world\015\012\015\012Starting a [[sublets|sublet]] from scratch is really easy, just create an empty [[sublets|sublet]] with [[sur]]: @sur template hello@\015\012\015\012This will create a folder *hello* with two files:\015\012\015\012|_. Filename    |_. Description                                                                              |\015\012| *hello.rb*    | The [[sublets|sublet]] file with the minimal required code                                 |\015\012| *hello.spec*  | The [[specification]] file with basic information for [[sur]] about the [[sublets|sublet]] |\015\012\015\012h3. Say hello\015\012\015\012Per default, the new [[sublets|sublet]] will do nothing and we need to add something do the data field:\015\012\015\012<pre>{{hide}}<code class="ruby"># Hello sublet file\015\012# Created with sur-0.2\015\012configure :hello do |s|\015\012  s.interval = 60\015\012end\015\012\015\012on :run do |s|\015\012  s.data = "Hello, world!"\015\012end</code></pre>\015\012\015\012h3. Using variables\015\012\015\012Typically, 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.\015\012\015\012<pre>{{hide}}<code class="ruby"># Hello sublet file\015\012# Created with sur-0.2\015\012configure :hello do |s|\015\012  s.interval = 60\015\012  s.world    = "variable world"\015\012end\015\012\015\012on :run do |s|\015\012  s.data = "Hello, #{s.world}"\015\012end</code></pre>\015\012\015\012h3. Move code to helper\015\012\015\012Sometimes 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.\015\012\015\012<pre>{{hide}}<code class="ruby"># Hello sublet file\015\012# Created with sur-0.2\015\012configure :hello do |s|\015\012  s.interval = 60\015\012  s.world    = "variable world"\015\012end\015\012\015\012helper do |s|\015\012  def hello_time\015\012    case Time.now.strftime("%H").to_i\015\012      when  6..9  then "Good morning"\015\012      when 10..15 then "Good day"\015\012      when 16..18 then "Good evening"\015\012      when 19..5  then "Good night"\015\012    end\015\012  end\015\012end\015\012\015\012on :run do |s|\015\012  s.data = "%s, %s" % [ s.hello_time, s.world ]\015\012end</code></pre>\015\012\015\012h3. Test your sublet\015\012\015\012[[sur]] comes with a tester for [[sublets|sublet]] to ease the whole thing: @sur test hello/hello.rb\015\012\015\012The output looks like this:\015\012\015\012<pre>{{hide}}<code class="bash">------------------\015\012 hello\015\012------------------\015\012 @visible = true\015\012 @interval = 60\015\012 @config = {}\015\012 @world = "variable world"\015\012 @data = Hello, variable world!\015\012------------------\015\012 (1) run\015\012 (2) hello_time (helper)\015\012 (0) Quit\015\012------------------\015\012>>> Select one method:\015\012>>> \015\012</code></pre>\015\012\015\012The lines starting with *&#64;* show the defined instanced variables, the lines with *(1)* the callable methods.\015\012\015\012h3. Install and submit\015\012\015\012Once you are satisfied with your work you need to build your [[sublets|sublet]]: @sur build hello/hello.spec@\015\012\015\012This 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.\015\012\015\012h2. Components\015\012\015\012h3. Configure\015\012\015\012A [[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.\015\012\015\012<pre>{{hide}}<code class="ruby">configure :sublet do |s|\015\012  s.interval = 60\015\012  s.variable = "something"\015\012end</code></pre>\015\012\015\012Generally there are two different types of [[Sublets]]:\015\012\015\012* [[sublets]] that are updated by given interval in seconds (default 60s)\015\012* [[sublets]] that are updated when a file is modified (via "inotify":http://en.wikipedia.org/wiki/Inotify) or via socket\015\012* [[sublets]] that are updated via [[Hacking#SUBTLE_SUBLET_DATA|SUBTLE_SUBLET_DATA]] client message\015\012\015\012The [[sublets|sublet]] data *must* be of type "String":http://www.ruby-doc.org/core/classes/String.html, everything else will be ignored.\015\012\015\012h3. Helper\015\012\015\012Custom 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:\015\012\015\012<pre>{{hide}}<code class="ruby">helper do\015\012  def something(test)\015\012    self.variable = test\015\012    self.data     = test\015\012  end\015\012end</code></pre>\015\012\015\012h3. Events\015\012\015\012[[Sublets]] are event driven, so there are some specific events only for [[sublets]]:\015\012\015\012|_. Name            |_. Description                                               |_. Arguments     |\015\012| *&#58;mouse_over* | Whenever the pointer is over the [[sublets|sublet]]         | s               |\015\012| *&#58;mouse_down* | Whenever the pointer is pressed on the [[sublets|sublet]].  | s, x, y, button |\015\012| *&#58;mouse_out*  | Whenever the pointer leaves the [[sublets|sublet]]          | s               |\015\012| *&#58;run*        | Whenever either the interval time is expired                | s               |\015\012| *&#58;watch*      | Whenever the watched file is modified/socket has data ready | s               |\015\012| *&#58;data*       | Whenever [[subtle]] receives a [[Hacking#SUBTLE_SUBLET_DATA|SUBTLE_SUBLET_DATA]] client message for this [[sublets|sublet]] | s |\015\012| *&#58;unload*     | Whenever the [[sublets|sublet]] is unloaded                 | s               |\015\012\015\012<pre>{{hide}}<code class="ruby">on :mouse_down do |s, x, y, button|\015\012  puts "button %d at x=%d and y=%d" % [ x, y, button ]\015\012end</code></pre>\015\012\015\012h3. Hooks\015\012\015\012[[Sublets]] can use all [[hooks]] that are useable in the main config too, the syntax is almost the same:\015\012\015\012<pre>{{hide}}<code class="ruby">on :client_create do |s, c|\015\012  puts s.name #< Name of the sublet\015\012  puts c.name #< Name of the client\015\012end</code></pre>\015\012\015\012h3. Grabs\015\012\015\012Since r2608 [[sublets]] can provide [[grabs]] identified by symbols, that can be used in the main config.\015\012\015\012<pre>{{hide}}<code class="ruby">\015\012configure :grabby do |s|\015\012  s.interval = 5\015\012end\015\012\015\012grab :GrabbyGrab do |s, c|\015\012  puts "sublet name: %s" % [ s.name ]\015\012  puts "pressed on : %s" % [ c.name ]\015\012end\015\012</code></pre>\015\012\015\012_Please use reasonable names for the [[grabs]], there is no check or enforcement of names._\015\012\015\012h2. Configuration\015\012\015\012Configuration 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:\015\012\015\012<pre>{{hide}}<code class="ruby"># Config:\015\012sublet :configured do\015\012  interval    50\015\012  some_string "#00ff00"\015\012end\015\012\015\012# Sublet:\015\012configure :configured do |s|\015\012  s.interval    = s.config[:interval]    || 30\015\012  s.some_string = s.config[:some_string] || "default"\015\012\015\012  # Works with colors too\015\012  s.red = Subtlext::Color.new(s.config[:red] || "#ff0000")\015\012end\015\012\015\012on :run do |s|\015\012  s.data = s.red + s.some_string\015\012end</code></pre>\015\012\015\012The [[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:\015\012\015\012<pre>{{hide}}<code class="ruby">\015\012spec.config = [\015\012  { :name => "format_string", :type => "string", :def_value => "%y/%m/%d %H:%M", :description => "Format of the clock (man date)" },\015\012]\015\012</code></pre>\015\012\015\012h2. Customization\015\012\015\012h3. Colors\015\012\015\012The color of the ouput of a [[sublet|Sublets]] can be changed in this way:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :colorful do |s|\015\012  s.red        = Subtlext::Color.new("#ff0000")\015\012  s.green      = Subtlext::Color.new("#00ff00")\015\012  s.blue       = Subtlext::Color.new("#0000ff")\015\012  s.background = "#303030"\015\012end\015\012\015\012on :run do |s|\015\012  s.data = s.red + "su" + s.green + "bt" + s.blue + "le"\015\012end</code></pre>\015\012\015\012h3. Icons\015\012\015\012There is also a way to add a "X bitmap":http://en.wikipedia.org/wiki/XBM to a [[Sublets|sublet]]:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :iconized do |s|\015\012  s.icon = Subtlext::Icon.new("/usr/share/icons/subtle.xbm")\015\012end\015\012\015\012on :run do |s|\015\012  s.data = @icon + "subtle"\015\012end</code></pre>\015\012\015\012A nice collection of this pixmap can be found "here":http://subforge.org/attachments/download/43/icons.xz\015\012\015\012_[[Subtle]] will add a padding of 3px left and right of the pixmap, so keep that in mind when using the click hooks._\015\012\015\012h2. Examples\015\012\015\012Below is the code of a shipped [[sublet|sublets]] that displays the time. It should be really straight forward:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :clock do |s|\015\012  s.interval = 60 #< Set interval time\015\012end\015\012\015\012on :run do |s|\015\012  s.data = Time.now.strftime("%d%m%y%H%M") #< Set data\015\012end</code></pre>\015\012\015\012Another example for the "inotify":http://en.wikipedia.org/wiki/Inotify [[sublets|sublet]] which is also included within [[subtle]]:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :notify do |s|\015\012  s.file = "/tmp/watch"\015\012  s.watch(s.file)\015\012end\015\012\015\012on :watch do\015\012  begin\015\012    self.data = IO.readlines(@file).first.chop #< Read data and strip\015\012  rescue => err #< Catch error\015\012    puts err\015\012    self.data = "subtle"\015\012  end\015\012end</code></pre>\015\012\015\012The *watch* command also works with "Ruby":http://www.ruby-lang.org sockets, but be aware of blocking I/O:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :socket do |s|\015\012  s.socket = TCPSocket.open("localhost", 6600)\015\012  s.watch(s.socket)\015\012end\015\012\015\012on :watch do |s|\015\012  begin\015\012    s.data = s.socket.readline.chop #< Read data and strip\015\012  rescue => err #< Catch error\015\012    puts err\015\012    s.data = "subtle"\015\012  end\015\012end\015\012\015\012on :run do |s|\015\012  #  Do nothing\015\012end</code></pre>\015\012\015\012_[[Subtle]] will automatically set sockets to O_NONBLOCK._\015\012\015\012And finally an example with a click callback:\015\012\015\012<pre>{{hide}}<code class="ruby">configure :click do |s|\015\012  s.interval = 999 #< Do nothing\015\012end\015\012\015\012on :mouse_down do |s, x, y, button|\015\012  Subtlext::Client["xterm"].raise\015\012  puts x, y, button\015\012end\015\012\015\012on :run do |s|\015\012  # Do nothing here\015\012end</code></pre>