Blog

Accessing the /sys Interface from Lua

I use the Awesome window manager, which relies on the Lua programming language for its configuration and customization. Here I’ll describe the way I use Lua to have Awesome display some informations provided by the underlying GNU/Linux operating system.

Under GNU/Linux, many informations about the machine and its peripherals, the system, and the network may be found in the /sys filesystem. Every (pseudo-)file in that filesystem contains a specific piece of information exposed directly by the kernel.

Reading data from /sys

It’s pretty simple to access those informations in Lua with the small SysEntry class shown below. The key principle to know here is that the __index function gets called by Lua whenever one tries to access a field that does not exist in an object’s metatable (see the section on metatables and metamethods in Lua reference manual). Here, I use that function to make Lua fetch a value from a file under /sys.

SysEntry = {
    path = nil;

    new = function(class, o)
        o = o or {}
        setmetatable(o, class)
        return o
    end;

    -- Called when accessing a field
    __index = function(table, key)
        local val = nil;
        -- Read attribute value from the corresponding file in /sys
        local f = io.open("/sys/" .. table.path .. "/" .. key, "r")
        if f then
            val = f:read()
            f:close()
        end

        return val
    end;
}

We instanciate an object of that class by providing its constructor with the pathname to one of the folders in the /sys filesystem (without the heading /sys). For example, to know whether the machine is plugged to an AC adapter, we can get a SysEntry object for the /sys/class/power_supply/AC folder and check the online property:

ac_adapter = SysEntry:new({ path = "class/power_supply/AC" })
if ac_adapter.online == "1" then
    print("Running on mains")
else
    print("Running on battery")
end

Similarly we can extract information about the battery, such as its current charge:1

battery = SysEntry:new({ path = "class/power_supply/BAT0" })
capacity = battery.charge_full
current = battery.charge_now
charge = current * 100 / capacity
print(string.format("Charge remaining: %.0f %%", charge))

Other informations I get in my Awesome’s configuration include:

Writing to /sys

Some files under /sys are writable and may be used both to set a property instead of getting it. The brightness file mentionned above is one of those; writing to it allows to change the screen's brightness.

We can add a new member to the SysEntry class above to allow writing to /sys.2 The __newindex function is similar to __index, but is called whenever one tries to assign a value to a non-existing field.

local SysEntry = {
    ...
    __newindex = function(table, key, value)
        -- Write attribute value to the corresponding file in /sys
        local f = io.open("/sys/" .. table.path .. "/" .. key, "w")
        if f then
            f:write(tostring(value))
            f:close()
        end
    end;
}

We can now create a new type of helper object to control the screen’s brightness from within Awesome:

BacklightController = {

    new = function(class, o)
        if not class.__index then
            class.__index = class
        end

        o = o or {}
        setmetatable(o, class)

        o.sys = SysEntry:new({ path = "class/backlight/intel_backlight" })
        o.max = tonumber(o.sys.max_brightness)
        -- We want to increase or decrease the brightness
        -- by increments of 1/10 of the maximal brightness
        o.step = o.max // 10

        return o
    end;

    increase = function(self)
        local current = tonumber(self.sys.brightness)
        -- Do nothing if we are already at the max
        if current < self.max then
            -- Compute the new value, capping at max if needed
            local newvalue = current + self.step
            if newvalue > self.max then
                newvalue = self.max
            end
            -- Write the new value back to /sys
            self.sys.brightness = newvalue
        end
    end;

    decrease = function(self)
        local current = tonumber(self.sys.brightness)
        -- Do nothing if we are already at 10% or less
        if current > self.step then
            local newvalue = current - self.step
            if newvalue < self.step then
                newvalue = self.step
            end
            self.sys.brightness = newvalue
        end
    end;
}

We can now instanciate a BacklightController object in Awesome’s rc.lua and bind the increase and decrease methods to, say, the XF86MonBrightnessUp and XF86MonBrightnessDown keys.

Why not simply calling xbacklight instead, which has the advantage of not requiring to make /sys/class/backlight/intel_backlight/brightness writable by a normal user account? Well, on my system xbacklight is not working, because the modesetting driver for X.org does not properly expose the backlight property.

  1. Lua’s automatic coercion of strings to numbers comes in handy here.
  2. Beware that most if not all writable files in /sys are only writable by a super-user. It’s up to you to make the files corresponding to the properties you are interested in modifying writable by your normal user account.