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

wm/win: fix handling of windows with multiple window types #1307

Merged
merged 1 commit into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
* Type and format specifiers are no longer used in rules. These specifiers are what you put after the colon (':') in rules, e.g. the `:32c` in `"_GTK_FRAME_EXTENTS@:32c"`. Now this information is ignored and the property is matched regardless of format or type.
* `backend` is now a required option. picom will not start if one is not specified explicitly.
* New predefined target for conditions: `group_focused`. This target indicate whether the focused window is in the same window group as the window being matched.
* Meaning of `window_type` in conditions changed slightly, now it supports windows with multiple types. (However the behavior of `wintypes` remains unchanged.)

## Deprecated features

Expand Down
8 changes: 6 additions & 2 deletions man/picom.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ Supported predefined targets are: :::
Whether the window bounding shape only has rounded corners, and is otherwise rectangular. This implies <<c2-bounding-shaped>>. Requires <<detect-rounded-corners>>. This has no relation to <<corner-radius>>.

`window_type`::::
Window type, as defined by _pass:[_]NET_WM_WINDOW_TYPE_. Name only, e.g. _normal_ means _pass:[_]NET_WM_WINDOW_TYPE_NORMAL_.
Window type, as defined by _pass:[_]NET_WM_WINDOW_TYPE_. Name only, e.g. _normal_ means _pass:[_]NET_WM_WINDOW_TYPE_NORMAL_. Because a window can have multiple types, testing for equality succeeds if any of the window's types match.

`name`::::
Name of the window. This is either _pass:[_]NET_WM_NAME_ or _pass:[_]WM_NAME_.
Expand Down Expand Up @@ -538,7 +538,7 @@ When `@include` directive is used in the config file, picom will first search fo

picom uses general libconfig configuration file format. A sample configuration file is available as `picom.sample.conf` in the source tree. Most of command line switches can be used as options in configuration file as well. For example, *--vsync* option documented above can be set in the configuration file using `vsync = `. Command line options will always overwrite the settings in the configuration file.

Window-type-specific settings allow you to set window-specific options based on the window type. These settings are exposed only in configuration file. Using this is discouraged, see the xref:_window_rules[*WINDOW RULES*] section for the recommended way to set window-specific options. The format of this option is as follows:
Window-type-specific settings allow you to set window-specific options based on the window type. These settings are exposed only in configuration file. The format of this option is as follows:

[#wintypes]
------------
Expand All @@ -548,6 +548,10 @@ wintypes:
};
------------

WARNING: Using this is highly discouraged, see the xref:_window_rules[*WINDOW RULES*] section for the recommended way to set window-specific options.

IMPORTANT: According to the window manager specification, a window can have multiple types. But due to the limitation of how _wintypes_ was implemented, if a window has multiple types, then for the purpose of applying `wintypes` options, one of the window types will be chosen at random. Again, you are recommended to use xref:_window_rules[*WINDOW RULES*] instead.

_WINDOW_TYPE_ is one of the 15 window types defined in EWMH standard: "unknown", "desktop", "dock", "toolbar", "menu", "utility", "splash", "dialog", "normal", "dropdown_menu", "popup_menu", "tooltip", "notification", "combo", and "dnd".

Following per window-type options are available: ::
Expand Down
14 changes: 11 additions & 3 deletions src/c2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1712,14 +1712,22 @@
// A predefined target
const char *predef_target = NULL;
if (leaf->predef != C2_L_PUNDEFINED) {
if (leaf->predef == C2_L_PWINDOWTYPE) {
for (unsigned i = 0; i < NUM_WINTYPES; i++) {
if (w->window_types & (1 << i) &&
c2_string_op(leaf, WINTYPES[i].name)) {
return true;

Check warning on line 1719 in src/c2.c

View check run for this annotation

Codecov / codecov/patch

src/c2.c#L1716-L1719

Added lines #L1716 - L1719 were not covered by tests
}
}
return false;

Check warning on line 1722 in src/c2.c

View check run for this annotation

Codecov / codecov/patch

src/c2.c#L1722

Added line #L1722 was not covered by tests
}

switch (leaf->predef) {
case C2_L_PWINDOWTYPE:
predef_target = WINTYPES[w->window_type].name;
break;
case C2_L_PNAME: predef_target = w->name; break;
case C2_L_PCLASSG: predef_target = w->class_general; break;
case C2_L_PCLASSI: predef_target = w->class_instance; break;
case C2_L_PROLE: predef_target = w->role; break;
case C2_L_PWINDOWTYPE:

Check warning on line 1730 in src/c2.c

View check run for this annotation

Codecov / codecov/patch

src/c2.c#L1730

Added line #L1730 was not covered by tests
default: unreachable();
}
if (!predef_target) {
Expand Down
20 changes: 20 additions & 0 deletions src/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
# define __has_include(x) 0
#endif

#ifndef __has_builtin
# define __has_builtin(x) 0
#endif

#if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>)
# include <threads.h>
#elif __STDC_VERSION__ >= 201112L
Expand All @@ -137,3 +141,19 @@ typedef unsigned int uint;
static inline int attr_const popcntul(unsigned long a) {
return __builtin_popcountl(a);
}

/// Get the index of the lowest bit set in a number. The result is undefined if
/// `a` is 0.
static inline int attr_const index_of_lowest_one(unsigned a) {
#if __has_builtin(__builtin_ctz)
return __builtin_ctz(a);
#else
auto lowbit = (a & -a);
int r = (lowbit & 0xAAAAAAAA) != 0;
r |= ((lowbit & 0xCCCCCCCC) != 0) << 1;
r |= ((lowbit & 0xF0F0F0F0) != 0) << 2;
r |= ((lowbit & 0xFF00FF00) != 0) << 3;
r |= ((lowbit & 0xFFFF0000) != 0) << 4;
return r;
#endif
}
26 changes: 23 additions & 3 deletions src/dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,33 @@

append(Mapped, bool_variant, w->state == WSTATE_MAPPED);
append(Id, wid_variant, win_id(w));
append(Type, string_variant, WINTYPES[w->window_type].name);
append(RawFocused, bool_variant,
w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused);
append(ClientWin, wid_variant, win_client_id(w, /*fallback_to_self=*/true));
append(Leader, wid_variant, wm_ref_win_id(wm_ref_leader(w->tree_ref)));
append_win_property(Name, name, string_variant);

if (!strcmp("Type", target)) {

Check warning on line 614 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L614

Added line #L614 was not covered by tests
DBusMessageIter iter, sub;
dbus_message_iter_init_append(reply, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;

Check warning on line 618 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L616-L618

Added lines #L616 - L618 were not covered by tests
}
for (int i = 0; i < NUM_WINTYPES; i++) {
if ((w->window_types & (1 << i)) == 0) {
continue;

Check warning on line 622 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L620-L622

Added lines #L620 - L622 were not covered by tests
}
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING,
&WINTYPES[i].name)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;

Check warning on line 626 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L624-L626

Added lines #L624 - L626 were not covered by tests
}
}
if (!dbus_message_iter_close_container(&iter, &sub)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;

Check warning on line 630 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L629-L630

Added lines #L629 - L630 were not covered by tests
}
return DBUS_HANDLER_RESULT_HANDLED;

Check warning on line 632 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L632

Added line #L632 was not covered by tests
}

if (!strcmp("Next", target)) {
cdbus_window_t next_id = 0;
auto below = wm_ref_below(cursor);
Expand Down Expand Up @@ -736,7 +756,7 @@
append_win_property(mode, enum);
append_win_property(opacity, double);
append_win_property(ever_damaged, boolean);
append_win_property(window_type, enum);
append_win_property(window_types, enum);

Check warning on line 759 in src/dbus.c

View check run for this annotation

Codecov / codecov/patch

src/dbus.c#L759

Added line #L759 was not covered by tests
append_win_property(name, string);
append_win_property(class_instance, string);
append_win_property(class_general, string);
Expand Down Expand Up @@ -1188,7 +1208,7 @@
" <property type='b' name='RawFocused' access='read'/>\n"
" <property type='b' name='Mapped' access='read'/>\n"
" <property type='s' name='Name' access='read'/>\n"
" <property type='s' name='Type' access='read'/>\n"
" <property type='as' name='Type' access='read'/>\n"
" </interface>\n"
"</node>\n";
// clang-format on
Expand Down
8 changes: 6 additions & 2 deletions src/inspect.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,12 @@
if (w->role != NULL) {
printf(" role = '%s'\n", w->role);
}
if (w->window_type != WINTYPE_UNKNOWN) {
printf(" window_type = '%s'\n", WINTYPES[w->window_type].name);
if (w->window_types != 0) {
for (int i = 0; i < NUM_WINTYPES; i++) {
if (w->window_types & (1 << i)) {
printf(" window_type = '%s'\n", WINTYPES[i].name);

Check warning on line 286 in src/inspect.c

View check run for this annotation

Codecov / codecov/patch

src/inspect.c#L283-L286

Added lines #L283 - L286 were not covered by tests
}
}
}
printf(" %sfullscreen\n", w->is_fullscreen ? "" : "! ");
if (w->bounding_shaped) {
Expand Down
55 changes: 30 additions & 25 deletions src/wm/win.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
}
// Use wintype_focus, and treat WM windows and override-redirected
// windows specially
if (ps->o.wintype_option[w->window_type].focus ||
if (ps->o.wintype_option[index_of_lowest_one(w->window_types)].focus ||
(ps->o.mark_wmwin_focused && is_wmwin) ||
(ps->o.mark_ovredir_focused && wm_ref_client_of(w->tree_ref) == NULL && !is_wmwin) ||
(w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
Expand Down Expand Up @@ -655,24 +655,26 @@
return bounding_shaped;
}

static wintype_t
wid_get_prop_wintype(struct x_connection *c, struct atom *atoms, xcb_window_t wid) {
static uint32_t
wid_get_prop_window_types(struct x_connection *c, struct atom *atoms, xcb_window_t wid) {
winprop_t prop =
x_get_prop(c, wid, atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32);

static_assert(NUM_WINTYPES <= 32, "too many window types");

uint32_t ret = 0;
for (unsigned i = 0; i < prop.nitems; ++i) {
for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
if (get_atom_with_nul(atoms, WINTYPES[j].atom, c->c) ==
(xcb_atom_t)prop.p32[i]) {
free_winprop(&prop);
return j;
if (get_atom_with_nul(atoms, WINTYPES[j].atom, c->c) == prop.atom[i]) {
ret |= (1 << j);
break;

Check warning on line 670 in src/wm/win.c

View check run for this annotation

Codecov / codecov/patch

src/wm/win.c#L668-L670

Added lines #L668 - L670 were not covered by tests
}
}
}

free_winprop(&prop);

return WINTYPE_UNKNOWN;
return ret;
}

// XXX should distinguish between frame has alpha and window body has alpha
Expand Down Expand Up @@ -745,12 +747,13 @@
return 0;
}
// Try obeying opacity property and window type opacity firstly
auto window_type = index_of_lowest_one(w->window_types);
if (w->has_opacity_prop) {
opacity = ((double)w->opacity_prop) / OPAQUE;
} else if (!safe_isnan(w->options.opacity)) {
opacity = w->options.opacity;
} else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) {
opacity = ps->o.wintype_option[w->window_type].opacity;
} else if (!safe_isnan(ps->o.wintype_option[window_type].opacity)) {
opacity = ps->o.wintype_option[window_type].opacity;

Check warning on line 756 in src/wm/win.c

View check run for this annotation

Codecov / codecov/patch

src/wm/win.c#L756

Added line #L756 was not covered by tests
} else {
// Respect active_opacity only when the window is physically
// focused
Expand Down Expand Up @@ -840,7 +843,7 @@
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
if (!ps->o.wintype_option[w->window_type].shadow) {
if (!ps->o.wintype_option[index_of_lowest_one(w->window_types)].shadow) {
log_debug("Shadow disabled by wintypes");
w->options.shadow = TRI_FALSE;
} else if (c2_match(ps->c2_state, w, ps->o.shadow_blacklist, NULL)) {
Expand Down Expand Up @@ -887,8 +890,9 @@
}

static void win_determine_clip_shadow_above(session_t *ps, struct win *w) {
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL));
bool should_crop =
(ps->o.wintype_option[index_of_lowest_one(w->window_types)].clip_shadow_above ||
c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL));
w->options.clip_shadow_above = should_crop ? TRI_TRUE : TRI_UNKNOWN;
}

Expand Down Expand Up @@ -918,7 +922,7 @@

bool blur_background_new = ps->o.blur_method != BLUR_METHOD_NONE;
if (blur_background_new) {
if (!ps->o.wintype_option[w->window_type].blur_background) {
if (!ps->o.wintype_option[index_of_lowest_one(w->window_types)].blur_background) {
log_debug("Blur background disabled by wintypes");
w->options.blur_background = TRI_FALSE;
} else if (c2_match(ps->c2_state, w, ps->o.blur_background_blacklist, NULL)) {
Expand Down Expand Up @@ -1029,8 +1033,10 @@
// on the focused state of the window
win_update_is_fullscreen(ps, w);

assert(w->window_types != 0);
if (ps->o.rules == NULL) {
bool focused = win_is_focused(ps, w);
auto window_type = index_of_lowest_one(w->window_types);
// Universal rules take precedence over wintype_option and
// other exclusion/inclusion lists. And it also supersedes
// some of the "override" options.
Expand All @@ -1056,7 +1062,7 @@
}
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL)) {
if (ps->o.wintype_option[w->window_type].redir_ignore) {
if (ps->o.wintype_option[window_type].redir_ignore) {

Check warning on line 1065 in src/wm/win.c

View check run for this annotation

Codecov / codecov/patch

src/wm/win.c#L1065

Added line #L1065 was not covered by tests
w->options.unredir = WINDOW_UNREDIR_PASSIVE;
} else {
w->options.unredir = WINDOW_UNREDIR_TERMINATE;
Expand All @@ -1067,7 +1073,7 @@
// look different after unredirecting. Instead we always follow
// the request.
w->options.unredir = WINDOW_UNREDIR_FORCED;
} else if (ps->o.wintype_option[w->window_type].redir_ignore) {
} else if (ps->o.wintype_option[window_type].redir_ignore) {
w->options.unredir = WINDOW_UNREDIR_WHEN_POSSIBLE;
}

Expand All @@ -1078,7 +1084,7 @@
w->options.transparent_clipping = TRI_FALSE;
}
w->options.full_shadow =
tri_from_bool(ps->o.wintype_option[w->window_type].full_shadow);
tri_from_bool(ps->o.wintype_option[window_type].full_shadow);
} else {
struct win_update_rule_params params = {
.w = w,
Expand Down Expand Up @@ -1136,27 +1142,27 @@
* Update window type.
*/
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct win *w) {
const wintype_t wtype_old = w->window_type;
const uint32_t wtypes_old = w->window_types;
auto wid = win_client_id(w, /*fallback_to_self=*/true);

// Detect window type here
w->window_type = wid_get_prop_wintype(c, atoms, wid);
w->window_types = wid_get_prop_window_types(c, atoms, wid);

// Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take
// override-redirect windows or windows without WM_TRANSIENT_FOR as
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG.
if (WINTYPE_UNKNOWN == w->window_type) {
if (w->window_types == 0) {
if (w->a.override_redirect ||
!wid_has_prop(c->c, wid, atoms->aWM_TRANSIENT_FOR)) {
w->window_type = WINTYPE_NORMAL;
w->window_types = (1 << WINTYPE_NORMAL);
} else {
w->window_type = WINTYPE_DIALOG;
w->window_types = (1 << WINTYPE_DIALOG);

Check warning on line 1159 in src/wm/win.c

View check run for this annotation

Codecov / codecov/patch

src/wm/win.c#L1159

Added line #L1159 was not covered by tests
}
}

log_debug("Window (%#010x) has type %s", win_id(w), WINTYPES[w->window_type].name);
log_debug("Window (%#010x) has type %#x", win_id(w), w->window_types);

return w->window_type != wtype_old;
return w->window_types != wtypes_old;
}

/**
Expand Down Expand Up @@ -1253,7 +1259,6 @@
// change

.mode = WMODE_TRANS,
.window_type = WINTYPE_UNKNOWN,
.opacity_prop = OPAQUE,
.opacity_set = 1,
.frame_extents = MARGIN_INIT,
Expand Down
5 changes: 3 additions & 2 deletions src/wm/win.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ struct win {
bool in_openclose;

// Client window related members
/// Type of the window.
wintype_t window_type;
/// A bitflag of window types. According to ICCCM, a window can have more than one
/// type.
uint32_t window_types;

// Blacklist related members
/// Name of the window.
Expand Down