Skip to content

Variable

Kevin edited this page Jun 19, 2026 · 1 revision

A Variable is a reactive container that holds a value and notifies subscribers whenever that value changes.

Variables are the foundation of state management in Astal Lua and can be used directly with Bindings to create reactive widgets.

Creating a Variable

Use Variable.new() to create a new Variable with an initial value.

local Variable = require('astal.variable')

local count = Variable.new(0)

print(count:get()) -- 0

You can also access the value through the value property.

local Variable = require('astal.variable')

local count = Variable.new(0)

print(count.value)

Updating values

Variables can be updated using :set().

local Variable = require('astal.variable')

local count = Variable.new(0)

count:set(10)

print(count:get()) -- 10

Or by assigning directly to the value property.

local Variable = require('astal.variable')

local count = Variable.new(0)

count.value = 42

Whenever the value changes, all subscribers will be notified automatically.

Subscribing to changes

Use :subscribe() to react to value updates.

local Variable = require('astal.variable')

local count = Variable.new(0)

local unsubscribe = count:subscribe(function(value)
    print('New value:', value)
end)

count:set(1)
count:set(2)

unsubscribe()

The returned function disconnects the subscription.

Using Variables with widgets

Variables are commonly used together with Bindings to create reactive user interfaces.

local astal = require('astal')
local bind = astal.bind
local Variable = require('astal.variable')
local Widget = require('astal.gtk4.widget')

local count = Variable.new(0)

return Widget.Label {
    label = bind(count):as(tostring),
}

Whenever the variable changes, the widget will update automatically.

Polling values

Variables can periodically update themselves using :poll().

The polling source can be either a Lua function or a shell command.

local Variable = require('astal.variable')

local clock = Variable
    .new(os.date('%H:%M:%S'))
    :poll(1000, function()
        return os.date('%H:%M:%S')
    end)

The callback receives the previous value and should return the next value.

local Variable = require('astal.variable')

local counter = Variable
    .new(0)
    :poll(1000, function(prev)
        return prev + 1
    end)

Polling shell commands

You can also poll the output of a command.

local Variable = require('astal.variable')

local volume = Variable
    .new('')
    :poll(1000, 'wpctl get-volume @DEFAULT_AUDIO_SINK@')

A transform function can be provided to process the command output.

local Variable = require('astal.variable')

local uptime = Variable
    .new('')
    :poll(
        5000,
        'uptime -p',
        function(out)
            return out:gsub('\n', '')
        end
    )

Watching subprocesses

Use :watch() when a command continuously produces output.

local Variable = require('astal.variable')

local workspace = Variable
    .new('')
    :watch('some-command --follow')

Whenever the process emits a new line, the variable will be updated.

A transform function can also be provided.

local Variable = require('astal.variable')

local workspace = Variable
    .new('')
    :watch(
        'some-command --follow',
        function(out)
            return out:match('%S+') or ''
        end
    )

Observing GObjects

Variables can observe GObject signals and automatically update their value.

local Variable = require('astal.variable')

local var = Variable.new('')

var:observe(
    battery,
    'notify::percentage',
    function(bat, percentage)
        return string.format('%d%%', percentage * 100)
    end
)

Regular signals are also supported.

local Variable = require('astal.variable')

local var = Variable.new(0)

var:observe(button, 'clicked', function()
    return os.time()
end)

Derived Variables

Use Variable.derive() to create a variable from one or more reactive dependencies.

local Variable = require('astal.variable')

local first = Variable.new('Hello')
local second = Variable.new('World')

local combined = Variable.derive(
    { first, second },
    function(a, b)
        return a .. ' ' .. b
    end
)

print(combined:get()) -- Hello World

Whenever any dependency changes, the derived variable is updated automatically.

first:set('Goodbye')

print(combined:get()) -- Goodbye World

Error handling

Polling and watching commands may emit errors.

Use :on_error() to handle them.

local Variable = require('astal.variable')

Variable
    .new('')
    :watch('invalid-command')
    :on_error(function(_, err)
        print('Error:', err)
    end)

Note

If no error handler is registered, errors will be printed to stdout automatically.

Cleanup

Variables may allocate resources such as polling timers, subprocesses and subscriptions.

Call :drop() when the variable is no longer needed.

local Variable = require('astal.variable')

local clock = Variable
    .new('')
    :poll(1000, 'date')

clock:drop()

Calling :drop() automatically:

  • Stops active polls
  • Stops active subprocesses
  • Disconnects derived subscriptions
  • Executes registered cleanup callbacks

Cleanup callbacks

Use :on_dropped() to register cleanup handlers.

local Variable = require('astal.variable')

local var = Variable.new(0)

var:on_dropped(function()
    print('Variable destroyed')
end)

These callbacks are executed when :drop() is called.

Clone this wiki locally