Toqozz / wired-notify

Lightweight notification daemon with highly customizable layout blocks, written in Rust.
MIT License
608 stars 28 forks source link

Dynamic monitor switching? #100

Open Moonlight63 opened 2 years ago

Moonlight63 commented 2 years ago

I really like wired, particularly because it allows me to create system alerts for actions on bspwm, and because each notification (or each layout) is it's own window, I can create layouts in the center of my screen, at the bottom, or whatever. However, one big limitation that I am finding is that the configuration of these layouts is not, and really can not ever be dynamic. When I want to create a popup through a script, I have to have a layout for that popup, which most of the time is fine. However, as I found with certain setups like multi-monitor, it just doesn't scale. Yes, you have focus follows mouse/active window, but I have found that this doesn't actually work with bspwm, and probably most other wms, if you are, say for example, making a popup every time you shift to a new workspace on a specific monitor. You can't use mouse focus, because the mouse could be anywhere, and you can't rely on active window, because if you switch to a workspace without a window, the notification will flicker for a second, then shoot over to the default monitor.

For proper script use, it would be great to have a way to specify overrides for certain config options that are likely to need to be dynamic. This is one such example. Admittedly, this is far from a perfect solution. Disclaimer, I am not a rust developer, but I'm trying to learn! The biggest issue with my proposed solution here is that I would actually prefer if when a new notification was created along with an override for the monitor, that a new layout/window for that new location be dynamically created, allowing for the two stacks of notifications to be independent from each other. This would be mitigated by having templates, but you would still have to have a separate layout for each monitor, and set some kind of rendercriteria for each. It would work, but not especially great for systems that might have a changing number of monitors attached. It doesn't scale.

Like I said, this is far from a perfect solution, and I am only personally using it as a temporary stop gap until something more robust can be implemented, but I wanted to actually provide a working example instead of just opening a ticket with my complaints :stuck_out_tongue: . Some guidance on whether or not dynamically creating new layouts based on hint would even be possible with the current system would be great, although I suspect it would be quite difficult because based on my 10 minutes of code reading it seems the layouts and windows are generated before notifications are received, so you would need some way to read a notification as it comes in, then go back up the lifecycle to create a new layout/window, then move forward? I haven't quite figured out how wireds lifecylcle works. If this would be possible, I think it would be very worth exploring. Not for every config option, but at least for things as dynamic as monitors.

Toqozz commented 2 years ago

Related: #81

Thanks for the pull request! I won't merge this (or maybe eventually I will if I end up doing the same thing), but it's a really good reference for what you actually mean.

This is interesting. The core request is that you can specify the monitor with notification hints, yeah? But as you note there's definitely a bigger issue with just generally having layouts be more dynamic.

My "answer" to this was being able to have different layouts and then just tagging notifications to select different layouts. To my understanding, this does technically work right? It's just really really cumbersome. I believe you can have the separate stacks with this approach too right? If that's true, then I think the question is how we can make the layout configuration stuff more dynamic/easier (possibly using runtime info a bit more?).

I don't have many ideas here. I think it needs a complete redesign to be good and it's going to be a huge breaking change. Here's a couple things I've been thinking of so far:

Moonlight63 commented 2 years ago

Having multiple layouts absolutely works, but yes, it is cumbersome. In this case, not only is it very cumbersome, but it is also inefficient. If you create multiple layouts, each stack of notifications will exist within the layouts that have matching RenderCriteria. Using this logic, it would be trivial in a script to simply change something about the notification in order to affect which layout renders it. In my case, I use notify-send "$OUTPUT" -a BspWorkspace -t 1500 -h string:wired-tag:bspinfo and render_criteria: [AppName("BspWorkspace")] on the notification block. You could create multiple notification blocks with render_criteria: [AppName("BspWorkspace0")], render_criteria: [AppName("BspWorkspace1")], etc, etc, where the number is the monitor to render on, but...... holy crap. Also, again, if the number of monitors changed, then you'd have to edit the config, or just make enough copies of the block that you'd be unlikely to need more. For example, you are pretty unlikely to need more than 4-5 monitors. What this does not account for however is other things that could also be dynamic. I saw an issue opened a while back about wanting to be able to change text color. While I personally would want all my color values to be controlled by one config file instead of in multiple scripts, I could totally see a syntax similar to color: Color(hint: "textColor"), and setting it like -h string:wired-textColor:666d81 in notify-send. Even better would be the ability to define custom variables in the config like:

default-color: #666d81
custom-color: #181d23
...
(
    name: "text",
    parent: "root",
    params: TextBlock((
            ...
            color: Color(var: "textColor"),
            ...
    )),
),
...

Then call up the noti like: notify-send "Hello" -h string:wired-var-textColor:default-color notify-send "World" -h string:wired-var-textColor:custom-color

A combination of both methods would be fantastic, one for defining variables, and one for direct overrides, like for monitors:

params: NotificationBlock((
        monitor: Hint(monnum),
        ...
)),
notify-send "World" -h string:wired-hint-monnum:2

On the subject of RenderCriteria, I think it would also be good to have some sort of catchall. What I mean by that is, the other problem I have with creating more and more layouts is that I also have to make sure that notifications only render on the layouts I want them to, which means that for regular notifications from other apps, I need to also have render_anti_criteria for each appname that is being used by my other layouts, which results in: render_anti_criteria: [Or([AppName("SystemInfo"),AppName("BspWorkspace"),AppName("VolumeCtrl"),AppName("BrightnessCtrl"),AppName("WifiCon")])], etc. Now imagine having a different stack for each monitor..... fml. So, In that regard, I wonder if there could also be a setting that just says "only render the notification on the first matching criteria" instead of "Render on all matching criteria".

Of course, the even better option to all of this is to use a completely different application to render popups from scripts, saving wired for dbus notifications, but to the best of my knowledge, nothing like that currently exists, and it would have to be a somewhat cut down version of wired anyway to be at all useful. One complaint I do have with continuing to use wired, even if all these types of changes were addressed, is that there is some noticeable delay between sending a notify-send and the notification actually displaying. It's not a huge deal, but while on my desktop it is never more that 1 second, on my laptop it can be up to 2 seconds. Kind of odd for just wanting a popup that tells me what workspace I just switched to. Even weirder is that sometimes there is practically no delay at all! I wonder if it has something to do with sending the notification through notify-send and waiting for wired to see it through dbus, and if there would be a simply way, such as making a new command, that would simply bypass dbus altogether and order wired to create a new notification pop up right now specifically for script use, kind of like how dunst has dunstify.

Edit: as I side note, I have been kind of experimenting with the idea of creating a simple go based app that just opens windows with a message, basically exactly like wired does, specifically for fast script based popups, that way wired could focus solely on being a notification daemon, but I ran into a road block because none of the implementations I found for opening windows would allow me to bypass the window manager. With rust, you have winit, which gives you that control, but nobody has really made any good xcb ports for rust that offer that level of control yet.... maybe it's time I really learn how rust works....

Toqozz commented 2 years ago

Thanks for the detailed response, super helpful. There's definitely a lot to chew on in terms of ideas here. Please note that I'm working on Wired just in my free time so nothing is guaranteed and progress may be slow.

I saw an issue opened a while back about wanting to be able to change text color. While I personally would want all my color values to be controlled by one config file instead of in multiple scripts, I could totally see a syntax similar to color: Color(hint: "textColor"), and setting it like -h string:wired-textColor:666d81 in notify-send. Even better would be the ability to define custom variables in the config like:

I think .ron and Serde are becoming a fairly big issue at this point. Too many compromises have been made to fit with them, and I think yes, we definitely need to support things like variables which isn't possible (in a sane way) in the existing formats. The problem here is I really don't want to parse a custom language or anything like that. There must be a way to technically do this but I don't know it yet.

So, In that regard, I wonder if there could also be a setting that just says "only render the notification on the first matching criteria" instead of "Render on all matching criteria".

Good idea. Invariably people will want that for some notifications and not others though, not sure how to marry the two there. Maybe the layout itself could "consume" the notification, which would stop it propagating to other layouts. You'd want to have some kind of ordering then though.

Of course, the even better option to all of this is to use a completely different application to render popups from scripts, saving wired for dbus notifications, but to the best of my knowledge, nothing like that currently exists,

Have you looked at https://github.com/elkowar/eww? Maybe that could be an option.

One complaint I do have with continuing to use wired, even if all these types of changes were addressed, is that there is some noticeable delay between sending a notify-send and the notification actually displaying.

You can perhaps try changing the timeout here: https://github.com/Toqozz/wired-notify/blob/a91ba83f314a6922858436828befa3b07ffea323/src/bus/dbus.rs#L196 I should probably make that configurable.

Additionally, for notifications with images, it can take a long time to resize them depending on the size of the image and the scaling algorithm chosen. Also if the image is large it takes a long time to pipe it through dbus. Improving the performance in those 2 areas aren't really under my control.

...but I ran into a road block because none of the implementations I found for opening windows would allow me to bypass the window manager.

Technically you only need bindings for X11/Wayland and you can do whatever you want -- maybe https://github.com/0persianman/x-go-binding for X11? Winit doesn't actually provide anything special, in fact I had to do some pull requests to get it to support the correct things to make notification windows. Wired doesn't actually bypass the window manager, it just provides the correct hints to tell the window manager not to move the window around.

I have a really old blog post that goes into detail on how to do it in C. The hints you want are mainly _NET_WM_WINDOW_TYPE_NOTIFICATION and _NET_WM_WINDOW_TYPE_UTILITY.

Moonlight63 commented 2 years ago

OK, great. That's a lot of good information on how things actually work. I did look at eww previously, but couldn't really figure out how to make a pop-up that had no mouse interaction and that would auto close after some time. At least, I didn't find any examples of anyone doing that. Maybe I'll look at it for more than 10 minutes this time and see if I can figure it out. Besides that, wired works great for this, it's just cumbersome. I think making these changes would improve thing quite a bit, but yes it would be a significant breaking change, and I can see how the '.ron' format is limiting. IMO, it would still be worth investigating. I know there are other config file types that have support for things like variables and templates. I've seen implementations of yaml that do, but that could just come down to the parser. I'll do some more research.