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

Allow to run multiple instances of AwesomeWM and communicate between them with dbus #3949

Open
actionless opened this issue Sep 1, 2024 · 11 comments

Comments

@actionless
Copy link
Member

actionless commented Sep 1, 2024

The concept is that i wanna hv smth like this in my init script:

awesome --config ./rc_wm.lua --name "wm" --disable-naughty &
awesome --config ./rc_notifications.lua --name "naughty" --disable-wm &
awesome --config ./rc_panels.lua --name "panels" --disable-wm --disable-naughty

and inside the config to hv smth like this:

awesome_remote("panels"):emit_signal("top_panel::expand", {some_data})
awesome_remote("naughty"):emit_signal("naughty::close_all", {some_data})

and so on

WDYT?

@Elv13
Copy link
Member

Elv13 commented Sep 8, 2024

This would require to strip out the window management code from the "other" processes (and most of capi). It might also be easier to do share-nothing threads within the main process and use lua built-in serialization to send messages to the thread mainloop. That requires to strip even more our of capi to avoid race conditions.

@actionless
Copy link
Member Author

i understand that inter-communication could be tricky and be implemented in different approaches, however for starters i'm more curious, how complicated could be implementing --disable-wm and --disable-naughty?

@Aire-One
Copy link
Member

Aire-One commented Sep 9, 2024

--disable-naughty is a frequently requested feature, so it would make sense to support it at some point. (Maybe through startup options/modeline https://awesomewm.org/apidoc/documentation/09-options.md.html ?)

To my understanding, --disable-wm would basically allow creating wiboxes/widgets on a "standalone mode". @xinhaoyuan already made some work on this direction: https://github.com/xinhaoyuan/awexygen. Widgets that interacts with the WM features would require some ipc/dbus work to be done on the awesome backend side.

@sclu1034
Copy link
Contributor

sclu1034 commented Sep 9, 2024

What's the use case, even?
--disable-naughty makes sense for people that want to use other daemons, like dunst. awexygen makes sense as a way to bring your UI to other WMs. So in both cases, the point is compatibility with other applications.

But what's the use case for this when all of it stays within Awesome anyways? What do you need separate processes for?

@actionless
Copy link
Member Author

What's the use case, even?

because rendering panels in the same thread as doing window-management stuff is cringe

and also should allow using awesome panels/widgets with hyprland

@joaopauloalbq
Copy link

joaopauloalbq commented Sep 10, 2024

What's the use case, even?

because rendering panels in the same thread as doing window-management stuff is cringe

and also should allow using awesome panels/widgets with hyprland

Making Awesome multithreading and making Awesome do what Hyprland does is a better approach IMO.

@actionless
Copy link
Member Author

@joaopauloalbq are you willing to contribute that code..?

@actionless
Copy link
Member Author

actionless commented Sep 11, 2024

also some after-thoughts:

  1. would we also need --disable-root flag probably? (or it's logical to assume that with --no-wm)
  2. since multi-threading was mentioned couple of times, i'll also add few cents about that (not in relation to main topic, mb even worth creating separate ticket for this point) - that actually would be dope to make separate drawing thread, but it would require rewriting big bunch of code, as now all drawing-related code is written synchronously
  3. and also after adding some decent Scrollable Area kind of widget, awesome in no-wm mode could be a nice UI toolkit for writing some small apps, as current ui toolkits either crazily bloat like gtk and qt, either not able to follow the system theme settings like tk or fltk

@Aire-One
Copy link
Member

Aire-One commented Oct 27, 2024

I worked a few hours on a new command line parameter to allow Awesome to be used as a Lua interpreter awesome -e somescript.lua someargs1 someargs2. It would allow running some Lua scripts using the Awesome Lua API (and more specifically, the C API that cannot be imported without running the C code from Awesome).

My primary target for such a feature is to allow running busted with a custom Lua interpreter to have an environment where tests could include Awesome specificities, similar to what https://github.com/mfussenegger/nlua does. It would allow writing integration tests without the current "start Awesome in some nested X server and play the test as a configuration" trick. With a long term vision, my work here could be used to allow running parts of Awesome without the actual WM, so I guess it's worth mentioning it here.

I have the early work done, the option can be used, and the base interpreter is starting. And obviously, with a runner script and some config for busted, we can run some tests with Awesome as the Lua interpreter:

runner

#!/usr/bin/env -S awesome -e

local runner = require "busted.runner"
runner { standalone = false }

.busted

return {
   _all = {
      lua = "./runner",
      lpath = "/usr/share/awesome/lib/?.lua;/usr/share/awesome/lib/?/init.lua;/usr/share/awesome/lib/?/?.lua",
   },
}

spec/awesome_spec.lua

if os.getenv "LOCAL_LUA_DEBUGGER_VSCODE" == "1" then
   require("lldebugger").start()
end

describe("awesome", function()
   it("should have access to awesome CAPIs", function()
      local capi = {
         awesome = _G.awesome,
         client = _G.client,
         screen = _G.screen,
         tag = _G.tag,
      }

      assert.is_not_nil(capi.client)
      assert.is_not_nil(capi.screen)
      assert.is_not_nil(capi.tag)
   end)

   it("should require awesome libraries", function()
      assert.has_no.errors(function()
         require "gears"
         require "awful"
         -- require "beautiful"
         -- require "naughty"
         -- require "wibox"
      end)
   end)
end)

It has to be noted that the only result for all of that are crashes 🥲:

  • When running the test with busted ., the "should require" test fails regardless of the libraries commented-out (except gears that works) because at some point in every library's internal requires chain, we have a require("beautiful"), which run set_font("sans 8") , which ends up in the xresources.get_dpi function, which does a root.size(), which I guess segfault on globalconf.screen->width_in_pixels since we skipped X stuff, by design from the awesome -e usage, so globalconf.screen has to be NULL.
  • When in debug mode, if you mouseover the screen stuff, the debugger will inspect the object to print it in a tooltip. This cause a crash with message W: Unknown screen output name: __len.

It was obvious that without X stuff, I couldn't have gone far. But I hoped it would have been a little further than that, and I would have had the time to have some fun.

My personal conclusion to this little experiment is that it's probably not worth putting more effort into this work, regarding my primary target (which was to run Awesome as a Lua interpreter for Busted tests). If we have to stub/fake half the APIs because we don't have X, there is no reason to force my test to use Awesome as the Lua interpreter. I can write a helper that exposes fake Awesome Globals (just like Awesome unit tests already do) and call it a day.

I wanted to run Awesome to have an environment with the C API, but without the X stuff, so basically without what the C API is supposed to be. At this point, why even run Awesome? duh!

For those who want to use the Awesome Wibox API to build some applications, it may be interesting to take on the work from here. In the meantime, just use Awexygen (BTW https://github.com/xinhaoyuan/awexygen/blob/b91cd86eed36988a86da70f3f3713e443aa77be3/lib/awesome_rt/root.lua#L24-L26).

So, yeah. That's what I did last night instead of sleeping...

Anyway, here is the code:

diff --git a/awesome.c b/awesome.c
index 2b17c725f..2d387a04e 100644
--- a/awesome.c
+++ b/awesome.c
@@ -560,6 +560,33 @@ true_config_callback(const char *unused)
     return true;
 }
 
+/** Run as a Lua interpreter and exit.
+ */
+static void
+interpreter(int argc, char **argv, int lua_arg0, string_array_t *searchpath)
+{
+    luaA_init(NULL, searchpath); // NULL xdgHandle, it seems to be unused anyway
+    string_array_wipe(searchpath);
+    init_rng();
+    ewmh_init_lua();
+    luaA_init_argv(argv, argc, lua_arg0);
+
+    lua_State *L = globalconf_get_lua_State();
+    bool success = true;
+
+    if(luaL_dofile(L, argv[lua_arg0]))
+    {
+        const char *err = lua_tostring(L, -1);
+        fprintf(stderr, "ERROR: %s\n", err);
+        success = false;
+    }
+
+    p_delete(searchpath);
+    lua_close(L);
+
+    exit(success ? EXIT_SUCCESS: EXIT_FAILURE);
+}
+
 /** Hello, this is main.
  * \param argc Who knows.
  * \param argv Who knows.
@@ -590,6 +617,7 @@ main(int argc, char **argv)
     globalconf.api_level = awesome_default_api_level();
     buffer_init(&globalconf.startup_errors);
     string_array_init(&searchpath);
+    int lua_argc = 0;
 
     /* save argv */
     awesome_argv = argv;
@@ -601,7 +629,7 @@ main(int argc, char **argv)
 
     /* if no shebang is detected, check the args. Shebang (#!) args are parsed later */
     if (!confpath)
-        confpath = options_check_args(argc, argv, &default_init_flags, &searchpath);
+        confpath = options_check_args(argc, argv, &default_init_flags, &searchpath, &lua_argc);
 
     /* Get XDG basedir data */
     if(!xdgInitHandle(&xdg))
@@ -652,7 +680,13 @@ main(int argc, char **argv)
 
     /* Parse `rc.lua` to see if it has an AwesomeWM modeline */
     if (!(default_init_flags & INIT_FLAG_FORCE_CMD_ARGS))
-        options_init_config(&xdg, awesome_argv[0], confpath, &default_init_flags, &searchpath);
+        options_init_config(&xdg, awesome_argv[0], confpath, &default_init_flags, &searchpath, &lua_argc);
+
+    /* Execute a Lua script and exit */
+    if (default_init_flags & INIT_FLAG_EXECUTE_LUA)
+    {
+        interpreter(argc, argv, lua_argc, &searchpath);
+    }
 
     /* Setup pipe for SIGCHLD processing */
     {
diff --git a/luaa.c b/luaa.c
index bf1800ce9..2377e28a8 100644
--- a/luaa.c
+++ b/luaa.c
@@ -1351,4 +1351,28 @@ luaA_default_newindex(lua_State *L)
     return luaA_class_newindex_miss_property(L, NULL);
 }
 
+/** Copies arguments starting at lua_arg0 to _G.arg
+ * based on https://github.com/neovim/neovim/blob/master/src/nvim/lua/executor.c#L347
+*/
+int
+luaA_init_argv(char **argv, int argc, int lua_arg0)
+{
+    lua_State *L = globalconf_get_lua_State();
+    int i = 0;
+    lua_newtable(L);  // _G.arg
+
+    if (lua_arg0 > 0) {
+        lua_pushstring(L, argv[lua_arg0 - 1]);
+        lua_rawseti(L, -2, 0);
+
+        for (; i + lua_arg0 < argc; i++) {
+            lua_pushstring(L, argv[i + lua_arg0]);
+            lua_rawseti(L, -2, i + 1);
+        }
+    }
+
+    lua_setglobal(L, "arg");
+    return i;
+}
+
 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/luaa.h b/luaa.h
index 04e283422..676344f5e 100644
--- a/luaa.h
+++ b/luaa.h
@@ -331,5 +331,7 @@ void luaA_emit_startup(void);
 
 void luaA_systray_invalidate(void);
 
+int luaA_init_argv(char **argv, int argc, int lua_arg0);
+
 #endif
 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/options.c b/options.c
index 3ef88b563..5efb6181f 100644
--- a/options.c
+++ b/options.c
@@ -72,7 +72,7 @@ push_arg(string_array_t *args, char *value, size_t *len)
  * Support both shebang and modeline modes.
  */
 bool
-options_init_config(xdgHandle *xdg, char *execpath, char *configpath, int *init_flags, string_array_t *paths)
+options_init_config(xdgHandle *xdg, char *execpath, char *configpath, int *init_flags, string_array_t *paths, int *lua_argc)
 {
     /* The different state the parser can have. */
     enum {
@@ -318,7 +318,7 @@ options_init_config(xdgHandle *xdg, char *execpath, char *configpath, int *init_
     /* Be future proof, allow let unknown keys live, let the Lua code decide */
     (*init_flags) |= INIT_FLAG_ALLOW_FALLBACK;
 
-    options_check_args(argv.len, argv.tab, init_flags, paths);
+    options_check_args(argv.len, argv.tab, init_flags, paths, lua_argc);
 
     /* Cleanup */
     string_array_wipe(&argv);
@@ -402,29 +402,31 @@ exit_help(int exit_code)
 #define NO_ARG 0
 
 char *
-options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths)
+options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths, int *lua_argc)
 {
 
     static struct option long_options[] =
     {
-        { "help"      , NO_ARG, NULL, 'h'  },
-        { "version"   , NO_ARG, NULL, 'v'  },
-        { "config"    , ARG   , NULL, 'c'  },
-        { "force"     , NO_ARG, NULL, 'f'  },
-        { "check"     , NO_ARG, NULL, 'k'  },
-        { "search"    , ARG   , NULL, 's'  },
-        { "no-argb"   , NO_ARG, NULL, 'a'  },
-        { "replace"   , NO_ARG, NULL, 'r'  },
-        { "screen"    , ARG   , NULL, 'm'  },
-        { "api-level" , ARG   , NULL, 'l'  },
-        { "reap"      , ARG   , NULL, '\1' },
-        { NULL        , NO_ARG, NULL, 0    }
+        { "help"       , NO_ARG, NULL, 'h'  },
+        { "version"    , NO_ARG, NULL, 'v'  },
+        { "config"     , ARG   , NULL, 'c'  },
+        { "force"      , NO_ARG, NULL, 'f'  },
+        { "check"      , NO_ARG, NULL, 'k'  },
+        { "search"     , ARG   , NULL, 's'  },
+        { "no-argb"    , NO_ARG, NULL, 'a'  },
+        { "replace"    , NO_ARG, NULL, 'r'  },
+        { "screen"     , ARG   , NULL, 'm'  },
+        { "api-level"  , ARG   , NULL, 'l'  },
+        { "execute-lua", NO_ARG, NULL, 'e'  }, // break out of the loop, everything after this parameter should be passed to the Lua interpreter
+        { "reap"       , ARG   , NULL, '\1' },
+        { NULL         , NO_ARG, NULL, 0    }
     };
 
     char *confpath = NULL;
     int opt;
+    int opt_position = 1; // 0 is the program name, getopt_long loop starts at argv[1]
 
-    while((opt = getopt_long(argc, argv, "vhfkc:arms:l:",
+    while((opt = getopt_long(argc, argv, "vhfkc:arms:l:e:",
                              long_options, NULL)) != -1) {
         switch(opt)
         {
@@ -473,6 +475,18 @@ options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths
           case 'l':
               set_api_level(optarg);
               break;
+          case 'e':
+            (*init_flags) |= INIT_FLAG_EXECUTE_LUA;
+            // --execute-lua is supposed to be the last parameter, followed by the Lua script and its parameters
+            if (opt_position + 1 < argc) {
+                *lua_argc = opt_position + 1;
+                return NULL; // No config file to load
+            }
+            else {
+                fprintf(stderr, "No Lua code to execute\n");
+                exit(EXIT_FAILURE);
+            }
+            break;
           case '\1':
             /* Silently ignore --reap and its argument */
             break;
@@ -480,7 +494,9 @@ options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths
             if (! ((*init_flags) & INIT_FLAG_ALLOW_FALLBACK))
                 exit_help(EXIT_FAILURE);
             break;
-        }}
+        }
+        opt_position++;
+    }
 
     return confpath;
 }
diff --git a/options.h b/options.h
index 97e66e8c8..569eb946c 100644
--- a/options.h
+++ b/options.h
@@ -32,8 +32,9 @@ typedef enum {
     INIT_FLAG_AUTO_SCREEN    = 0x1 << 3,
     INIT_FLAG_ALLOW_FALLBACK = 0x1 << 4,
     INIT_FLAG_FORCE_CMD_ARGS = 0x1 << 5,
+    INIT_FLAG_EXECUTE_LUA    = 0x1 << 6,
 } awesome_init_config_t;
 
 char *options_detect_shebang(int argc, char **argv);
-bool options_init_config(xdgHandle *xdg, char *execpath, char *configpath, int *init_flags, string_array_t *paths);
-char *options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths);
+bool options_init_config(xdgHandle *xdg, char *execpath, char *configpath, int *init_flags, string_array_t *paths, int *lua_argc);
+char *options_check_args(int argc, char **argv, int *init_flags, string_array_t *paths, int *lua_argc);

@actionless
Copy link
Member Author

actionless commented Oct 27, 2024

thanks for sharing, @Aire-One

i'm overall looking positive towards merging smth like that - as it would decrease the "diff surface" of things which would need to be changed for that nowm/toolkit thing

btw getting back to the thing about using awesomewm as UI toolkit, if we'll move into that direction, i could also contribute the code for matching not only xresources/gtk3 themes but also gtk4 and qt5/qt6 (i have some drafts for that code already but not in lua, and not for all the theme variables). and would support that direction by porting Themix and some smaller GUI apps to that new toolkit (which would require adding python bindings - which as a result would add some, mb at first limited, python bindings to awesome wm itself*)


* instead of modifying and encomplicated awesomewm C code and adding python bindings there, instead the python "port" would start one instanse of awesome itself in noWM mode as lua interpreter, and next translating each awesome python API command into awesome lua api command** and feeding to that interpreter. which is pretty similar to how gi/gtk bindings work in python as well

** also i think that hacking ldoc a bit might help to auto-generate those bindings

btw, fun fact:

$ pacman -Qi awesome-luajit-git | grep Size
Installed Size  : 34.54 MiB

$ pacman -Qi gtk{2,3,4} | grep Size
Installed Size  : 36.47 MiB
Installed Size  : 52.14 MiB
Installed Size  : 46.50 MiB

@actionless
Copy link
Member Author

actionless commented Oct 27, 2024

and regarding the awexygen i thought i wrote it in previous messages but apparently either forgot or mistakenly removed while typing other paragraphs:

yeah, it looks similar to that final goal i wanna achieve, but its problem that it's a fork of awesome, while keeping smth similar inside existing codebase would take significantly less maintenance effort (and with the time it could be used to speed-up and decouple awesome by splitting it into different processes for panel, wm, notifications, sidebar and other user-defined features, or running all features with alternative WM even inside wayland)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants