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

Yet another swaybar ipc implementation #1244

Merged
merged 23 commits into from
Dec 1, 2021
Merged

Conversation

alebastr
Copy link
Contributor

@alebastr alebastr commented Sep 14, 2021

This feature makes waybar a client for the Sway bar IPC protocol and allows controlling some aspects of the bar behavior with swaymsg

Documentation: i3 bar display modes, sway-bar(5)

Quickstart

Prerequisites:

  • Sway
  • Single bar configuration block (i.e. $XDG_CONFIG_HOME/config/waybar is a JSON object)
  • The bar is started from the sway config with swaybar_command waybar

Configuration

  1. Add "ipc": true to the waybar configuration
  2. Set the desired mode, hidden_state and modifier in the same bar configuration block as swaybar_command
    • Sway will assign the default identifier bar-0 to the only bar instance and run waybar as /usr/bin/waybar -b bar-0
    • Waybar will read one bar configuration; create a bar; connect to the sway IPC with the bar id bar-0 provided via commandline argument and get the initial configuration
    • Only after that waybar will create a window with the mode specified in the sway config.
  3. Control the bar mode/visibility with swaymsg bar mode <mode> bar-0, swaymsg bar hidden_state <show|hide> bar-0 (note the trailing bar_id syntax)

Multiple bars

Your $XDG_CONFIG_HOME/config/waybar is a JSON array with multiple bars defined.

  • If you don't want a particular bar instance to be controlled via IPC, simply don't add the ipc option to the corresponding config block. It will default to disabled.
  • If you want the bar instance to be controlled with the id from sway config (the one specified in the bar config block with swaybar_command waybar) - add only "ipc": true. Sway will pass the bar_id and the bar instance will use that value.
  • If you want the bar instance to be controlled with a separate bar id (i.e. you have two bar configuration blocks in the sway config), set the id in the waybar config for the corresponding bar instance ("id": "bar-1") and then use this identifier for the swaymsg command invocation: swaymsg bar mode hide bar-1

Example configuration:

$XDG_CONFIG_HOME/sway/config
bar  {
        # will asssign id 'bar-0' by default
	swaybar_command /path/to/waybar
	mode dock
	position top
}

# the id can be set with `bar <id> { ... }` or with the `bar { id <id>; ... }`
# if you don't specify the id explicitly, sway will assign `bar-1`
bar bar-1 {
        # placeholder command, so that sway could create the configuration but won't run an actual second bar instance
	swaybar_command /bin/true
	mode hide
	position bottom
}
$XDG_CONFIG_HOME/waybar/config
[
{ // bar id is passed via `-b bar-0` commandline argument from sway
  // will react only to the events for "bar-0" or for any bar
	"ipc": true,
	"position": "top",
	...
},
{ // bar id is explicitly set to "bar-1"
  // will react only to the events for "bar-1" or for any bar
	"id": "bar-1",
	"ipc": true,
	"position": "bottom",
	...
}
]

The bar_id parameter is required for IPC subscription.

Test commands (note the trailing bar_id syntax. bar <bar-id> mode ... is broken in sway and will modify config for all the defined bars - swaywm/sway#6680):

swaymsg bar mode dock bar-1
swaymsg bar mode hide bar-1
swaymsg bar hidden_state show bar-1
swaymsg bar hidden_state hide bar-1
swaymsg bar mode overlay bar-0
...

Non-blocking issues:

  • Defaults for bar_id - do we want to assume bar-0 or to take the first entry from get_bar_config result if it's not set explicitly?
    bar-0 is assumed if no other value is passed. The swaybar IPC client will try to get the initial configuration for the bar_id and disable itself if the sway config does not have the bar with that id.

  • setVisible should be controlled either via signals or via IPC. Allowing both at the same time makes things messy and inconsistent.
    setVisible was changed to switch between invisible and default modes. That addressed most of the problems, the only remaining is to remember the last visible mode and use it instead of the default.

  • Copy/move optimizations for SafeSignal

  • D-Bus method for changing mode or visibility when sway IPC is not available (but definitely not in this PR)

  • Support for custom modes/mode configuration overrides;

    example:

    "modes": {
      "dock": {
        "layer": "top"
      },
      // show overlay bar over fullscreen applications
      "overlay": {
        "layer": "overlay"
      },
      // hide bar under the current window instead of making it invisible
      "invisible": {
        "passthrough": false,
        "visible": true
      } 
    }
    

Implement a wrapper over Glib::Dispatcher that passes the arguments to
the signal consumer via synchronized `std::queue`.
Arguments are always passed by value and the return type of the signal
is expected to be `void`.
Add a fixture for writing tests that require interaction with Glib event
loop and a very basic test for SafeSignal.
Use `mode` (`waybar::Bar::setMode`) as a shorthand to configure bar
visibility, layer, exclusive zones and input event handling in the same
way as `swaybar` does.
See `sway-bar(5)` for a description of available modes.
This allows to apply the mode atomically and adds possibility of
defining custom modes (to be implemented).
Read `layer`, `exclusive`, `passthrough` into a special mode "default".
Drop `overlay` layer hacks, as it's easier to use `"mode": "overlay"`
for the same result.
@alebastr alebastr force-pushed the swaybar-ipc branch 2 times, most recently from 3108edf to 5786a27 Compare November 20, 2021 05:23
@alebastr alebastr marked this pull request as ready for review November 20, 2021 06:12
@emirror-de
Copy link

I just tested this one and it looks and works great! I used the following config:

{
    "bar_id": "bar-0",
    "ipc": true,
    "position": "top",
    "mode": "hide",
    "hidden_state": "show"
    ...
}

This works as the expected behavior from sway-bar. Many thanks for that!

If I could make a wish, I would love to be the bar visible if there is no other window on the screen. Is this possible with your custom mode configuration overrides?
In additional, it would be great to have an additional parameter to set the initial value if a bar should be hidden or shown on start until the first MOD press. But as far as I understood, you already do this with the hidden_state parameter? I was not able to figure the configuration out yet.

@alebastr
Copy link
Contributor Author

alebastr commented Nov 21, 2021

If I could make a wish, I would love to be the bar visible if there is no other window on the screen. Is this possible with your custom mode configuration overrides?

Something like "invisible": { "passthrough": false, "visible": true }? I.e. any opened window would cover the bar so you'll see it only on an empty workspace or through a transparent window.
And CSS bits to finish the trick and deal with transparent windows:

/* show window only if the workspace is empty (requires `sway/window` module) */
window#waybar.mode-invisible {
   opacity: 0;
}
window#waybar.mode-invisible.empty {
   opacity: 1;
}

I haven't tested if that works; for now you can try modifying the preset for "hide" in bar.cpp.
Edit: tested, updated to use the correct mode.
Note that .empty class requires sway/window module, but you can set the format to "" to hide it.

In additional, it would be great to have an additional parameter to set the initial value if a bar should be hidden or shown on start until the first MOD press. But as far as I understood, you already do this with the hidden_state parameter? I was not able to figure the configuration out yet.

The behavior here is copied from swaybar and fully controlled by sway config.
I.e. the mode is received and applied before creating the window, and if it is set to mode hide; hidden_state hide then you won't see the bar on start

@somini
Copy link

somini commented Nov 21, 2021

I testes this, but it doesn't work like #1241. The $Mod key cannot toggle the correct bar, it always toggle the first one.

@alebastr
Copy link
Contributor Author

I testes this, but it doesn't work like #1241. The $Mod key cannot toggle the correct bar, it always toggle the first one.

Are you sure you assigned correct bar ids and modes? Check example configs in the pr description; with this configuration it should toggle only the second bar.

Also, there's a bug in sway that makes swaymsg bar <bar_id> mode <mode> change state for all the defined bars. If you ran this command, you may have incorrect internal state in the sway itself.

@alebastr
Copy link
Contributor Author

@somini the PR description now documents the feature, the configuration and the expected behavior.

Let me know if you still have problems or if you have any suggestions for making it easier to set up.

@somini
Copy link

somini commented Nov 22, 2021

@alebastr Thanks, I re-read the docs and now I understood. Here's my setup (same test as #1241).

  • One bar on bottom, always shown
  • One bar on top, hidden, "peekable" by $Mod

Sway:

bar {
    swaybar_command /home/somini/Programming/Waybar/build/waybar
    mode dock
    hidden_state show
    position bottom
}
bar bar-1 {
    swaybar_command true
    mode hide
    hidden_state hide
    position top
}

Waybar:

[
{
    "position": "bottom",
    "name": "main",
    "modules-left": [
	"sway/workspaces",
	"sway/mode"
    ],
    "modules-center": [
	"tray"
    ],
    "modules-right": [
	"clock#localtime"
    ],
    "sway/workspaces": {
	"disable-scroll-wraparound": true
    },
    "sway/mode": {},
    "clock#localtime": {
	"format": "{:%d%b %H:%M:%S}",
	"interval": 1,
	"tooltip": true,
	"tooltip-format": "<big>{:%Y %B}</big>\n<tt>{calendar}</tt>"
    },
},
{
    "ipc": true,
    "id": "bar-1",
    "position": "top",
    "name": "info",
    "modules-center": [
	"clock#localtime"
    ],
    "modules-right": [
    ],
    "clock#localtime": {
	"format": "HIDE {:%d%b %H:%M:%S}",
	"interval": 1,
	"tooltip": true,
	"tooltip-format": "<big>{:%Y %B}</big>\n<tt>{calendar}</tt>",
	"on-click": "notify-send -t 1000 Example 'Body Text'",
    }
}
]

I see that #1241 was abandoned in favour of this, can confirm this works well too.

src/bar.cpp Outdated Show resolved Hide resolved
@Alexays
Copy link
Owner

Alexays commented Nov 23, 2021

LGTM, except one comment.
Amazing work, like always!

Defaults for bar_id - do we want to assume bar-0 or to take the first entry from get_bar_config result if it's not set explicitly?

Assuming bar-0 will be good enough for me

Try to use the default bar id (`bar-0`) if none is set.
@alebastr
Copy link
Contributor Author

Rebased, added default bar id and connection error handling.
BarIpcClient::onInitialConfig intentionally doesn't swallow exceptions so that we can detect a wrong bar_id from the initial config request result and stop ipc.

Intermittent CI failures without any useful diagnostics could be caused
by the OOM killer. 1024MB is not really enough to run 3 parallel jobs
with a modern C++ compiler.
Allow changing existing modes and adding new ones via `modes`
configuration key.
`modes` accepts a JSON object roughly described by the following type
```typescript
type BarMode = {
    layer: 'bottom' | 'top' | 'overlay';
    exclusive: bool;
    passthrough: bool;
    visible: bool;
};
type BarModeList = {
    [name: string]: BarMode;
};
```
and will be merged with the default modes defined in `bar.cpp`.

Note that with absence of other ways to set mode, only those defined in
the `sway-bar(5)`[1] documentation could be used right now.

[1]: https://github.com/swaywm/sway/blob/master/sway/sway-bar.5.scd
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.

4 participants