Add for_exec_window command and activation_token criteria#339
Conversation
|
This is related to this old feature request for sway: swaywm/sway#4045 |
1555569 to
819f013
Compare
|
I prefer to use Lua for these things instead of cluttering the code more. The Lua environment is richer and more powerful than some parsed config rule. In general The local scroll = require("scroll")
local function on_create(view, data)
if scroll.view_get_app_id(view) == "kitty" then
local container = scroll.view_get_container(view)
if container then
scroll.container_set_focus(container)
scroll.command(scroll.view_get_container(view), "set_size h 0.2")
scroll.command(scroll.view_get_container(view), "align left")
end
end
end
scroll.add_callback("view_map", on_create, nil)If you think something is missing from the Lua API to implement this feature, maybe it would be better to add it there. Currently, there are functions to retrieve information about the view and application running in it, maybe something else is missing there. Running commands is already supported. |
|
The key thing this adds is the ability to match based on an xdg activation token, which was not previously exposed either via IPC or LUA, in order to say: launch a new window, and perform certain actions on just that exact window associated with my launch action. I can certainly add a LUA API for getting the activation token of a window (which this PR also exposes via get_tree now) and for minting a new activation token (which this PR also exposes as a new IPC request), but I think the added |
|
You can already do what
local args, state = ...
local scroll = require("scroll")
local id_cmd = nil
local id_app = nil
local function on_view_map(view, data)
if id_app then
scroll.remove_callback(id_app)
end
if #args > 1 then
local container = scroll.view_get_container(view)
scroll.command(container, args[2])
end
end
local function on_command_end(cmd_data, data)
if id_cmd then
scroll.remove_callback(id_cmd)
end
id_app = scroll.add_callback("view_map", on_view_map, nil)
end
if #args > 0 then
id_cmd = scroll.add_callback("command_end", on_command_end, nil)
scroll.command(nil, args[1])
endand with the |
|
The problem with this approach of just matching the next window to map is that if there are multiple windows opening concurrently you might match the wrong one. For my specific use case of the session manager I want to be able to restore multiple windows at the same time in some cases (e.g. after restart) and also be robust to the user opening windows manually at the same time. I also can't rely on app_id because there may be multiple windows with the same app_id, e.g. multiple terminals or multiple browser windows. There are other heuristics that can be used for matching, like title, but activation token provides the most robust and easiest way by far. |
|
Sure, what I meant is we could add Lua functions to retrieve the view's launch context activation token, or whatever is necessary. There is already one to get the view's parent PID too. My main idea when I added Lua was to get rid of IPC as much as possible, because it is usually abused, slow, and needs a lot of parsing of the content tree, which is fragile and prone to errors and questions by users. |
|
In 5bd4975 I added the Lua API function
local args, state = ...
local scroll = require("scroll")
local id_app = nil
local context = nil
local function on_view_map(view, data)
if context and scroll.view_get_pid(view) == context.pid then
if id_app then
scroll.remove_callback(id_app)
end
if #args > 1 then
local container = scroll.view_get_container(view)
scroll.command(container, args[2])
end
end
end
if #args > 0 then
context = scroll.exec_process(args[1])
if context then
id_app = scroll.add_callback("view_map", on_view_map, nil)
end
end
local args, state = ...
local scroll = require("scroll")
local id_map = nil
local id_unmap = nil
local context = nil
local function on_view_map(view, data)
if context and #args > 1 then
local parent = scroll.view_get_parent_view(view)
if parent and scroll.view_get_pid(parent) == context.pid then
local container = scroll.view_get_container(view)
scroll.command(container, args[2])
end
end
end
local function on_view_unmap(view, data)
if context and scroll.view_get_pid(view) == context.pid then
if id_map then
scroll.remove_callback(id_map)
end
if id_unmap then
scroll.remove_callback(id_unmap)
end
end
end
if #args > 0 then
context = scroll.exec_process(args[1])
if context then
id_map = scroll.add_callback("view_map", on_view_map, nil)
id_unmap = scroll.add_callback("view_unmap", on_view_unmap, nil)
end
endDo you miss anything else? I think the activation token has its own problems, like timeouts, regeneration for different purposes etc, the PID should be more robust and always present until the application closes. |
|
For my use case of the session manager, I already could match by pid using the existing capabilities but pid matching is problematic for programs where you have a shared background process that can be used to launch multiple windows, e.g. Firefox, chromium, footclient. Many of those programs support xdg activation protocol. Also because the session manager is a separate program, not implemented as a lua extension, and intended to support multiple compositors, ipc is generally more convenient than lua although I'm happy to use lua if needed to access certain capabilities. For most other customizations, though, I agree lua is better. |
|
But what I see from the code is you basically are using the initial activation token as some sort of initialzation id token, you store it within the view, and after that point, it stops being an activation token, "ignoring" any rules that apply to the original token. (which will be destroyed not much later). A real activation token will change during the lifetime of the application, for example the application will generate new ones to give focus away, and that will change the But if we consider this is just a way to generate a unique parent ID created at launch, I could add that part of the code and provide a Lua API function to retrieve it, something like: What would be the minimum you need for your project, and how do you expect to implement it using those new capabilities? |
|
You are right that the name "activation_token" may be a bit misleading --- "initial_activation_token" might be better. The use case is as follows: The session manager creates its own windows to represent each suspended window (and shows a screenshot). To restore a window, it needs to tell the application to create a new window with the same "state" as the suspended window, ideally have it initially map on a hidden workspace to avoid flicker, and then once it opens and is ready swap it with the screenshot window. Using the initial activation token, the session manager can:
I know that activation tokens were really only intended to prevent focus stealing but they do provide a mechanism to uniquely associate a launch request with the window that gets mapped, without relying on application-specific and less reliable heuristics (though of course some applications don't support them). |
8b6d449 to
740ba7c
Compare
|
Note: I discovered that firefox and other programs like footclient do correctly support xdg activation protocol but chromium has code intended to support XDG_ACTIVATION_TOKEN but that support is currently buggy and mostly doesn't work. |
28e4a91 to
ea15f76
Compare
|
I think we are not understanding each other well. Let me explain what I understand you are trying to achieve:
With that in mind, you developed a framework that allows you to:
Isn't there a cleaner and more general way to achieve this? I mean, we just want a way to communicate specific new mapped windows to do something, and we want to have support for every application.
So in my understanding, we don't need activation tokens for anything. The So I added the Lua API function local args, state = ...
local scroll = require("scroll")
local id_app = nil
local context = nil
local function on_view_map(view, data)
if context and scroll.view_get_pid(view) == context.pid then
if id_app then
scroll.remove_callback(id_app)
end
if #args > 1 then
local id = scroll.view_get_env(view, "XDG_ACTIVATION_TOKEN")
scroll.log("from view_get_env XDG_ACTIVATION_TOKEN="..id)
scroll.log("from exec_process XDG_ACTIVATION_TOKEN="..context.activation_token)
local container = scroll.view_get_container(view)
scroll.command(container, args[2])
end
end
end
if #args > 0 then
context = scroll.exec_process(args[1])
if context then
id_app = scroll.add_callback("view_map", on_view_map, nil)
end
endI think with this, we don't need to fake activation tokens or store them, but correct me if I am wrong. |
|
Consider:
Lots of applications work the same way as firefox as far as multiple windows, many of them support XDG_ACTIVATION_TOKEN, and even the ones that don't are likely to be receptive to a patch to support it since it is also needed for the new window to gain focus under certain compositors. |
|
I created a corresponding sway PR swaywm/sway#9205 I also updated this to match the IPC number used in the sway PR, but this shouldn't be merged like that until it is also merged in sway. If sway rejects this but you are okay with it, then I'll fix the IPC number. |
- Implement `for_exec_window <commands> <command_to_exec>` to run commands on the next window opened by the executed process. - Add `activation_token` criteria to match windows by their XDG activation token. - Implement `IPC_MINT_ACTIVATION_TOKEN` (102) IPC command to allow clients to obtain a freshly generated XDG activation token. - Expose `activation_token` in the window JSON representation (get_tree). - Add documentation for the new IPC command in `scroll-ipc.7.scd` and `README.md`. - Add tests for `for_exec_window` (including PID, XDG activation, and X11 startup ID matching) and `IPC_MINT_ACTIVATION_TOKEN`.
for_exec_window allows executing a program and applying a list of sway commands to the window created by that program.
Syntax:
for_exec_window "<sway commands>" <exec command>Matches via XDG activation token, PID (fallback), or X11 startup ID (Xwayland).
Expose activation_token in window's JSON representation (IPC). Added activation_token to sway_view and serialized it in get_tree.
Support for_window [activation_token=...] criteria. This is a one-shot criteria that binds commands to the corresponding launcher_ctx immediately, and cannot be combined with other criteria.
Changes: