diff --git a/client.h b/client.h index bea7a97..123a584 100644 --- a/client.h +++ b/client.h @@ -98,6 +98,8 @@ toplevel_from_wlr_surface(struct wlr_surface *s, Client **pc, LayerSurface **pl) xdg_surface = tmp_xdg_surface; break; case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (!xdg_surface->toplevel) + return -1; c = xdg_surface->data; type = c->type; goto end; diff --git a/dwl.c b/dwl.c index e7edaa0..69e4188 100644 --- a/dwl.c +++ b/dwl.c @@ -83,7 +83,6 @@ #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) #define TAGMASK ((1u << TAGCOUNT) - 1) #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) -#define LISTEN_STATIC(E, H, D) do { StaticListener *_l = ecalloc(1, sizeof(*_l)); _l->listener.notify = (H); _l->data = (D); wl_signal_add((E), &_l->listener); } while (0) /* enums */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ @@ -246,6 +245,17 @@ typedef struct { struct wl_listener destroy; } PointerConstraint; +typedef struct { + struct wlr_idle_inhibitor_v1 *inhibitor; + struct wl_listener destroy; +} IdleInhibitor; + +typedef struct { + struct wlr_xdg_popup *popup; + struct wl_listener commit; + struct wl_listener destroy; +} Popup; + typedef struct { const char *id; const char *title; @@ -263,12 +273,6 @@ typedef struct { struct wl_listener destroy; } SessionLock; -typedef struct { - /* Must keep this field first */ - struct wl_listener listener; - void *data; -} StaticListener; - /* function declarations */ static void applybounds(Client *c, struct wlr_box *bbox); static void applygeom(Client *c); @@ -304,6 +308,7 @@ static void createpopup(struct wl_listener *listener, void *data); static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); static void cursorframe(struct wl_listener *listener, void *data); static void cursorwarptohint(void); +static void destroybackend(struct wl_listener *listener, void *data); static void destroydecoration(struct wl_listener *listener, void *data); static void destroydragicon(struct wl_listener *listener, void *data); static void destroyidleinhibitor(struct wl_listener *listener, void *data); @@ -312,6 +317,7 @@ static void destroylock(SessionLock *lock, int unlocked); static void destroylocksurface(struct wl_listener *listener, void *data); static void destroynotify(struct wl_listener *listener, void *data); static void destroypointerconstraint(struct wl_listener *listener, void *data); +static void destroypopup(struct wl_listener *listener, void *data); static void destroysessionlock(struct wl_listener *listener, void *data); static void destroykeyboardgroup(struct wl_listener *listener, void *data); static Monitor *dirtomon(enum wlr_direction dir); @@ -319,6 +325,7 @@ static void focusclient(Client *c, LayerSurface *l, int lift); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); static Client *focustop(Monitor *m); +static void forgetoffmons(void); static void fullscreennotify(struct wl_listener *listener, void *data); static void gpureset(struct wl_listener *listener, void *data); static void handlesig(int signo); @@ -461,6 +468,7 @@ static struct wl_array ghosts; /* struct wlr_scene_node *, unmapped clients */ static struct wl_array offmons; /* char *, names of outputs turned off by clients */ /* global event handlers */ +static struct wl_listener backend_destroy = {.notify = destroybackend}; static struct wl_listener cursor_axis = {.notify = axisnotify}; static struct wl_listener cursor_button = {.notify = buttonpress}; static struct wl_listener cursor_frame = {.notify = cursorframe}; @@ -855,7 +863,8 @@ cleanup(void) /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. * Destroy it until it's fixed on the wlroots side */ - wlr_backend_destroy(backend); + if (backend) + wlr_backend_destroy(backend); if (transaction_idle) wl_event_source_remove(transaction_idle); @@ -911,6 +920,7 @@ cleanuplisteners(void) wl_list_remove(&new_idle_inhibitor.link); wl_list_remove(&layout_change.link); wl_list_remove(&new_input_device.link); + wl_list_remove(&backend_destroy.link); wl_list_remove(&new_virtual_keyboard.link); wl_list_remove(&new_virtual_pointer.link); wl_list_remove(&new_pointer_constraint.link); @@ -1054,8 +1064,8 @@ commitnotify(struct wl_listener *listener, void *data) void commitpopup(struct wl_listener *listener, void *data) { - StaticListener *sl = wl_container_of(listener, sl, listener); - struct wlr_xdg_popup *popup = sl->data; + Popup *p = wl_container_of(listener, p, commit); + struct wlr_xdg_popup *popup = p->popup; LayerSurface *l = NULL; Client *c = NULL; struct wlr_box box; @@ -1077,8 +1087,7 @@ commitpopup(struct wl_listener *listener, void *data) box.x -= (type == LayerShell ? l->scene->node.x : c->geom.x - c->surface.xdg->geometry.x); box.y -= (type == LayerShell ? l->scene->node.y : c->geom.y - c->surface.xdg->geometry.y); wlr_xdg_popup_unconstrain_from_box(popup, &box); - wl_list_remove(&listener->link); - free(listener); + destroypopup(&p->destroy, NULL); } void @@ -1098,7 +1107,9 @@ void createidleinhibitor(struct wl_listener *listener, void *data) { struct wlr_idle_inhibitor_v1 *idle_inhibitor = data; - LISTEN_STATIC(&idle_inhibitor->events.destroy, destroyidleinhibitor, idle_inhibitor); + IdleInhibitor *i = ecalloc(1, sizeof(*i)); + i->inhibitor = idle_inhibitor; + LISTEN(&idle_inhibitor->events.destroy, &i->destroy, destroyidleinhibitor); checkidleinhibitor(NULL); } @@ -1372,7 +1383,10 @@ createpopup(struct wl_listener *listener, void *data) /* This event is raised when a client (either xdg-shell or layer-shell) * creates a new popup. */ struct wlr_xdg_popup *popup = data; - LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup, popup); + Popup *p = ecalloc(1, sizeof(*p)); + p->popup = popup; + LISTEN(&popup->base->surface->events.commit, &p->commit, commitpopup); + LISTEN(&popup->events.destroy, &p->destroy, destroypopup); } void @@ -1413,6 +1427,21 @@ cursorwarptohint(void) } } +void +destroybackend(struct wl_listener *listener, void *data) +{ + /* wlroots destroys the backend itself when the session goes away (e.g. + * seatd stopped at shutdown), and asserts that its listeners are gone */ + wl_list_remove(&new_output.link); + wl_list_init(&new_output.link); + wl_list_remove(&new_input_device.link); + wl_list_init(&new_input_device.link); + wl_list_remove(&backend_destroy.link); + wl_list_init(&backend_destroy.link); + backend = NULL; + quit(NULL); +} + void destroydecoration(struct wl_listener *listener, void *data) { @@ -1435,12 +1464,11 @@ destroydragicon(struct wl_listener *listener, void *data) void destroyidleinhibitor(struct wl_listener *listener, void *data) { - StaticListener *sl = wl_container_of(listener, sl, listener); - struct wlr_idle_inhibitor_v1 *idle_inhibitor = sl->data; + IdleInhibitor *i = wl_container_of(listener, i, destroy); /* At this point the idle inhibitor is still in the list of the manager */ - checkidleinhibitor(wlr_surface_get_root_surface(idle_inhibitor->surface)); - wl_list_remove(&listener->link); - free(listener); + checkidleinhibitor(wlr_surface_get_root_surface(i->inhibitor->surface)); + wl_list_remove(&i->destroy.link); + free(i); } void @@ -1492,14 +1520,21 @@ destroylocksurface(struct wl_listener *listener, void *data) if (lock_surface->surface != seat->keyboard_state.focused_surface) return; - if (locked && cur_lock && !wl_list_empty(&cur_lock->surfaces)) { - surface = wl_container_of(cur_lock->surfaces.next, surface, link); - client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); - } else if (!locked) { + if (!locked) { focusclient(focustop(selmon), NULL, 1); - } else { - wlr_seat_keyboard_clear_focus(seat); + return; } + + /* The surface being destroyed is still in the list */ + if (cur_lock) { + wl_list_for_each(surface, &cur_lock->surfaces, link) { + if (surface != lock_surface) { + client_notify_enter(surface->surface, wlr_seat_get_keyboard(seat)); + return; + } + } + } + wlr_seat_keyboard_clear_focus(seat); } void @@ -1547,6 +1582,15 @@ destroypointerconstraint(struct wl_listener *listener, void *data) free(pointer_constraint); } +void +destroypopup(struct wl_listener *listener, void *data) +{ + Popup *p = wl_container_of(listener, p, destroy); + wl_list_remove(&p->commit.link); + wl_list_remove(&p->destroy.link); + free(p); +} + void destroysessionlock(struct wl_listener *listener, void *data) { @@ -1666,6 +1710,8 @@ void focusmon(const Arg *arg) { int i = 0, nmons = wl_list_length(&mons); + if (!selmon) + return; if (nmons) { do /* don't switch to disabled mons */ selmon = dirtomon(arg->i); @@ -1706,18 +1752,42 @@ focusstack(const Arg *arg) Client * focustop(Monitor *m) { - Client *c; + Client *c, *fs = NULL; - wl_list_for_each(c, &fstack, flink) - if (VISIBLEON(c, m) && c->isfullscreen) - return c; + wl_list_for_each(c, &fstack, flink) { + if (VISIBLEON(c, m) && c->isfullscreen) { + fs = c; + break; + } + } + /* A fullscreen client's children are shown above it */ wl_list_for_each(c, &fstack, flink) - if (VISIBLEON(c, m)) + if (VISIBLEON(c, m) && (!fs || c == fs || client_get_parent(c) == fs)) return c; return NULL; } +void +forgetoffmons(void) +{ + /* Forget the outputs that were unplugged while off, or they would stay + * off when plugged in again */ + char **name, **kept = offmons.data; + Monitor *m; + + wl_array_for_each(name, &offmons) { + wl_list_for_each(m, &mons, link) + if (!strcmp(*name, m->wlr_output->name)) + break; + if (&m->link != &mons) + *kept++ = *name; + else + free(*name); + } + offmons.size = (char *)kept - (char *)offmons.data; +} + void fullscreennotify(struct wl_listener *listener, void *data) { @@ -2272,8 +2342,12 @@ outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test) for (i = 0; i < states_len; i++) { struct wlr_swapchain *swapchain = wlr_output_swapchain_manager_get_swapchain( &swapchain_manager, states[i].output); - if (swapchain && !states[i].output->enabled) - wlr_output_state_set_buffer(&states[i].base, wlr_swapchain_acquire(swapchain)); + struct wlr_buffer *buffer; + if (swapchain && !states[i].output->enabled + && (buffer = wlr_swapchain_acquire(swapchain))) { + wlr_output_state_set_buffer(&states[i].base, buffer); + wlr_buffer_unlock(buffer); + } } if (!(ok = wlr_backend_commit(backend, states, states_len))) @@ -2405,6 +2479,8 @@ powermgrsetmode(struct wl_listener *listener, void *data) if (!m) return; + if (event->mode) + forgetoffmons(); m->gamma_lut_changed = 1; /* Reapply gamma LUT when re-enabling the output */ wlr_output_state_set_enabled(&state, event->mode); /* The output was reconnected while off and was never modeset */ @@ -2749,6 +2825,7 @@ setup(void) * if an X11 server is running. */ if (!(backend = wlr_backend_autocreate(event_loop, &session))) die("couldn't create backend"); + wl_signal_add(&backend->events.destroy, &backend_destroy); /* Initialize the scene graph used to lay out windows */ scene = wlr_scene_create(); @@ -3142,7 +3219,7 @@ void transactionapply(void) { struct wlr_scene_node **ghost; - Client *c; + Client *c, *p; Monitor *m; wl_event_source_timer_update(transaction_timer, 0); @@ -3164,7 +3241,8 @@ transactionapply(void) wl_list_for_each(m, &mons, link) { if (m->wlr_output->enabled) wlr_scene_node_set_enabled(&m->fullscreen_bg->node, - (c = focustop(m)) && c->isfullscreen); + (c = focustop(m)) && (c->isfullscreen + || ((p = client_get_parent(c)) && p->isfullscreen))); } motionnotify(0, NULL, 0, 0, 0, 0);