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

IPC: Accept multiple actions from stdin #1017

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

bbb651
Copy link
Contributor

@bbb651 bbb651 commented Jan 19, 2025

This needs some testing, io::stdin().lines() behaves a little weird with EOF, notably pressing Ctrl + D twice (apparently that's a unix thing, you need to hit it twice when not at the start of a line), you still need to hit it an additional time for some reason.. with echo -e "one\ntwo" | niri msg action it works as expected (by the way, it seems like NIRI_SOCKET isn't replaced correctly in the winit backend).

I made the serialization prefer keeping 1-length vectors as single values to maximize compatibility. The amount of tests for one_or_many is a bit excessive (I thought a visitor was needed at first, and then got the order of the enum variants wrong, so they were helpful), should I only keep the derive ones?

Also I'm not sure what should happen if you pass no actions, currently the cli prints a warning and does nothing but it's allowed in the ipc, should niri respond with an error instead?

Also, this currently splits on spaces, meaning it handles quoting and multiple spaces incorrectly, should this use e.g. shlex instead?

Copy link
Owner

@YaLTeR YaLTeR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this currently splits on spaces, meaning it handles quoting and multiple spaces incorrectly, should this use e.g. shlex instead?

I understand the problem but I feel like more complex lexing is a worse trade-off. Especially when actions are generated programmatically from an existing shell, the last thing you want is some weird extra splitting.

Comment on lines +68 to +69
/// Perform actions.
Action(#[serde(with = "utils::one_or_many")] Vec<Action>),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just add a separate Actions variant and avoid the whole custom parsing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense, it's simpler and also more ergonomic for niri-ipc. The "compactness" of the json representation doesn't really matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish rust allowed somehow matching over both a T and Vec<T> as &[T], the code in the match arm ends up duplicated and it's awkward to break into a helper since it's async and environment capturing, it's just short enough that it's probably fine..?

        Request::Action(action) => {
            let (tx, rx) = async_channel::bounded(1);

            ctx.event_loop.insert_idle(move |state| {
                // Make sure some logic like workspace clean-up has a chance to run before doing
                // actions.
                state.niri.advance_animations();
                state.do_action(action.into(), false);
                let _ = tx.send_blocking(());
            });

            // Wait until the action has been processed before returning. This is important for a
            // few actions, for instance for DoScreenTransition this wait ensures that the screen
            // contents were sampled into the texture.
            let _ = rx.recv().await;
            Response::Handled
        }
        Request::Actions(actions) => {
            if actions.is_empty() {
                return Ok(Response::Handled);
            }

            let (tx, rx) = async_channel::bounded(1);

            ctx.event_loop.insert_idle(move |state| {
                state.niri.advance_animations();
                for action in actions.into_iter().map(niri_config::Action::from) {
                    state.do_action(action, false);
                }
                let _ = tx.send_blocking(());
            });

            let _ = rx.recv().await;
            Response::Handled
        }

Copy link
Owner

@YaLTeR YaLTeR Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you could try something like async fn do_actions(ctx, actions: impl Iterator<Item=Action>)?

Comment on lines +77 to +78
// NOTE: This is only optional because of clap_derive limitations and will never be `None`.
// If an action is not provided it reads actions from stdin and turns to [`Msg::Actions`].
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, could this remain as non-optional, with a separate niri msg actions that does the stdin reading?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this feels a bit weird to me, because it is the same subcommand in terms of functionality, just with different input, unlike how niri msg outputs and niri msg output are different.
Most commands that takes a positional arguments fallback to stdin if it's missing, e.g. cat, jq, nvim, etc. so it feels logical to follow the same convention. Granted this is a bit different since it takes it normally takes a single literal action and not a file of actions (and a flag like -i - feels redundant since it's mutually exclusive with a literal action and the majority of the time you'd use stdin). It might be a bit confusing to have niri msg action not print the help message though...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a bit confusing to have niri msg action not print the help message though...

Yeah, I want that to keep printing help.

Maybe then something more descriptive like niri msg multiple-actions or niri msg actions-from-stdin or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I want that to keep printing help.

Another option is checking if it's a terminal and printing the help message only in that case, then the help message can show a echo -e '...' | niri msg action example

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, I think an explicit separate subcommand is better. Also I guess if we pick multiple-actions or do-multiple-actions, then the IPC enum variant should also be renamed to MultipleActions for consistency.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe ActionBatch?

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.

2 participants