[GH-ISSUE #105] Cairo module/widget #7086

Closed
opened 2026-05-23 02:52:25 +01:00 by JakeStanger · 21 comments
Owner

Originally created by @JakeStanger on GitHub (Apr 10, 2023).
Original GitHub issue: https://github.com/JakeStanger/ironbar/issues/105

Is your feature request related to a problem? Please describe.
You are currently limited to the available widgets, which imposes some restriction on the possible shapes and content that can be displayed.

Describe the solution you'd like
GTK allows you to draw to widgets directly using Cairo. Some form of wrapper around this (perhaps via Lua) would allow full scripting of custom content.

Describe alternatives you've considered
None yet.

Additional context
Originally discussed in https://github.com/JakeStanger/ironbar/issues/68#issuecomment-1493427388
Example implementation in eww: https://github.com/elkowar/eww/pull/328/files

Originally created by @JakeStanger on GitHub (Apr 10, 2023). Original GitHub issue: https://github.com/JakeStanger/ironbar/issues/105 **Is your feature request related to a problem? Please describe.** You are currently limited to the available widgets, which imposes some restriction on the possible shapes and content that can be displayed. **Describe the solution you'd like** GTK allows you to draw to widgets directly using Cairo. Some form of wrapper around this (perhaps via Lua) would allow full scripting of custom content. **Describe alternatives you've considered** None yet. **Additional context** Originally discussed in https://github.com/JakeStanger/ironbar/issues/68#issuecomment-1493427388 Example implementation in eww: https://github.com/elkowar/eww/pull/328/files
Author
Owner

@yavko commented on GitHub (Apr 12, 2023):

WASM could also work. I also wrote up this insanity below, that could keep everything declarative, but honestly, maybe it's too much, and would be difficult to create an embedded DSL as I came up with below.

{
	"steps": [
		"   Centering  ",
		{ "type": "translate", "dx": "{{total_width  / 2}}", "dy": "{{total_height  / 2}}"  },
		{ "type": "rotate", "degrees": "{{ 2 * pi * ( start_at / 100 ) }}"  },
		{ "type": "translate", "dx": "{{ -1 * ( total_width  / 2 ) }}", "dy": "{{ -1 * ( total_height  / 2 ) }}"  },

		" Background Ring ",
		{ "type": "move_to", "x": "{{total_width  / 2}}", "y": "{{total_height  / 2}}"  },
		" ... "
	]
}
<!-- gh-comment-id:1504677085 --> @yavko commented on GitHub (Apr 12, 2023): WASM could also work. I also wrote up this insanity below, that could keep everything declarative, but honestly, maybe it's too much, and would be difficult to create an embedded DSL as I came up with below. ```json { "steps": [ " Centering ", { "type": "translate", "dx": "{{total_width / 2}}", "dy": "{{total_height / 2}}" }, { "type": "rotate", "degrees": "{{ 2 * pi * ( start_at / 100 ) }}" }, { "type": "translate", "dx": "{{ -1 * ( total_width / 2 ) }}", "dy": "{{ -1 * ( total_height / 2 ) }}" }, " Background Ring ", { "type": "move_to", "x": "{{total_width / 2}}", "y": "{{total_height / 2}}" }, " ... " ] } ```
Author
Owner

@JakeStanger commented on GitHub (Apr 12, 2023):

WASM is a fantastic idea! If I can figure out a way to do that without requiring too much work for the user (ie I'd rather not force people to require a whole build chain just to configure a module), I absolutely will.

The DSL I think I'd rather avoid. In part, because I think writing/maintaining a whole parser just for this would be wayyy to much work, but also in part because it adds the requirement of learning it just to be able to use it here and nowhere else, vs re-using an existing lang like Lua (or even JavaScript...) which can also be used elsewhere.

<!-- gh-comment-id:1505593161 --> @JakeStanger commented on GitHub (Apr 12, 2023): WASM is a fantastic idea! If I can figure out a way to do that without requiring too much work for the user (ie I'd rather not force people to require a whole build chain just to configure a module), I absolutely will. The DSL I think I'd rather avoid. In part, because I think writing/maintaining a whole parser just for this would be wayyy to much work, but also in part because it adds the requirement of learning it just to be able to use it here and nowhere else, vs re-using an existing lang like Lua (or even [JavaScript](https://deno.com/blog/roll-your-own-javascript-runtime)...) which can also be used elsewhere.
Author
Owner

@yavko commented on GitHub (Apr 22, 2023):

The DSL I think I'd rather avoid. In part, because I think writing/maintaining a whole parser just for this would be wayyy to much work, but also in part because it adds the requirement of learning it just to be able to use it here and nowhere else, vs re-using an existing lang like Lua (or even JavaScript...) which can also be used elsewhere.

I actually found something perfect for this https://lib.rs/crates/evalexpr, since it uses syntax that every language uses it could work, I could write up a quick POC of a DSL like this in a bit

<!-- gh-comment-id:1518724231 --> @yavko commented on GitHub (Apr 22, 2023): > The DSL I think I'd rather avoid. In part, because I think writing/maintaining a whole parser just for this would be wayyy to much work, but also in part because it adds the requirement of learning it just to be able to use it here and nowhere else, vs re-using an existing lang like Lua (or even [JavaScript](https://deno.com/blog/roll-your-own-javascript-runtime)...) which can also be used elsewhere. I actually found something perfect for this https://lib.rs/crates/evalexpr, since it uses syntax that every language uses it could work, I could write up a quick POC of a DSL like this in a bit
Author
Owner

@yavko commented on GitHub (Apr 22, 2023):

Also, will this be a custom module widget, or a just a module? Or maybe both?

<!-- gh-comment-id:1518739929 --> @yavko commented on GitHub (Apr 22, 2023): Also, will this be a custom module widget, or a just a module? Or maybe both?
Author
Owner

@JakeStanger commented on GitHub (Apr 22, 2023):

Lua is still my number 1 choice at the moment, but go for it, I'd be interested to see what you're thinking of and how it would work.

Also, will this be a custom module widget, or a just a module? Or maybe both?

TBC, but hopefully both. The long term goal is that at least some of the simpler modules will be shared between both without duplication.

<!-- gh-comment-id:1518771293 --> @JakeStanger commented on GitHub (Apr 22, 2023): Lua is still my number 1 choice at the moment, but go for it, I'd be interested to see what you're thinking of and how it would work. > Also, will this be a custom module widget, or a just a module? Or maybe both? TBC, but hopefully both. The long term goal is that at least some of the simpler modules will be shared between both without duplication.
Author
Owner

@yavko commented on GitHub (Apr 24, 2023):

I'd be interested to see what you're thinking of and how it would work.

I have made something, tho incomplete, It's able to almost recreate the eww circular progress, (except for child widgets, since I didn't make it in GTK), here is the code in case you want to see how it's done https://gist.github.com/yavko/2f5a74020ccbeadd08c5a0d6b63136b3

The types could maybe also be used to support Lua, or other runtimes

rendered example (you can see my wallpaper behind bc of kitty, sicne it renders transparent stuff as not just transparent, but transparent through the whole window)
image

<!-- gh-comment-id:1519260837 --> @yavko commented on GitHub (Apr 24, 2023): > I'd be interested to see what you're thinking of and how it would work. I have made something, tho incomplete, It's able to almost recreate the eww circular progress, (except for child widgets, since I didn't make it in GTK), here is the code in case you want to see how it's done https://gist.github.com/yavko/2f5a74020ccbeadd08c5a0d6b63136b3 The types could maybe also be used to support Lua, or other runtimes > rendered example (you can see my wallpaper behind bc of kitty, sicne it renders transparent stuff as not just transparent, but transparent through the whole window) ![image](https://user-images.githubusercontent.com/15178513/233880607-bad79d0a-cffa-4500-a3c3-37e4ae268288.png)
Author
Owner

@A-Cloud-Ninja commented on GitHub (Apr 24, 2023):

Fwiw to throw this idea out there if lua is the current champ as far as existing ideas goes, a specific lua library, LGI, could be very powerful here. Here's a small sample of coercing objects in and out of C (should be plausible to work this with rust) from lua with bindings on both ends

//main.c
#include <luajit-2.1/lua.h>
#include <luajit-2.1/lualib.h>
#include <luajit-2.1/lauxlib.h>
#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    luaL_dofile(L, "main.lua");
    lua_getglobal(L, "get_window");
    lua_call(L, 0, 1);
    GtkWidget *window = lua_touserdata(L, -1);
    lua_pop(L, 1);
    gtk_widget_show_all(window);
    gtk_main();
    return 0;
}
--main.lua
local lgi = require 'lgi'
local Gtk = lgi.require('Gtk', '3.0')

local window = Gtk.Window {
   title = 'Hello to Gtk+',
   default_width = 400,
   default_height = 300,
   on_destroy = Gtk.main_quit
}
function get_window()
	return window._native
end

Note that the "release" builds of LGI are outdated and will crash on some newer libraries, but building the latest version will properly work, including gtk4{-layer-shell}, so for the forseeable future this should be future proof too.

<!-- gh-comment-id:1520908090 --> @A-Cloud-Ninja commented on GitHub (Apr 24, 2023): Fwiw to throw this idea out there if lua is the current champ as far as existing ideas goes, a specific lua library, [LGI](https://github.com/lgi-devs/lgi), could be very powerful here. Here's a small sample of coercing objects in and out of C (should be plausible to work this with rust) from lua with bindings on both ends ```c //main.c #include <luajit-2.1/lua.h> #include <luajit-2.1/lualib.h> #include <luajit-2.1/lauxlib.h> #include <gtk/gtk.h> int main(int argc, char *argv[]) { lua_State *L = luaL_newstate(); luaL_openlibs(L); luaL_dofile(L, "main.lua"); lua_getglobal(L, "get_window"); lua_call(L, 0, 1); GtkWidget *window = lua_touserdata(L, -1); lua_pop(L, 1); gtk_widget_show_all(window); gtk_main(); return 0; } ``` ```lua --main.lua local lgi = require 'lgi' local Gtk = lgi.require('Gtk', '3.0') local window = Gtk.Window { title = 'Hello to Gtk+', default_width = 400, default_height = 300, on_destroy = Gtk.main_quit } function get_window() return window._native end ``` Note that the "release" builds of LGI are outdated and will crash on some newer libraries, but building the latest version will properly work, including gtk4{-layer-shell}, so for the forseeable future this should be future proof too.
Author
Owner

@yavko commented on GitHub (Apr 24, 2023):

I personally think mlua would be better as there is no need to use a C library & ffi

<!-- gh-comment-id:1520912210 --> @yavko commented on GitHub (Apr 24, 2023): I personally think [mlua](https://crates.io/crates/mlua) would be better as there is no need to use a C library & ffi
Author
Owner

@A-Cloud-Ninja commented on GitHub (Apr 24, 2023):

I personally think mlua would be better as there is no need to use a C library & ffi

The benefit of LGI here would be the flexibility to we'll say, end users in creating essentially arbitrary widgets. All up to what the true goals here are, though.

Also, regardless of LGI or not, mlua (or another VM library impl) would have to be used anyway if lua is to be used. LGI just facilitates creation and passing of GObjects to and from both sides easier.

<!-- gh-comment-id:1520915012 --> @A-Cloud-Ninja commented on GitHub (Apr 24, 2023): > I personally think [mlua](https://crates.io/crates/mlua) would be better as there is no need to use a C library & ffi The benefit of LGI here would be the flexibility to we'll say, end users in creating essentially arbitrary widgets. All up to what the true goals here are, though. Also, regardless of LGI or not, mlua (or another VM library impl) would have to be used anyway if lua is to be used. LGI just facilitates creation and passing of GObjects to and from both sides easier.
Author
Owner

@yavko commented on GitHub (Apr 24, 2023):

That's cool and all but it doesn't have rust bindings and making rust bindings for a C library isn't fun as it requires unsafe code and idk if it's possible or easy to pass a gtk-rs struct as a raw GObject

<!-- gh-comment-id:1520919089 --> @yavko commented on GitHub (Apr 24, 2023): That's cool and all but it doesn't have rust bindings and making rust bindings for a C library isn't fun as it requires unsafe code and idk if it's possible or easy to pass a gtk-rs struct as a raw GObject
Author
Owner

@A-Cloud-Ninja commented on GitHub (Apr 24, 2023):

This shouldn't require anything extra rust-side, unless I'm mistaken. If you use Mlua targeting we'll say PUC Lua 5.1, and LGI is shipped/depended on and built for 5.1, "rust" doesn't have to know anything about LGI, as the Lua-side passes GtkWidget._native which will return a native pointer to the actual object. See Interoperability with native code.

<!-- gh-comment-id:1520920443 --> @A-Cloud-Ninja commented on GitHub (Apr 24, 2023): This shouldn't require anything extra rust-side, unless I'm mistaken. If you use Mlua targeting we'll say PUC Lua 5.1, and LGI is shipped/depended on and built for 5.1, "rust" doesn't have to know anything about LGI, as the Lua-side passes `GtkWidget._native` which will return a native pointer to the actual object. See [Interoperability with native code](https://github.com/lgi-devs/lgi/blob/master/docs/guide.md#8-interoperability-with-native-code).
Author
Owner

@JakeStanger commented on GitHub (Apr 24, 2023):

I have made something, tho incomplete, It's able to almost recreate the eww circular progress,

I've had a quick look at this and it looks very promising. I'll have a proper read through/review as soon as I can, but I reckon we can do something with it.

idk if it's possible or easy to pass a gtk-rs struct as a raw GObject

This is the make or break situation for having a scripting interface with GTK inside the bar. I can't see that it wouldn't be possible, but it depends on what rust will let me pass to lua. I'm not afraid of getting my hands dirty and playing with some unsafe code if necessary, but by the sounds of it there shouldn't be much need anyway. It's something that'll be a good few months off anyway (assuming nobody else gets there before I do :p)

<!-- gh-comment-id:1520923043 --> @JakeStanger commented on GitHub (Apr 24, 2023): > I have made something, tho incomplete, It's able to almost recreate the eww circular progress, I've had a quick look at this and it looks very promising. I'll have a proper read through/review as soon as I can, but I reckon we can do something with it. > idk if it's possible or easy to pass a gtk-rs struct as a raw GObject This is the make or break situation for having a scripting interface with GTK inside the bar. I can't see that it wouldn't be possible, but it depends on what rust will let me pass to lua. I'm not afraid of getting my hands dirty and playing with some unsafe code if necessary, but by the sounds of it there shouldn't be much need anyway. It's something that'll be a good few months off anyway (assuming nobody else gets there before I do :p)
Author
Owner

@yavko commented on GitHub (Apr 24, 2023):

This shouldn't require anything extra rust-side, unless I'm mistaken.

I'm like 94.87% sure you are, I've had to do FFI in rust before, and if you didn't wrap C libraries you'd be introducing unsafe and UB which is not the rust way

<!-- gh-comment-id:1520924063 --> @yavko commented on GitHub (Apr 24, 2023): > This shouldn't require anything extra rust-side, unless I'm mistaken. I'm like 94.87% sure you are, I've had to do FFI in rust before, and if you didn't wrap C libraries you'd be introducing unsafe and UB which is not the rust way
Author
Owner

@yavko commented on GitHub (Apr 24, 2023):

I think having Lua and json would be better as sometimes you need more power, but the way I was thinking of adding child widgets to my json impl was just by using the ironbar syntax like in custom modules

<!-- gh-comment-id:1520927571 --> @yavko commented on GitHub (Apr 24, 2023): I think having Lua and json would be better as sometimes you need more power, but the way I was thinking of adding child widgets to my json impl was just by using the ironbar syntax like in custom modules
Author
Owner

@yavko commented on GitHub (Mar 8, 2024):

I just saw u changed the milestone, and remembered this issue, and I was thinking, that maybe instead of lua, rhai could be used, since it's more rusty https://lib.rs/crates/rhai

<!-- gh-comment-id:1985247018 --> @yavko commented on GitHub (Mar 8, 2024): I just saw u changed the milestone, and remembered this issue, and I was thinking, that maybe instead of lua, rhai could be used, since it's more rusty https://lib.rs/crates/rhai
Author
Owner

@JakeStanger commented on GitHub (Mar 8, 2024):

I've been playing around with this in the background over the past few days funnily enough, and Rhai is one of the things I've looked at.

The issue with Rhai is that it needs to generate its own methods and typings rather than using the Rust ones, and although it does have methods of automatically doing that I've not been able to make them play nice with GTK/Cairo. It seems everything would have to be manually implemented (or I'd have to write a macro to generate everything and I don't know where I'd begin with that for this). GTK especially poses difficultly because of its prelude based implementation.

Lua meanwhile has the advantage of being very low level and very lightweight, and fairly popular. Using mlua + lgi seems like the combo here, as I can just pass a glib pointer for any gobject into the runtime and have it fully exist in Lua. I'm testing this one currently.

<!-- gh-comment-id:1985343230 --> @JakeStanger commented on GitHub (Mar 8, 2024): I've been playing around with this in the background over the past few days funnily enough, and Rhai is one of the things I've looked at. The issue with Rhai is that it needs to generate its own methods and typings rather than using the Rust ones, and although it does have methods of automatically doing that I've not been able to make them play nice with GTK/Cairo. It seems everything would have to be manually implemented (or I'd have to write a macro to generate everything and I don't know where I'd begin with that for this). GTK especially poses difficultly because of its prelude based implementation. Lua meanwhile has the advantage of being very low level and very lightweight, and fairly popular. Using mlua + lgi seems like the combo here, as I can just pass a glib pointer for any gobject into the runtime and have it fully exist in Lua. I'm testing this one currently.
Author
Owner

@JakeStanger commented on GitHub (Mar 8, 2024):

A lot to work out still, but it WORKS! We can do a lot with this potentially

let surface = ImageSurface::create(Format::ARgb32, 32, 32).unwrap();
let surface_ptr = unsafe { surface.into_glib_ptr().cast() };

let lua = unsafe { Lua::unsafe_new() };
lua.globals().set("surface_ptr", LightUserData(surface_ptr)).unwrap();

let script = r#"
    local lgi = require('lgi')
    local cairo = lgi.cairo

    surface = cairo.Surface(surface_ptr)
    cr = cairo.Context(surface)

    cr:set_source_rgb(1.0, 0.0, 0.0);
    cr:paint();

    surface:write_to_png("test.png")
"#;

lua.load(script).exec().unwrap();

mlua + lgi, currently requires both to be installed as system packages but playing with the possibility of vendoring.

<!-- gh-comment-id:1986556958 --> @JakeStanger commented on GitHub (Mar 8, 2024): A *lot* to work out still, but it WORKS! We can do a lot with this potentially ```rust let surface = ImageSurface::create(Format::ARgb32, 32, 32).unwrap(); let surface_ptr = unsafe { surface.into_glib_ptr().cast() }; let lua = unsafe { Lua::unsafe_new() }; lua.globals().set("surface_ptr", LightUserData(surface_ptr)).unwrap(); let script = r#" local lgi = require('lgi') local cairo = lgi.cairo surface = cairo.Surface(surface_ptr) cr = cairo.Context(surface) cr:set_source_rgb(1.0, 0.0, 0.0); cr:paint(); surface:write_to_png("test.png") "#; lua.load(script).exec().unwrap(); ``` mlua + lgi, currently requires both to be installed as system packages but playing with the possibility of vendoring.
Author
Owner

@yavko commented on GitHub (Mar 8, 2024):

A lot to work out still, but it WORKS! We can do a lot with this potentially

let surface = ImageSurface::create(Format::ARgb32, 32, 32).unwrap();
let surface_ptr = unsafe { surface.into_glib_ptr().cast() };

let lua = unsafe { Lua::unsafe_new() };
lua.globals().set("surface_ptr", LightUserData(surface_ptr)).unwrap();

let script = r#"
    local lgi = require('lgi')
    local cairo = lgi.cairo

    surface = cairo.Surface(surface_ptr)
    cr = cairo.Context(surface)

    cr:set_source_rgb(1.0, 0.0, 0.0);
    cr:paint();

    surface:write_to_png("test.png")
"#;

lua.load(script).exec().unwrap();

mlua + lgi, currently requires both to be installed as system packages but playing with the possibility of vendoring.

Sick, generating an image is cool, though integrating it must be harder. Maybe lua could also be integrated with other parts, like instead of a script.

<!-- gh-comment-id:1986578452 --> @yavko commented on GitHub (Mar 8, 2024): > A *lot* to work out still, but it WORKS! We can do a lot with this potentially > > ```rust > let surface = ImageSurface::create(Format::ARgb32, 32, 32).unwrap(); > let surface_ptr = unsafe { surface.into_glib_ptr().cast() }; > > let lua = unsafe { Lua::unsafe_new() }; > lua.globals().set("surface_ptr", LightUserData(surface_ptr)).unwrap(); > > let script = r#" > local lgi = require('lgi') > local cairo = lgi.cairo > > surface = cairo.Surface(surface_ptr) > cr = cairo.Context(surface) > > cr:set_source_rgb(1.0, 0.0, 0.0); > cr:paint(); > > surface:write_to_png("test.png") > "#; > > lua.load(script).exec().unwrap(); > ``` > > mlua + lgi, currently requires both to be installed as system packages but playing with the possibility of vendoring. Sick, generating an image is cool, though integrating it must be harder. Maybe lua could also be integrated with other parts, like instead of a script.
Author
Owner

@yavko commented on GitHub (Mar 8, 2024):

I've been playing around with this in the background over the past few days funnily enough, and Rhai is one of the things I've looked at.

The issue with Rhai is that it needs to generate its own methods and typings rather than using the Rust ones, and although it does have methods of automatically doing that I've not been able to make them play nice with GTK/Cairo. It seems everything would have to be manually implemented (or I'd have to write a macro to generate everything and I don't know where I'd begin with that for this). GTK especially poses difficultly because of its prelude based implementation.

Lua meanwhile has the advantage of being very low level and very lightweight, and fairly popular. Using mlua + lgi seems like the combo here, as I can just pass a glib pointer for any gobject into the runtime and have it fully exist in Lua. I'm testing this one currently.

If I figure out how to add rhai, would you merge a PR?

<!-- gh-comment-id:1986579965 --> @yavko commented on GitHub (Mar 8, 2024): > I've been playing around with this in the background over the past few days funnily enough, and Rhai is one of the things I've looked at. > > The issue with Rhai is that it needs to generate its own methods and typings rather than using the Rust ones, and although it does have methods of automatically doing that I've not been able to make them play nice with GTK/Cairo. It seems everything would have to be manually implemented (or I'd have to write a macro to generate everything and I don't know where I'd begin with that for this). GTK especially poses difficultly because of its prelude based implementation. > > Lua meanwhile has the advantage of being very low level and very lightweight, and fairly popular. Using mlua + lgi seems like the combo here, as I can just pass a glib pointer for any gobject into the runtime and have it fully exist in Lua. I'm testing this one currently. If I figure out how to add rhai, would you merge a PR?
Author
Owner

@JakeStanger commented on GitHub (Mar 9, 2024):

Maybe lua could also be integrated with other parts, like instead of a script.

Yeah this unlocks an enormous amount of potential. I'm still not 100% on the direction I want to take it yet but this does possibly give a much more powerful alternative to calling out to shell scripts for custom stuff.

If I figure out how to add rhai, would you merge a PR?

I'm certainly open to it, no harm in more choice at this level I don't think. I'd want it to closely mirror whatever lua impl I come up with is the main consideration, so that ideally the choice is "pick your lang, they do the same thing"

<!-- gh-comment-id:1986801079 --> @JakeStanger commented on GitHub (Mar 9, 2024): > Maybe lua could also be integrated with other parts, like instead of a script. Yeah this unlocks an enormous amount of potential. I'm still not 100% on the direction I want to take it yet but this does possibly give a much more powerful alternative to calling out to shell scripts for custom stuff. > If I figure out how to add rhai, would you merge a PR? I'm certainly open to it, no harm in more choice at this level I don't think. I'd want it to closely mirror whatever lua impl I come up with is the main consideration, so that ideally the choice is "pick your lang, they do the same thing"
Author
Owner

@JakeStanger commented on GitHub (Mar 14, 2024):

Far from done, but I've got a working module at the PR above which is in a useable state with some fiddling about. Full details in there.

<!-- gh-comment-id:1998650730 --> @JakeStanger commented on GitHub (Mar 14, 2024): Far from done, but I've got a working module at the PR above which is in a useable state with some fiddling about. Full details in there.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
JakeStanger/ironbar#7086
No description provided.