Skip to main content
Version: Next

Data Sources

Now that you feel sufficiently greeted by your bar, you may realize that showing data like the time and date might be even more useful than having a button that greets you.

To implement dynamic content in your widgets, you make use of variables.

Nbcl Variables

In Nbcl, all variables are dynamically typed bindings to values. You can define variables using let or const keyword, pass them as function parameters.

Basic variables (let and const)

let foo = "value"
const bar = "value"

This is the simplest type of variable. These variables exist only during evaluation and changes to it does not effect the rendered widgets, if you need a dynamic variable that if updated, updates the widget, you can use built in Poll and Listen components to register dynamic values which we will talk about in the following section.

Dynamic Global Signals

Just having nbcl variables that wont update is pretty limiting. So, ewwii has two built in components to register dynamic signals that can change according to the command. These variables are global, which means that it is available in all modules.

Polling Signal (Poll)

Poll "var_name" {
  initial = "initial value"
  interval = "2s"
  cmd = "date +%H:%M:%S"
}

A polling signal is a signal which runs a provided shell-script repeatedly, in a given interval.

This may be the most commonly used type of signal. They are useful to access any quickly retrieved value repeatedly, and thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather, and battery level.

To externally update a polling signal, ewwii update can be used like with basic variables to assign a value. Learn more about ewwii update.

tip

When a graph widget is driven by a polling signal, set skip_unchanged to false to ensure continuous updates.

Listening Signal (Listen)

Listen "foo" {
  initial = "whatever"
  cmd = "tail -F /tmp/some_file"
}

Listening signals might be the most confusing of the bunch. A listening signal runs a script once, and reads its output continously. Whenever the script outputs a new line, the value will be updated to that new line. In the example given above, the value of foo will start out as "whatever", and will change whenever a new line is appended to /tmp/some_file.

These are particularly useful when you want to apply changes instantaneously when an operation happens if you have a script that can monitor some value on its own. Volume, brightness, workspaces that get added/removed at runtime, monitoring currently focused desktop/tag, etc. are the most common usecases of this type of signals. These are particularly efficient and should be preferred if possible.

For example, the command xprop -spy -root _NET_CURRENT_DESKTOP writes the currently focused desktop whenever it changes. Another example usecase is monitoring the currently playing song with playerctl: playerctl --follow metadata --format {{title}}.

Using these signals

Since these are global signals, they can be used everywhere in the configuration using the global function.

Poll "time" {
    initial = "initial value"
    interval = "2s"
    cmd = "date +%H:%M:%S"
}

Window "1" {
    CustomWidget {}
}

component CustomWidget (any: props) {
    Box {
        Label {
            # Using the value directly
            text = global("time")
        }
    }
}

Values of globals can also be mutated temporarily for if you pair it with the mutate function. It takes a closure as a parameter, and inside it, you can work with the current value of the globals and return a new value that is set as the widget's property.

Label {
    text = global("time").mutate(|val| {
        # val is the current value of "time"

        # make sure to return a value to set
        return "new_val"
    })
}

Another thing that is similar to mutate, but simpler, is the template function. You can set a template on how value should be set.

Label {
    text = global("time").template("Time: {time}")

    # You can refer to other globals too:
    # text = global("time").template("Time: {time} {other_time}")
}

You can do arithmetic and if/else inside template too:

# arithmetic
template("{foo + 32 + ' baz'}")

# if/else
template("{foo == 10 ? 'hi' : 'bye'}")

Dynamic Script Signals

Script is a special signal which allows running an nbcl script periodically or on event. It is a very powerful feature which allows mutating a widgets at runtime.

Script {
    # 'every' accepts a duration
    every = "5s"

    # 'on' accepts a command.
    # It listens to the command
    # and runs the script when the command
    # prints a message to stdout.
    #
    # on = "path/to/script"

    run = |w| {
        let my_widget = w.find("my_widget")
        my_widget.set_property("label", "hi")
        my_widget.add_class("red")
        my_widget.remove_class("red")
    }
}

The script requires either every or on. But not both at the same time. That is something to keep in mind. Inside the run lambda, you receive a special type on which you can call the shown functions on.

find function

Finds a widget which you can set property of or mutate class. A widget can be given a name like so:

Label "my_widget" {
    text = "bye"
}

or like so (though the first method is preferred):

Label {
    widget_name = "my_widget"
    text = "bye"
}

set_property function

Set's a property of the widget found.

add_class & remove_class function

Adds/Removes a class from the widget found.

Common Misconceptions

There is a common misconception that ewwii config is revaluated every time a dynamic global signal updates. Ewwii configs are only evaluated once and a tree is generated out of it. Ewwii then works purely on this tree with no context to anything happening in the config.

So if someone were to try to dynamically inject an widget into the config, it won't work without a reload because of the said reason. Same is true for trying to update global signals.

If you want the post render dyanmic reactivity, see advanced and advanced commands.