dunst-project/dunst

Double Borders/frames?

Barbaross93 opened this issue ยท 31 comments

I apologize as this isn't an issue ; more of a question/request. I was wondering if its possible to setup double borders around the notification window? I'm aware of the settings allowing for one frame color, but I haven't found a way to set two frame colors. I currently use a tool called chwb2 from wmutils (https://github.com/wmutils/opt) to set double borders on normal windows, however, it works by accepting a window id. At least in bspwm, I can't seem to get a window id of the notification window (xprop doesn't return any wid).

Installation info

  • Version: v1.4.1-118-g0de8610
  • Install type: dunst-git from AUR
  • Distro and version: Arch Linux

you can edit the source code , editing the render_background function in draw.c and learning a bit of cairo for how to will do it, should be a rather easy addition

I'm not sure exactly what you mean by double border (a screenshot would help). Dunst uses an override redirect window so I'm not sure how much luck you'd have with trying to mess with it.

1595506336

Hopefully this make it a little clearer; My windows have two border colors, a lighter green/grey inner border (depending on whether the window is focused or not) and a dark outer color. If I can't get window properties on dunst's windows then editing the source code is probably my only hope :(

So, I have zero background in C and therefore have no clue what I'm doing, but I tried looking at and studied the function @kushraj suggested. Here are my edits to it (which I 100% fully expect to not work):

static cairo_surface_t *render_background(cairo_surface_t *srf,
                                          struct colored_layout *cl,
                                          struct colored_layout *cl_next,
                                          int y,
                                          int width,
                                          int height,
                                          int corner_radius,
                                          bool first,
					  bool second,
					  bool penultimate,
                                          bool last,
                                          int *ret_width)
{
        int x = 0;
        int radius_int = corner_radius;

        cairo_t *c = cairo_create(srf);

        /* stroke area doesn't intersect with main area */
        cairo_set_fill_rule(c, CAIRO_FILL_RULE_EVEN_ODD);

        /* for correct combination of adjacent areas */
        cairo_set_operator(c, CAIRO_OPERATOR_ADD);

        if (first)
                height += settings.frame_width;
	if (second)
		height += settings.frame_width_outer;
	if (penultimate)
		height += settings.frame_width;
        if (last)
                height += settings.frame_width_outer;
        else
                height += settings.separator_height;

        draw_rounded_rect(c, x, y, width, height, corner_radius, first, last);

        /* adding frame */
        x += settings.frame_width;
        if (first) {
                y += settings.frame_width;
                height -= settings.frame_width;
        }

        width -= 2 * settings.frame_width;

	x += settings.frame_width_outer;
	if (second) {
		y += settings.frame_width;
		height -= settings.frame_width_outer;
	}

	width -= 2 * settings.frame_width_outer;

	if (penultimate) {
		height -= settings.frame_width;
	else
		height -= settings.separator_height;
	}

        if (last)
                height -= settings.frame_width_outer;
        else
                height -= settings.separator_height;

        radius_int = frame_internal_radius(corner_radius, settings.frame_width, height);
	radius_int = frame_internal_radius(corner_radius, settings.frame_width_outer, height);

        draw_rounded_rect(c, x, y, width, height, radius_int, first, last);
        cairo_set_source_rgba(c, cl->frame_outer.r, cl->frame_outer.g, cl->frame_outer.b, cl->frame_outer.a);
        cairo_fill(c);

        draw_rounded_rect(c, x, y, width, height, radius_int, first, last);
        cairo_set_source_rgba(c, cl->frame.r, cl->frame.g, cl->frame.b, cl->frame.a);
        cairo_fill(c);

        draw_rounded_rect(c, x, y, width, height, radius_int, first, last);
        cairo_set_source_rgba(c, cl->bg.r, cl->bg.g, cl->bg.b, cl->bg.a);
        cairo_fill(c);

        cairo_set_operator(c, CAIRO_OPERATOR_SOURCE);

        if (   settings.sep_color.type != SEP_FRAME
            && settings.separator_height > 0
            && !last) {
                struct color sep_color = layout_get_sepcolor(cl, cl_next);
                cairo_set_source_rgba(c, sep_color.r, sep_color.g, sep_color.b, sep_color.a);

                cairo_rectangle(c, settings.frame_width, y + height, width, settings.separator_height);

                cairo_fill(c);
        }

        cairo_destroy(c);

        if (ret_width)
                *ret_width = width;

        return cairo_surface_create_for_rectangle(srf, x, y, width, height);
}

if either @kushraj, @tsipinakis, or anyone who stumbles upon this issue would be willing to guide me through this, I'll try to implement it. If you'd prefer I'd do this in the form of a PR, let me know. I didn't think it would be worth it at this point since I'm 100% certain that this won't work in its current state.

Have you tried running it? It's not that hard to build Dunst.
I don't think we'll accept it as a PR now, since the drawing code is already quite messy, and you'll likely only want double borders if all windows have double borders.
But I'm happy to help you a bit too get it done :).

Well, based on your most excellent suggestion @fwsmit, simply compiling dunst gave me super helpful messages of where I needed to correct things. I spent a few hours and realized that the necessary changes are much simpler than I thought. All I had to do was define an outer width frame and simply add/subtract it to where frame_width were called. However, the thing I'm stuck on is how do I color just the outer frame? I'm able to compile what I have, but I get segfaults whenever a notification is sent. I tried adding a colors.frame_outer to notification.h struct notification_colors after adding struct color frame_outer to struct colored_layout in draw.c and cl->frame_outer = string_to_color(n->colors.frame_outer); on line 339 in draw.c. I'll see if I can make a fork and push a commit there to make this clearer

EDIT:
Here's my fork: https://github.com/Barbarossa93/dunst
Any ideas @fwsmit?

yeah, is it really necessary to make a commit ? I actually did the same thing some time before, its somewhat messy implementation so I just left it separate. could send you that code part if you want it ?

I just thought a commit would make it easier to understand what I was describing. I didn't feel like I was making much sense... but I guess that's probably because I don't fully understand what I'm doing?

In any case, yeah, I'd love to see what you have @kushraj

sure, I will ping you tomorrow

here, you can use this version

Thanks @kushraj! That was a huge help. My code no longer seg faults, and I feel I'm incredibly close to the end product; The problems I have now are that the inner border color isn't quite right. It's a bit lighter than it should be. Additionally, I have the inner border running in between the notifications when ideally they wouldn't be there at all. Could someone take a look at my latest changes over at my fork? I've spent hours and can't make any further headway with this. I think if there's a way to tell cairo to overwrite rather than add colors that would solve the first problem; secondly, if I can create a proper second frame, I shouldn't see the inner borders running between the notifications.

EDIT:
Wow, just occurred to me to change the cairo operator. The colors are now how they are suppose to be! The separator is still not applied correctly, and the inner frame color runs between notifications, but at least the colors are right. Still would appreciate some help here

Actually my code works just fine ( for dual borders ), you will have to use some new options in config to setup the borders properly.
Also you might want to clone and build the code again, I edited it a bit

I made some other changes too, frame is now for every notification separately and inner border is now of same color as average color of icon which you can toggle off in config file

I shouldn't see the inner borders running between the notifications

are you talking about separator color ? you can set that to transparent

I think you misunderstood @kushraj, I'm not using your fork. I'm continuing to improve mine. While your fork works indeed, I don't quite like your implementation. Even with your latest changes, I don't see a separator between the notifications, I see a small gap. You also have the double borders running around each notification. What I want is to have it run around the entire notification window. Here's a screenshot of yours:
1613307450
Here's a screenshot of mine:
1613307513
The problem I have with mine currently is that the separator is overlapped by the inner frame. I can't figure out how to make it draw as a window wide border rather than a per notification border

oh yeah, I actually wanted all my notifications separately with individual borders, that's exactly what I implemented.

Because of picom, shadow is applied to entire window making them seem connected. Not a problem for me as I'm using sway, ( no shadows) so it looks good in mine.

guess, I can take a look at your fork

I'd appreciate it if you would :)

An easy way to make sure you don't draw the border in between notifications is to only draw the left and right line, and only if it's the first or last notification, draw the top or bottom line. That's the same way rounded corners are drawn.
Notice that a bool is passed you can used to see if it's the first or last notification:

static cairo_surface_t *render_background(cairo_surface_t *srf,
                                          struct colored_layout *cl,
                                          struct colored_layout *cl_next,
                                          int y,
                                          int width,
                                          int height,
                                          int corner_radius,
                                          bool first,
                                          bool last,
                                          int *ret_width)

Hmm, I thought my border drawing logic was ok. I thought my problem was the painting bit. I'll take another stab at the border drawing section. Should I maybe incorporate the painting logic into the drawing section?

I'm not sure what you mean by painting logic. What may be causing you issue is that draw_rounded_rect is drawing a filled in rectangle of that color. On line 612 you draw that rectangle with yellow color. I suspect that one isn't drawn over by something else, leaving those lines there. I'm not very sure though

Thanks for the hint @fwsmit, I think I finally figured it out!!
1613364862
I think I need to do some more strenuous testing; as I'm pretty sure dynamic width won't work since I didn't factor in the outer border width, but for the moment, this should work! I plan to do some more cleanup and renaming the options to something more reasonable (like @kushraj's setting naming) and I think that should be it!

Welp, the problem now is that if the separator height is significantly larger than the inner frame border, there's a gap on the sides for the inner frame. Not sure how to proceed =/

EDIT:
Nevermind, turned out to be a simple fix!

Just to be clear, we won't accept this feature in dunst right now. It's probably too specific for it to be worth the added complexity. If more users are interested in this feature being added to Dunst, please comment.
Good to hear that your fork is working though

I'm just going to comment here to say... this feature seems great, especially for the ricing community. It certainly leads me to wonder if providing an input for a variable to determine the number of borders would be simple enough. Then when someone comes along asking about three borders it would already be possible.

And how would you select the color for the borders then?

While I think it is a cool idea, I do see this adding a bit more in terms of code complexity. Since that's what @fwsmit is trying to avoid, I'm not sure how feasible this would be.

In terms of the configuration, maybe specify a list of width and colors based on a configured number of frames? So something like:

number_of_frames = 3
frame_width = 2,6,1
frame_color = '#000000,#ffffff,#cccccc'

No idea how complicated this would be to implement, but again, I do like it.

Yeah I though something like that as well. It wouldn't be terribly hard to implement.
The problem is that I feel like an arbitrary number of border seems like such a niche feature, whole still being pretty complicated.
If you want to see multiple borders I would suggest using @Barbarossa93's fork. You can even contribute a more than 2 borders patch there.

Yeah, I'd be more than happy for PRs at my fork for this sort of thing. I think it'd require a bit of a rewrite of what I currently have, but if someone wants to implement it, than by all means go for it.

Also, it seems like a niche feature... until more people know about it ;)

I have literally zero experience with C (not that that's an excuse) and almost none with git (also, not an excuse) but when I have some time I'll try to get my hands dirty on this :3

I guess from my perspective, doing something like this isn't about added complexity - it's about added modularity (which, of course, usually comes with more lines of code). But, hey, that's why forks exist I suppose!

It be great if its merged, the ricing comunity will love it

Just wanted to pitch in that this would indeed be an awesome feature for the ricing community!

i am really really interested in this feature, would be cool for my 2 border theme on bspwm

I wouldn't mind having a little bit of a tool that we could use along the lines of css statements like border-style: double. Sadly I lack the knowledge to implement it so I can just ask. Apologies.