1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-25 14:06:08 -05:00

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

ICCCM states a window can have multiple types, which we didn't handle.

Fixing this also includes a slight change to the dbus interface, I hope
that doesn't break too many people.

Also this is pretty unfixable for `wintypes`, and we are moving to
universal rules anyway, so I didn't bother.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-08-06 06:44:38 +01:00
parent 1cfd2a0d98
commit dff14c538a
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
8 changed files with 100 additions and 37 deletions

View file

@ -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. * 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. * `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. * 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 ## Deprecated features

View file

@ -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>>. 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`::::
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`::::
Name of the window. This is either _pass:[_]NET_WM_NAME_ or _pass:[_]WM_NAME_. Name of the window. This is either _pass:[_]NET_WM_NAME_ or _pass:[_]WM_NAME_.
@ -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. 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] [#wintypes]
------------ ------------
@ -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". _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: :: Following per window-type options are available: ::

View file

@ -1712,14 +1712,22 @@ c2_match_once_leaf_string(struct atom *atoms, const struct win *w, const c2_l_t
// A predefined target // A predefined target
const char *predef_target = NULL; const char *predef_target = NULL;
if (leaf->predef != C2_L_PUNDEFINED) { 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;
}
}
return false;
}
switch (leaf->predef) { 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_PNAME: predef_target = w->name; break;
case C2_L_PCLASSG: predef_target = w->class_general; break; case C2_L_PCLASSG: predef_target = w->class_general; break;
case C2_L_PCLASSI: predef_target = w->class_instance; break; case C2_L_PCLASSI: predef_target = w->class_instance; break;
case C2_L_PROLE: predef_target = w->role; break; case C2_L_PROLE: predef_target = w->role; break;
case C2_L_PWINDOWTYPE:
default: unreachable(); default: unreachable();
} }
if (!predef_target) { if (!predef_target) {

View file

@ -118,6 +118,10 @@
# define __has_include(x) 0 # define __has_include(x) 0
#endif #endif
#ifndef __has_builtin
# define __has_builtin(x) 0
#endif
#if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>) #if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>)
# include <threads.h> # include <threads.h>
#elif __STDC_VERSION__ >= 201112L #elif __STDC_VERSION__ >= 201112L
@ -137,3 +141,19 @@ typedef unsigned int uint;
static inline int attr_const popcntul(unsigned long a) { static inline int attr_const popcntul(unsigned long a) {
return __builtin_popcountl(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
}

View file

@ -605,13 +605,33 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
append(Mapped, bool_variant, w->state == WSTATE_MAPPED); append(Mapped, bool_variant, w->state == WSTATE_MAPPED);
append(Id, wid_variant, win_id(w)); append(Id, wid_variant, win_id(w));
append(Type, string_variant, WINTYPES[w->window_type].name);
append(RawFocused, bool_variant, append(RawFocused, bool_variant,
w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused); w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused);
append(ClientWin, wid_variant, win_client_id(w, /*fallback_to_self=*/true)); 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(Leader, wid_variant, wm_ref_win_id(wm_ref_leader(w->tree_ref)));
append_win_property(Name, name, string_variant); append_win_property(Name, name, string_variant);
if (!strcmp("Type", target)) {
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;
}
for (int i = 0; i < NUM_WINTYPES; i++) {
if ((w->window_types & (1 << i)) == 0) {
continue;
}
if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING,
&WINTYPES[i].name)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
}
if (!dbus_message_iter_close_container(&iter, &sub)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
return DBUS_HANDLER_RESULT_HANDLED;
}
if (!strcmp("Next", target)) { if (!strcmp("Next", target)) {
cdbus_window_t next_id = 0; cdbus_window_t next_id = 0;
auto below = wm_ref_below(cursor); auto below = wm_ref_below(cursor);
@ -736,7 +756,7 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
append_win_property(mode, enum); append_win_property(mode, enum);
append_win_property(opacity, double); append_win_property(opacity, double);
append_win_property(ever_damaged, boolean); append_win_property(ever_damaged, boolean);
append_win_property(window_type, enum); append_win_property(window_types, enum);
append_win_property(name, string); append_win_property(name, string);
append_win_property(class_instance, string); append_win_property(class_instance, string);
append_win_property(class_general, string); append_win_property(class_general, string);
@ -1188,7 +1208,7 @@ static bool cdbus_process_window_introspect(DBusMessage *reply) {
" <property type='b' name='RawFocused' access='read'/>\n" " <property type='b' name='RawFocused' access='read'/>\n"
" <property type='b' name='Mapped' access='read'/>\n" " <property type='b' name='Mapped' access='read'/>\n"
" <property type='s' name='Name' 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" " </interface>\n"
"</node>\n"; "</node>\n";
// clang-format on // clang-format on

View file

@ -280,8 +280,12 @@ int inspect_main(int argc, char **argv, const char *config_file) {
if (w->role != NULL) { if (w->role != NULL) {
printf(" role = '%s'\n", w->role); printf(" role = '%s'\n", w->role);
} }
if (w->window_type != WINTYPE_UNKNOWN) { if (w->window_types != 0) {
printf(" window_type = '%s'\n", WINTYPES[w->window_type].name); for (int i = 0; i < NUM_WINTYPES; i++) {
if (w->window_types & (1 << i)) {
printf(" window_type = '%s'\n", WINTYPES[i].name);
}
}
} }
printf(" %sfullscreen\n", w->is_fullscreen ? "" : "! "); printf(" %sfullscreen\n", w->is_fullscreen ? "" : "! ");
if (w->bounding_shaped) { if (w->bounding_shaped) {

View file

@ -105,7 +105,7 @@ static bool win_is_focused(session_t *ps, struct win *w) {
} }
// Use wintype_focus, and treat WM windows and override-redirected // Use wintype_focus, and treat WM windows and override-redirected
// windows specially // 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_wmwin_focused && is_wmwin) ||
(ps->o.mark_ovredir_focused && wm_ref_client_of(w->tree_ref) == NULL && !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 && (w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
@ -655,24 +655,26 @@ static inline bool win_bounding_shaped(struct x_connection *c, xcb_window_t wid)
return bounding_shaped; return bounding_shaped;
} }
static wintype_t static uint32_t
wid_get_prop_wintype(struct x_connection *c, struct atom *atoms, xcb_window_t wid) { wid_get_prop_window_types(struct x_connection *c, struct atom *atoms, xcb_window_t wid) {
winprop_t prop = winprop_t prop =
x_get_prop(c, wid, atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); 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 (unsigned i = 0; i < prop.nitems; ++i) {
for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
if (get_atom_with_nul(atoms, WINTYPES[j].atom, c->c) == if (get_atom_with_nul(atoms, WINTYPES[j].atom, c->c) == prop.atom[i]) {
(xcb_atom_t)prop.p32[i]) { ret |= (1 << j);
free_winprop(&prop); break;
return j;
} }
} }
} }
free_winprop(&prop); free_winprop(&prop);
return WINTYPE_UNKNOWN; return ret;
} }
// XXX should distinguish between frame has alpha and window body has alpha // XXX should distinguish between frame has alpha and window body has alpha
@ -745,12 +747,13 @@ static double win_calc_opacity_target(session_t *ps, const struct win *w, bool f
return 0; return 0;
} }
// Try obeying opacity property and window type opacity firstly // Try obeying opacity property and window type opacity firstly
auto window_type = index_of_lowest_one(w->window_types);
if (w->has_opacity_prop) { if (w->has_opacity_prop) {
opacity = ((double)w->opacity_prop) / OPAQUE; opacity = ((double)w->opacity_prop) / OPAQUE;
} else if (!safe_isnan(w->options.opacity)) { } else if (!safe_isnan(w->options.opacity)) {
opacity = w->options.opacity; opacity = w->options.opacity;
} else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) { } else if (!safe_isnan(ps->o.wintype_option[window_type].opacity)) {
opacity = ps->o.wintype_option[w->window_type].opacity; opacity = ps->o.wintype_option[window_type].opacity;
} else { } else {
// Respect active_opacity only when the window is physically // Respect active_opacity only when the window is physically
// focused // focused
@ -840,7 +843,7 @@ static void win_determine_shadow(session_t *ps, struct win *w) {
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return; 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"); log_debug("Shadow disabled by wintypes");
w->options.shadow = TRI_FALSE; w->options.shadow = TRI_FALSE;
} else if (c2_match(ps->c2_state, w, ps->o.shadow_blacklist, NULL)) { } else if (c2_match(ps->c2_state, w, ps->o.shadow_blacklist, NULL)) {
@ -887,8 +890,9 @@ bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms
} }
static void win_determine_clip_shadow_above(session_t *ps, struct win *w) { 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 || bool should_crop =
c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL)); (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; w->options.clip_shadow_above = should_crop ? TRI_TRUE : TRI_UNKNOWN;
} }
@ -918,7 +922,7 @@ static void win_determine_blur_background(session_t *ps, struct win *w) {
bool blur_background_new = ps->o.blur_method != BLUR_METHOD_NONE; bool blur_background_new = ps->o.blur_method != BLUR_METHOD_NONE;
if (blur_background_new) { 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"); log_debug("Blur background disabled by wintypes");
w->options.blur_background = TRI_FALSE; w->options.blur_background = TRI_FALSE;
} else if (c2_match(ps->c2_state, w, ps->o.blur_background_blacklist, NULL)) { } else if (c2_match(ps->c2_state, w, ps->o.blur_background_blacklist, NULL)) {
@ -1029,8 +1033,10 @@ void win_on_factor_change(session_t *ps, struct win *w) {
// on the focused state of the window // on the focused state of the window
win_update_is_fullscreen(ps, w); win_update_is_fullscreen(ps, w);
assert(w->window_types != 0);
if (ps->o.rules == NULL) { if (ps->o.rules == NULL) {
bool focused = win_is_focused(ps, w); 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 // Universal rules take precedence over wintype_option and
// other exclusion/inclusion lists. And it also supersedes // other exclusion/inclusion lists. And it also supersedes
// some of the "override" options. // some of the "override" options.
@ -1056,7 +1062,7 @@ void win_on_factor_change(session_t *ps, struct win *w) {
} }
if (w->a.map_state == XCB_MAP_STATE_VIEWABLE && if (w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
c2_match(ps->c2_state, w, ps->o.unredir_if_possible_blacklist, NULL)) { 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) {
w->options.unredir = WINDOW_UNREDIR_PASSIVE; w->options.unredir = WINDOW_UNREDIR_PASSIVE;
} else { } else {
w->options.unredir = WINDOW_UNREDIR_TERMINATE; w->options.unredir = WINDOW_UNREDIR_TERMINATE;
@ -1067,7 +1073,7 @@ void win_on_factor_change(session_t *ps, struct win *w) {
// look different after unredirecting. Instead we always follow // look different after unredirecting. Instead we always follow
// the request. // the request.
w->options.unredir = WINDOW_UNREDIR_FORCED; 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; w->options.unredir = WINDOW_UNREDIR_WHEN_POSSIBLE;
} }
@ -1078,7 +1084,7 @@ void win_on_factor_change(session_t *ps, struct win *w) {
w->options.transparent_clipping = TRI_FALSE; w->options.transparent_clipping = TRI_FALSE;
} }
w->options.full_shadow = 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 { } else {
struct win_update_rule_params params = { struct win_update_rule_params params = {
.w = w, .w = w,
@ -1136,27 +1142,27 @@ void win_on_win_size_change(struct win *w, int shadow_offset_x, int shadow_offse
* Update window type. * Update window type.
*/ */
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct win *w) { 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); auto wid = win_client_id(w, /*fallback_to_self=*/true);
// Detect window type here // 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 // Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take
// override-redirect windows or windows without WM_TRANSIENT_FOR as // override-redirect windows or windows without WM_TRANSIENT_FOR as
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. // _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 || if (w->a.override_redirect ||
!wid_has_prop(c->c, wid, atoms->aWM_TRANSIENT_FOR)) { !wid_has_prop(c->c, wid, atoms->aWM_TRANSIENT_FOR)) {
w->window_type = WINTYPE_NORMAL; w->window_types = (1 << WINTYPE_NORMAL);
} else { } else {
w->window_type = WINTYPE_DIALOG; w->window_types = (1 << WINTYPE_DIALOG);
} }
} }
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;
} }
/** /**
@ -1253,7 +1259,6 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor,
// change // change
.mode = WMODE_TRANS, .mode = WMODE_TRANS,
.window_type = WINTYPE_UNKNOWN,
.opacity_prop = OPAQUE, .opacity_prop = OPAQUE,
.opacity_set = 1, .opacity_set = 1,
.frame_extents = MARGIN_INIT, .frame_extents = MARGIN_INIT,

View file

@ -165,8 +165,9 @@ struct win {
bool in_openclose; bool in_openclose;
// Client window related members // Client window related members
/// Type of the window. /// A bitflag of window types. According to ICCCM, a window can have more than one
wintype_t window_type; /// type.
uint32_t window_types;
// Blacklist related members // Blacklist related members
/// Name of the window. /// Name of the window.