Skip to content

Commit

Permalink
First attempt: Make rounded win corners
Browse files Browse the repository at this point in the history
  • Loading branch information
Javyre committed Sep 13, 2018
1 parent ded0397 commit 94ba788
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ VERSION := $(shell $(VERCMD) || cat VERSION)
CPPFLAGS += -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\"
CFLAGS += -std=c99 -pedantic -Wall -Wextra
LDFLAGS ?=
LDLIBS = $(LDFLAGS) -lm -lxcb -lxcb-util -lxcb-keysyms -lxcb-icccm -lxcb-ewmh -lxcb-randr -lxcb-xinerama
LDLIBS = $(LDFLAGS) -lm -lxcb -lxcb-util -lxcb-keysyms -lxcb-icccm -lxcb-ewmh -lxcb-randr -lxcb-xinerama -lxcb-shape

PREFIX ?= /usr/local
BINPREFIX ?= $(PREFIX)/bin
Expand Down
112 changes: 112 additions & 0 deletions src/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#include "parse.h"
#include "window.h"

#include <xcb/shape.h>

void schedule_window(xcb_window_t win)
{
coordinates_t loc;
Expand Down Expand Up @@ -404,6 +406,108 @@ void draw_border(node_t *n, bool focused_node, bool focused_monitor)
}
}

void window_rounded_border(xcb_window_t win)
{
// Check for compatibility
const xcb_query_extension_reply_t *shape_query;
xcb_shape_query_extents_cookie_t extents_cookie;
xcb_shape_query_extents_reply_t *extents;

shape_query = xcb_get_extension_data (dpy, &xcb_shape_id);
if (!shape_query->present) return;

// get geometry
xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL);
if (geo == NULL) return;

uint16_t w = geo->width;
uint16_t h = geo->height;
uint16_t bw = geo->border_width;
free(geo);

xcb_pixmap_t bpid = xcb_generate_id(dpy);
xcb_pixmap_t cpid = xcb_generate_id(dpy);

xcb_create_pixmap(dpy, 1, bpid, win, w+2*bw, h+2*bw);
xcb_create_pixmap(dpy, 1, cpid, win, w, h);

xcb_gcontext_t bblack = xcb_generate_id(dpy);
xcb_gcontext_t bwhite = xcb_generate_id(dpy);
xcb_gcontext_t cblack = xcb_generate_id(dpy);
xcb_gcontext_t cwhite = xcb_generate_id(dpy);

xcb_create_gc(dpy, bblack, bpid,
XCB_GC_FOREGROUND,
//(uint32_t[]){screen->black_pixel, 0});
(uint32_t[]){0, 0});
xcb_create_gc(dpy, bwhite, bpid,
XCB_GC_FOREGROUND,
//(uint32_t[]){screen->white_pixel, 0});
(uint32_t[]){1, 0});
xcb_create_gc(dpy, cblack, cpid,
XCB_GC_FOREGROUND,
//(uint32_t[]){screen->black_pixel, 0});
(uint32_t[]){0, 0});
xcb_create_gc(dpy, cwhite, cpid,
XCB_GC_FOREGROUND,
//(uint32_t[]){screen->white_pixel, 0});
(uint32_t[]){1, 0});

This comment has been minimized.

Copy link
@psychon

psychon Sep 16, 2018

Why so many GCs? Two and two of them are the same, and you could reconfigure the GC with xcb_change_gc instead of creating another one, thus doing everything with just a single GC.
This would not make any measurable effects, but would mean that you use less XIDs, thus the very unlikely event of running through the assigned XID space becomes even less likely.

This comment has been minimized.

Copy link
@Javyre

Javyre Sep 16, 2018

Author Owner

I agree. This commit is very messy as I was experimenting with many combinations of attempts to make the code work as I was not very familiar with xcb and it's documentation is nearly none.

This should be fixed in next commit.


int32_t rad, dia;

rad = 40; dia = rad*2;

xcb_arc_t barcs[] = {
{ 0, 0, dia, dia, 0, 360 << 6 },
{ 0, h-dia, dia, dia, 0, 360 << 6 },
{ w-dia, 0, dia, dia, 0, 360 << 6 },
{ w-dia, h-dia, dia, dia, 0, 360 << 6 },
};
xcb_rectangle_t brects[] = {
{ rad, 0, w-dia, h },
{ 0, rad, w, h-dia },
};

rad -= bw; dia = rad*2;

xcb_arc_t carcs[] = {
{ bw, bw, dia, dia, 0, 360 << 6 },
{ bw, h-dia, dia, dia, 0, 360 << 6 },
{ w-dia, 0, dia, dia, 0, 360 << 6 },
{ w-dia, h-dia, dia, dia, 0, 360 << 6 },
};
xcb_rectangle_t crects[] = {
{ bw+rad, bw, w-dia-bw, h },
{ bw, bw+rad, w, h-dia-bw },
};

xcb_rectangle_t bounding = {-bw, -bw, w+2*bw, h+2*bw};
//xcb_rectangle_t bounding = {-bw, -bw, w, h};
xcb_poly_fill_rectangle(dpy, bpid, bblack, 1, &bounding);
xcb_poly_fill_rectangle(dpy, bpid, bwhite, 2, brects);
xcb_poly_fill_arc(dpy, bpid, bwhite, 4, barcs);

xcb_rectangle_t clipping = {0, 0, w, h};
xcb_poly_fill_rectangle(dpy, cpid, cblack, 1, &clipping);
xcb_poly_fill_rectangle(dpy, cpid, cwhite, 2, crects);
xcb_poly_fill_arc(dpy, cpid, cwhite, 4, carcs);

xcb_shape_mask(
dpy,
0 /*Bounding*/,
0 /*ShapeSet*/,

This comment has been minimized.

Copy link
@psychon

psychon Sep 16, 2018

Why not XCB_SHAPE_SK_BOUNDING and XCB_SHAPE_SO_SET instead of these hardcoded numbers?

This comment has been minimized.

Copy link
@Javyre

Javyre Sep 16, 2018

Author Owner

Oh thank you, I couldn't find the proper documentation describing the constant names so I based it off of the protocol spec. I'll make sure to correct this.

This comment has been minimized.

Copy link
@psychon

psychon Sep 16, 2018

I do not have any magic suggestions here either. I just searched for "BOUNDING" in /usr/include/xcb/shape.h and checked that it has value 0.

win, 0, 0, bpid);

xcb_shape_mask(
dpy,
1 /*Clipping*/,
0 /*ShapeSet*/,
win, 0, 0, cpid);

xcb_free_pixmap(dpy, bpid);
xcb_free_pixmap(dpy, cpid);
}

void window_draw_border(xcb_window_t win, uint32_t border_color_pxl)
{
xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl);
Expand Down Expand Up @@ -798,6 +902,8 @@ void window_border_width(xcb_window_t win, uint32_t bw)
{
uint32_t values[] = {bw};
xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);

window_rounded_border(win);
}

void window_move(xcb_window_t win, int16_t x, int16_t y)
Expand All @@ -810,12 +916,18 @@ void window_resize(xcb_window_t win, uint16_t w, uint16_t h)
{
uint32_t values[] = {w, h};
xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values);

window_rounded_border(win);
}

void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h)
{
uint32_t values[] = {x, y, w, h};

window_rounded_border(win);
xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values);

window_rounded_border(win);

This comment has been minimized.

Copy link
@psychon

psychon Sep 16, 2018

Why does this set the border twice?

This comment has been minimized.

Copy link
@Javyre

Javyre Sep 16, 2018

Author Owner

This was a mistake and I'm still experimenting with where to put these calls.
The next commit should make much more sense..

Any pointers on where they should be?

This comment has been minimized.

Copy link
@psychon

psychon Sep 16, 2018

Anything that changes window size needs to update the window shapes as well.

This comment has been minimized.

Copy link
@Javyre

Javyre Sep 17, 2018

Author Owner

yeah I thought so but it isn't so simple to find the exact spots in code where it's necessary and not redundant..
Any specific pointers to begin with?

This comment has been minimized.

Copy link
@psychon

psychon Sep 17, 2018

Uhm... not really. "After calls to xcb_configure_window" would be something that one can grep for.

}

void window_center(monitor_t *m, client_t *c)
Expand Down
1 change: 1 addition & 0 deletions src/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void hide_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n);
void update_colors(void);
void update_colors_in(node_t *n, desktop_t *d, monitor_t *m);
void draw_border(node_t *n, bool focused_node, bool focused_monitor);
void window_rounded_border(xcb_window_t win);
void window_draw_border(xcb_window_t win, uint32_t border_color_pxl);
void adopt_orphans(void);
uint32_t get_border_color(bool focused_node, bool focused_monitor);

This comment has been minimized.

Copy link
@psychon

psychon Sep 18, 2018

Hi again,

I won't be able to send an answer by mail in the next few days, so I'll answer here. Sorry for this weird way of "mailing".

Re: https://lists.freedesktop.org/archives/xcb/2018-September/011147.html

If I understand this question correctly, then you are worried about mouse-clicks being handled as expected, i.e. when one clicks outside of the "rounded corner" but inside of the rectangular window bounds, the mouse click should go to the window below. For this, (if I remember correctly), version 1.1 of the SHAPE extension adds a new kind of shape: In addition to bounding and clip shape, there is also the input shape.

Thus, you could do something like this:

  • Turn bspwm into a reparenting WM (I am assuming it is not yet one)
  • The frame window for reparenting has two childs: The actual window with the client content and another window with the border that is above the content window
  • Draw an antialiased version of rounded corners to some pixmap with the RENDER extension
  • Any pixel with non-zero alpha is the shape of the window that things are reparented to
  • Draw an antialiased version of just the border to some pixmap with the RENDER extension
  • Any pixel with non-zero alpha in this pixmap is used for the bounding shape of the border window
  • Additionally, you actually draw this shape to the border window while honoring alpha (i.e. this is done through the SHAPE extension)

Thus, you end up with the parent window having a shape that includes anything where the window should be visible. The border window only touches pixels where the border is visible thanks to its shape. The anti-aliasing of the shape happens through the actual window content.

Additionally, you could set the input shape of the window that things are reparented to. For example, anything with alpha less than 50% could be made click-through.

I am not quite sure if the above answers your question. However, I am sure that this is way too complicated for anyone to actually want to do this. And of course, if no compositor is running, this fails, since then you cannot actually do any semi-transparency.

Perhaps it is best not to do antialiased rounded corners. :-)

This comment has been minimized.

Copy link
@Javyre

Javyre via email Sep 19, 2018

Author Owner

This comment has been minimized.

Copy link
@Javyre

Javyre via email Sep 19, 2018

Author Owner

This comment has been minimized.

Copy link
@Javyre

Javyre Sep 19, 2018

Author Owner

great that sent twice -_-
I was trying to reply to the GH notification and the mailing list at the same time and it worked but I didn't wait long enough before sending individual emails...

sorry for all the notifications

This comment has been minimized.

Copy link
@psychon

psychon Sep 19, 2018

Just remember how old X11 is and that things like transparency were... "made to work" years later. There are quite some rough edges there. Especially so since none of the "major players" still use window borders.

Expand Down

0 comments on commit 94ba788

Please sign in to comment.