5. Scripting
Keywords: notionconf

Subsections

- 5.1 Protected mode and (un-)safe functions
- 5.2 Hooks
- 5.3 Referring to regions
  - 5.3.1 Direct object references
  - 5.3.2 Name-based lookups
- 5.4 Alternative winprop selection criteria
- 5.5 Writing `ion-statusd` monitors

#
5. Scripting

This chapter documents some additional features of the Notion configuration and scripting interface that can be used for more advanced scripting than the basic configuration explained in chapter .

## 5.1 Protected mode and (un-)safe functions

Some of Notion's functions call other functions in so-called ``protected mode''. In particular this is true for all the iterator functions mentioned in . If a function `fn` is called in protected mode, the call will only succeed if `fn` is considered ``safe'', and will otherwise fail with the message ``Ignoring call to unsafe function `fn` in restricted mode.''.

Let us try to explain the rationale behind this behavior and the heuristic for deciding when a function can be considered safe through an example. The function `WMPlex.managed_i` iterates over all regions managed by an `mplex` and calls `iterfn` on each of these regions. If `iterfn` was able to alter the list of regions managed by `mplex`, say by adding additional regions, this iteration could easily turn into an infinite loop or lead to inconsistent behavior.

To avoid situations like this, in order to be considered safe, a function must not alter any of the internal data structures of Notion.

If you ever need to use an unsafe function in combination with an iterator, there is a simple workaround. In the example above, instead of writing `WMPlex.managed_i(mplex, iterfn)`, which fails if `iterfn` is unsafe, you can use the following code snippet to achieve the same result:

    managed_regions={}
    mplex:managed_i(function(reg)
        table.insert(managed_regions, reg) return true
    end)
    for _,reg in ipairs(managed_regions) do
        iterfn(reg)
    end

##
5.2 Hooks

Hooks are lists of functions to be called when a certain event occurs. There are two types of them; normal and ``alternative'' hooks. Normal hooks do not return anything, but alt-hooks should return a boolean indicating whether it handled its assigned task successfully. In the case that `true` is returned, remaining handlers are not called.

Hook handlers are registered by first finding the hook with `ioncore.get_hook` and then calling `WHook.add` on the (successful) result with the handler as parameter. Similarly handlers are unregistered with `WHook.remove`. For example:

    ioncore.get_hook("ioncore_snapshot_hook"):add(
        function() print("Snapshot hook called.") end
    )

In this example the hook handler has no parameters, but many hook handlers do. The types of parameters for each hook are listed in the hook reference, section .

Note that many of the hooks are called in ``protected mode'' and can not use any functions that modify Notion's internal state.

## 5.3 Referring to regions

### 5.3.1 Direct object references

All Notion objects are passed to Lua scripts as 'userdatas', and you may safely store such object references for future use. The C-side object may be destroyed while Lua still refers to the object. All exported functions gracefully fail in such a case, but if you need to explicitly test that the C-side object still exists, use `obj_exists`.

As an example, the following short piece of code implements bookmarking:

    local bookmarks={}

    -- Set bookmark bm point to the region reg
    function set_bookmark(bm, reg)
        bookmarks[bm]=reg
    end

    -- Go to bookmark bm
    function goto_bookmark(bm)
        if bookmarks[bm] then
            -- We could check that bookmarks[bm] still exists, if we
            -- wanted to avoid an error message.
            bookmarks[bm]:goto()
        end
    end

### 5.3.2 Name-based lookups

If you want to a single non-WClientWin region with an exact known name, use `ioncore.lookup_region`. If you want a list of all regions, use `ioncore.region_list`. Both functions accept an optional argument that can be used to specify that the returned region(s) must be of a more specific type. Client windows live in a different namespace and for them you should use the equivalent functions `ioncore.lookup_clientwin` and `ioncore.clientwin_list`.

To get the name of an object, use `WRegion.name`. Please be aware, that the names of client windows reflect their titles and are subject to changes. To change the name of a non-client window region, use `WRegion.set_name`.

## 5.4 Alternative winprop selection criteria

It is possible to write more complex winprop selection routines than those described in section . To match a particular winprop using whatever way you want to, just set the `match` field of the winprop to a function that receives the as its parameters the triple `(prop, cwin, id)`, where `prop` is the table for the winprop itself, `cwin` is the client window object, and `id` is the `WClientWin.get_ident` result. The function should return `true` if the winprop matches, and `false` otherwise. Note that the `match` function is only called after matching against class/role/instance.

The title of a client window can be obtained with `WRegion.name`. If you want to match against (almost) arbitrary window properties, have a look at the documentation for the following functions, and their standard Xlib counterparts: `ioncore.x_intern_atom` (XInternAtom), `ioncore.x_get_window_property` (XGetWindowProperty), and `ioncore.x_get_text_property` (XGetTextProperty).

##
5.5 Writing `ion-statusd` monitors

All statusbar meters that do not monitor the internal state of Notion should go in the separate `ion-statusd` program.

Whenever the user requests a meter ``%foo`' or ``%foo_bar`' to be inserted in a statusbar, mod_statusbar asks `ion-statusd` to load `statusd_foo.lua` on its search path (same as that for Notion-side scripts). This script should then supply all meters with the initial part ``foo`'.

To provide this value, the script should simply call `statusd.inform` with the name of the meter and the value as a string. Additionally the script should provide a 'template' for the meter to facilitate expected width calculation by mod_statusbar, and may provide a 'hint' for colour-coding the value. The interpretation of hints depends on the graphical style in use, and currently the stock styles support the ``normal`', ``important`' and ``critical`' hints.

In our example of the 'foo monitor', at script initialisation we might broadcast the template as follows:

    statusd.inform("foo_template", "000")

To inform mod_statusbar of the actual value of the meter and indicate that the value is critical if above 100, we might write the following function:

    local function inform_foo(foo)
        statusd.inform("foo", tostring(foo))
        if foo>100 then
            statusd.inform("foo_hint", "critical")
        else
            statusd.inform("foo_hint", "normal")
        end
    end

To periodically update the value of the meter, we must use timers. First we must create one:

    local foo_timer=statusd.create_timer()

Then we write a function to be called whenever the timer expires. This function must also restart the timer.

    local function update_foo()
        local foo= ... measure foo somehow ...
        inform_foo(foo)
        foo_timer:set(settings.update_interval, update_foo)
    end

Finally, at the end of our script we want to do the initial measurement, and set up timer for further measurements:

    update_foo()

If our scripts supports configurable parameters, the following code (at the beginning of the script) will allow them to be configured in cfg_statusbar.lua and passed to the status daemon and our script:

    local defaults={
        update_interval=10*1000, -- 10 seconds
    }

    local settings=table.join(statusd.get_config("foo"), defaults)