Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new cairo module #497

Merged
merged 4 commits into from
Apr 17, 2024
Merged

feat: new cairo module #497

merged 4 commits into from
Apr 17, 2024

Conversation

JakeStanger
Copy link
Owner

@JakeStanger JakeStanger commented Mar 14, 2024

This is an early working draft of the Cairo module, which allows you to write custom Lua scripts to draw whatever you like to a region of the bar. It's pretty rough around the edges but just about good enough to start playing with.

Currently this cannot be used in popups, however that will change fairly soon once #131 is resolved (that's been brought forward specifically because of this).

You need LuaJIT installed along with the LGI package (lua-lgi on Arch). This needs to be installed to the LUA_PATH and LUA_CPATH respectively. If installing system-wide, this should Just Werk as it's in /usr.

I have played around with the idea of vendoring but suspect I will need to write bindings to statically link lua-lgi and its scripts into the Ironbar binary, so unless anybody has any better ideas we'll roll with it like this for now.

Example usage

In your config you can define eg:

let {
  $cairo = { type = "cairo" path = "$config_dir/clock.lua" frequency = 50 width = 300 height = 300 }
} in {
  center = [ $cairo ]
}

Where:

  • path - path to Lua script
  • interval number of milliseconds between updates
  • width - canvas width
  • height - canvas height

GTK's sizing rules mean if the canvas height (or width for vertical bars) is less than the bar height, it will automatically expand to the bar height.

At the specified path, define a Lua script. The script must contain a function called draw, which takes the Cairo context as a parameter. Other than that, you have free reign.

You can optionally create an init.lua file in your config directory, which is executed once on bar startup. This can house any required initialization code, as well as shared functions.

The most basic example, which draws a red square, can be shown below:

function draw(cr) 
    cr:set_source_rgb(1.0, 0.0, 0.0)
    cr:paint()
end

A more complex example is included on the wiki page.

OLD Currently there are some hard rules you need to keep in mind:
  • The script can contain a single function only. This does not need a name.
  • The function takes a single argument, which is a pointer to the Cairo context. You must pass this to the context constructor using eg local cr = cairo.Context(ptr) to get a usable object.
  • The function must return an unsigned integer. This is not used, so you can return 0.
  • The cairo global is available everywhere. This gives full access to the Cairo library, although some constructors may not work yet.

I've included a proof-of-concept example script below, which results in a kinda nifty clock I wrote for Conky about 5 years ago.

OLD Lua script
function(ptr)
    local cr = cairo.Context(ptr)

    local center_x = 150
    local center_y = 150
    local radius = 130

    local date_table = os.date("*t")

    local hours = date_table["hour"]
    local minutes = date_table["min"]
    local seconds = date_table["sec"]

    local ms = tostring(io.popen('date +%s%3N'):read('a')):sub(-4, 9999)
    ms = tonumber(ms) / 1000

    local label_seconds = seconds
    seconds = seconds + ms

    local hours_str = tostring(hours)
    if string.len(hours_str) == 1 then
        hours_str = "0" .. hours_str
    end

    local minutes_str = tostring(minutes)
    if string.len(minutes_str) == 1 then
        minutes_str = "0" .. minutes_str
    end

    local seconds_str = tostring(label_seconds)
    if string.len(seconds_str) == 1 then
        seconds_str = "0" .. seconds_str
    end

    local font_size = radius / 5.5

    cr:set_source_rgb(1.0, 1.0, 1.0)

    cr:move_to(center_x - font_size * 2.5 + 10, center_y + font_size / 2.5)
    cr:set_font_size(font_size)
    cr:show_text(hours_str .. ':' .. minutes_str .. ':' .. seconds_str)
    cr:stroke()

    if hours > 12 then
        hours = hours - 12
    end

    local line_width = radius / 8
    local start_angle = -math.pi / 2

    local end_angle = start_angle + ((hours + minutes / 60 + seconds / 3600) / 12) * 2 * math.pi
    cr:set_line_width(line_width)
    cr:arc(center_x, center_y, radius, start_angle, end_angle)
    cr:stroke()

    end_angle = start_angle + ((minutes + seconds / 60) / 60) * 2 * math.pi
    cr:set_line_width(line_width)
    cr:arc(center_x, center_y, radius * 0.8, start_angle, end_angle)
    cr:stroke()

    if seconds == 0 then
        seconds = 60
    end

    end_angle = start_angle + (seconds / 60) * 2 * math.pi
    cr:set_line_width(line_width)
    cr:arc(center_x, center_y, radius * 0.6, start_angle, end_angle)
    cr:stroke()

    return 0
end

image

I want to try and polish this up and sort most of those gotchas out before this gets merged. Any and all help/ideas welcome.

Resolves #105

@JakeStanger JakeStanger force-pushed the feat/cairo branch 4 times, most recently from b039e12 to 799a0b7 Compare March 30, 2024 23:45
@JakeStanger
Copy link
Owner Author

@yavko this requires luajitPackages.lgi as runtime dependency. Ideally this should be treated as an optional dep as you only need it if you use this module. Do you think you'd be able to take a look at that please on the nix side?

@JakeStanger JakeStanger marked this pull request as ready for review March 31, 2024 00:29
@yavko
Copy link
Contributor

yavko commented Mar 31, 2024

@yavko this requires luajitPackages.lgi as runtime dependency. Ideally this should be treated as an optional dep as you only need it if you use this module. Do you think you'd be able to take a look at that please on the nix side?

i think the best way to do this would be detecting if the feature flag is enabled, so like this

deps = [ "old deps" ] ++ (if (builtins.elem "cairo" features) || (features == []) then [lgi_or_whatever] else [])

features is a param to the package, so it should work there, and you'll have to add lgi to the params, then just refrence from there. Also i think deps should be runtime deps

@JakeStanger
Copy link
Owner Author

JakeStanger commented Mar 31, 2024

From my (admittedly very limited) nix understanding I think that gives me what I need to do the if/else, but it's otherwise not correct.

Effectively these env vars need to be set at runtime as follows:

LUA_PATH = "${luajitPackages.lgi}/share/lua/5.1/?.lua;${luajitPackages.lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/?.lua;${luajit}/share/lua/5.1/?/init.lua";
LUA_CPATH = "${luajitPackages.lgi}/lib/lua/5.1/?.so;${
luajit}/lib/lua/5.1/?.so";

Problem is that's where my knowledge ends. Are you able to pick this up please?

@yavko
Copy link
Contributor

yavko commented Mar 31, 2024

From my (admittedly very limited) nix understanding I think that gives me what I need to do the if/else, but it's otherwise not correct.

Effectively these env vars need to be set at runtime as follows:

LUA_PATH = "${luajitPackages.lgi}/share/lua/5.1/?.lua;${luajitPackages.lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/?.lua;${luajit}/share/lua/5.1/?/init.lua";
LUA_CPATH = "${luajitPackages.lgi}/lib/lua/5.1/?.so;${
luajit}/lib/lua/5.1/?.so";

Problem is that's where my knowledge ends. Are you able to pick this up please?

Yeah sure

@JakeStanger JakeStanger force-pushed the feat/cairo branch 2 times, most recently from aad2079 to c49e75d Compare April 2, 2024 19:09
@JakeStanger JakeStanger force-pushed the feat/cairo branch 5 times, most recently from e5cc70e to 7f6c04c Compare April 11, 2024 22:33
@JakeStanger
Copy link
Owner Author

JakeStanger commented Apr 11, 2024

I've managed to get LGI loading in Nix, but it's behaving differently there for some reason.

There's a bug with the LGI library where you cannot pass a pointer to the Cairo Context constructor by default. That's fixed in an init script that just blanks it, allowing LGI to inject its magic:

cairo.Context.create = nil

Note that the init script is inlined as a string into the binary, so there's no IO involved.

On Arch, this works fine. On Nix, it seems to do nothing, and you get hit with this:

2024-04-11T22:10:14.202394Z ERROR ironbar::modules::cairo: 145: runtime error: [string "src/modules/cairo.rs:130:22"]:2: 
bad argument #2 to 'Context' (cairo.Surface expected, got userdata)
stack traceback:
	[C]: in ?
	[C]: in function 'Context'
	[string "src/modules/cairo.rs:130:22"]:2: in function <[string "src/modules/cairo.rs:130:22"]:1>

It makes absolutely no sense to me why an inlined piece of code would behave differently when using the same lua runtime version, same library version, and same ironbar version. I'm wondering (dreading?) if patching the LGI package itself on Nix is the answer. It's literally only a single line that needs commenting out at least.

@JakeStanger JakeStanger force-pushed the feat/cairo branch 2 times, most recently from bb64f21 to ea9ea7f Compare April 12, 2024 21:10
@JakeStanger
Copy link
Owner Author

After many hours, I have no idea why it doesn't work on Nix, but I've added a nix-specific patch that just removes the program code from LGI and it's sorted it.

@JakeStanger
Copy link
Owner Author

So: it didn't work on Nix because it didn't work anywhere. Except on my machine, where I had patched the system library without realising. Many thanks to @A-Cloud-Ninja for his help working on this with me in the background, but we've managed to work around an LGI bug and get this working reliably everywhere.

@JakeStanger JakeStanger merged commit e6f7108 into master Apr 17, 2024
8 checks passed
@JakeStanger JakeStanger deleted the feat/cairo branch April 17, 2024 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cairo module/widget
2 participants