Fix focus transfer when clients unmap

116a58f91d9dc075d66586d58360a17229df7a71
Alexis Sellier committed ago 1 parent 3ce65d3d
swm.c +10 -4
1162 1162
        return;
1163 1163
    }
1164 1164
    wlr_xdg_toplevel_send_close(c->surface.xdg->toplevel);
1165 1165
}
1166 1166
1167 -
/* Set the color of every client border. */
1167 +
/* Set the color of every managed client border while its scene exists. */
1168 1168
static inline void client_set_border_color(client_t *c, const float color[static 4]) {
1169 1169
    int i;
1170 1170
1171 +
    if (!c->scene || client_is_unmanaged(c))
1172 +
        return;
1173 +
1171 1174
    for (i = 0; i < 4; i++)
1172 1175
        wlr_scene_rect_set_color(c->border[i], color);
1173 1176
}
1174 1177
1175 1178
/* Notify a client of its fullscreen state. */
2954 2957
            return;
2955 2958
        } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) {
2956 2959
            return;
2957 2960
            /* Don't deactivate old client if the new one wants focus, as this
2958 2961
             * avoids breaking applications such as winecfg. */
2959 -
        } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) {
2962 +
        } else if (
2963 +
            old_c && old_c->scene && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))
2964 +
        ) {
2960 2965
            client_set_border_color(old_c, bordercolor);
2961 2966
            client_activate_surface(old, 0);
2962 2967
        }
2963 2968
    }
2964 2969
    print_status();
5975 5980
void unmap_notify(struct wl_listener *listener, void *data) {
5976 5981
    /* Called when the surface is unmapped, and should no longer be shown. */
5977 5982
    client_t  *c    = wl_container_of(listener, c, unmap);
5978 5983
    client_t  *next = nullptr, *under = nullptr;
5979 5984
    monitor_t *oldmon     = c->mon;
5985 +
    bool       hadfocus   = client_surface(c) == seat->keyboard_state.focused_surface;
5980 5986
    int        wasfocused = !client_is_unmanaged(c) && c == focus_top(c->mon);
5981 5987
5982 5988
    if (c == grabc) {
5983 5989
        if (cursor_mode == CURSOR_RESIZE)
5984 5990
            client_set_resizing(c, 0);
6000 6006
            next = focus_close(c);
6001 6007
        wl_list_remove(&c->link);
6002 6008
        wl_list_remove(&c->flink);
6003 6009
        c->mon = nullptr;
6004 6010
6005 -
        if (wasfocused && !sloppyfocus)
6011 +
        if (hadfocus && !sloppyfocus)
6006 6012
            focus_client(next ? next : focus_top(selmon), 1);
6007 6013
6008 6014
        if (oldmon)
6009 6015
            arrange(oldmon);
6010 6016
    }
6024 6030
    c->resize_edges = WLR_EDGE_NONE;
6025 6031
    /* Hide the old scene before hit-testing, but keep its borders alive until
6026 6032
     * focus_client() has finished deactivating the old surface. */
6027 6033
    wlr_scene_node_set_enabled(&c->scene->node, 0);
6028 6034
6029 -
    if (wasfocused && sloppyfocus) {
6035 +
    if (hadfocus && sloppyfocus) {
6030 6036
        /* Hit-test after hiding the old scene node so focus follows the
6031 6037
         * surface newly exposed beneath the stationary cursor. */
6032 6038
        point_to_node(cursor->x, cursor->y, nullptr, &under, nullptr, nullptr, nullptr);
6033 6039
6034 6040
        if (under && !client_is_unmanaged(under))
test/clients.py +3 -1
201 201
    window.destroy()
202 202
    connection.close()
203 203
204 204
205 205
def xdg_lifecycle(arguments: list[str]) -> None:
206 -
    """Repeatedly map and unmap one XDG toplevel."""
206 +
    """Repeatedly map and unmap a focused XDG toplevel."""
207 207
208 208
    count = int(arguments[0]) if arguments else 8
209 209
    connection = Connection(WlCompositor, WlShm, XdgWmBase)
210 +
    fallback = Window(connection, "swm-xdg-fallback", 0xFF446622)
210 211
    window = Window(connection, "swm-xdg-lifecycle", 0xFF224466)
211 212
    connection.roundtrips(3)
212 213
    for index in range(count):
213 214
        window.unmap()
214 215
        connection.roundtrips(2)
215 216
        window.color += 0x00050311
216 217
        window.surface.commit()
217 218
        connection.roundtrips(2)
218 219
    window.destroy()
220 +
    fallback.destroy()
219 221
    connection.close()
220 222
221 223
222 224
def transient(arguments: list[str]) -> None:
223 225
    """Exercise popup, parented, and fullscreen XDG surfaces."""
test/unit.c +11 -3
207 207
    pool_release(&client_pool, client);
208 208
}
209 209
210 210
/* Verify state-file paths and client border-width policy. */
211 211
static void test_paths_and_border_policy(void) {
212 -
    const char *directory = getenv("SWM_TEST_DIR");
213 -
    client_t    client    = {};
214 -
    char        path[PATH_MAX], expected[PATH_MAX];
212 +
    const char                 *directory = getenv("SWM_TEST_DIR");
213 +
    client_t                    client    = {};
214 +
    struct wlr_scene_tree       scene     = {};
215 +
    struct wlr_xwayland_surface xsurface  = { .override_redirect = true };
216 +
    char                        path[PATH_MAX], expected[PATH_MAX];
215 217
216 218
    assert(directory);
217 219
    assert(snprintf(path, sizeof(path), "%s/state", directory) < (int)sizeof(path));
218 220
    assert(setenv("XDG_STATE_HOME", path, 1) == 0);
219 221
    assert(snprintf(expected, sizeof(expected), "%s/swm/windows", path) < (int)sizeof(expected));
224 226
    assert(
225 227
        snprintf(expected, sizeof(expected), "%s/.local/state/swm/windows", path) <
226 228
        (int)sizeof(expected)
227 229
    );
228 230
    assert(!strcmp(window_state_path(), expected));
231 +
    client_set_border_color(&client, bordercolor);
232 +
    client.type             = X11;
233 +
    client.surface.xwayland = &xsurface;
234 +
    client.scene            = &scene;
235 +
    client_set_border_color(&client, bordercolor);
236 +
    client    = (client_t){};
229 237
    client.bw = 9;
230 238
    assert(client_border_width(&client) == borderwidth);
231 239
    client.is_fullscreen = 1;
232 240
    assert(client_border_width(&client) == 0);
233 241
}