diff --git a/include/ipc.h b/include/ipc.h index ff011750..e417b421 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -29,9 +29,10 @@ enum ipc_command_type { IPC_EVENT_MODE = ((1<<31) | 2), IPC_EVENT_WINDOW = ((1<<31) | 3), IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4), - IPC_EVENT_BINDING = ((1<<31) | 5), - IPC_EVENT_SHUTDOWN = ((1<<31) | 6), - IPC_EVENT_TICK = ((1<<31) | 7), + IPC_EVENT_BACKGROUND_COVERED = ((1<<31) | 5), + IPC_EVENT_BINDING = ((1<<31) | 6), + IPC_EVENT_SHUTDOWN = ((1<<31) | 7), + IPC_EVENT_TICK = ((1<<31) | 8), // sway-specific event types IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20), diff --git a/include/sway/ipc-server.h b/include/sway/ipc-server.h index d4c00942..41e00679 100644 --- a/include/sway/ipc-server.h +++ b/include/sway/ipc-server.h @@ -17,6 +17,7 @@ void ipc_event_workspace(struct sway_workspace *old, void ipc_event_window(struct sway_container *window, const char *change); void ipc_event_barconfig_update(struct bar_config *bar); void ipc_event_bar_state_update(struct bar_config *bar); +void ipc_event_background_covered(struct sway_output *output, bool covered); void ipc_event_mode(const char *mode, bool pango); void ipc_event_shutdown(const char *reason); void ipc_event_binding(struct sway_binding *binding); diff --git a/include/sway/output.h b/include/sway/output.h index 4584ea45..83e3cd7f 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -53,6 +53,8 @@ struct sway_output { bool enabled; list_t *workspaces; + bool background_covered; + struct sway_output_state current; struct wl_listener layout_destroy; diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 58bde20c..1fcd5017 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -40,6 +40,9 @@ struct sway_workspace { enum sway_container_layout layout; enum sway_container_layout prev_split_layout; + bool background_covered; + bool pending_background_check; + struct side_gaps current_gaps; int gaps_inner; struct side_gaps gaps_outer; @@ -47,6 +50,7 @@ struct sway_workspace { struct sway_output *output; // NULL if no outputs are connected list_t *floating; // struct sway_container list_t *tiling; // struct sway_container + list_t *sorted; // struct sway_container list_t *output_priority; bool urgent; @@ -89,6 +93,8 @@ bool workspace_is_visible(struct sway_workspace *ws); bool workspace_is_empty(struct sway_workspace *ws); +void workspace_check_background_covered(struct sway_workspace *ws); + void workspace_output_raise_priority(struct sway_workspace *workspace, struct sway_output *old_output, struct sway_output *new_output); diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 8f12832a..9869c934 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -702,30 +702,60 @@ static void transaction_apply(struct sway_transaction *transaction) { "(%.1f frames if 60Hz)", transaction, ms, ms / (1000.0f / 60)); } + struct sway_transaction_instruction *instruction; + struct sway_node *node; + struct sway_workspace *ws = NULL; + // Apply the instruction state to the node's current state for (int i = 0; i < transaction->instructions->length; ++i) { - struct sway_transaction_instruction *instruction = - transaction->instructions->items[i]; - struct sway_node *node = instruction->node; + instruction = transaction->instructions->items[i]; + node = instruction->node; switch (node->type) { case N_ROOT: + ws = NULL; break; case N_OUTPUT: apply_output_state(node->sway_output, &instruction->output_state); + ws = node->sway_output->current.active_workspace; break; case N_WORKSPACE: apply_workspace_state(node->sway_workspace, &instruction->workspace_state); + ws = node->sway_workspace; break; case N_CONTAINER: apply_container_state(node->sway_container, &instruction->container_state); + ws = node->sway_container->current.workspace; break; } + if (ws) ws->pending_background_check = true; + node->instruction = NULL; } + + for (int i = 0; i < transaction->instructions->length; ++i) { + instruction = transaction->instructions->items[i]; + node = instruction->node; + + switch (node->type) { + case N_ROOT: + continue; + case N_OUTPUT: + ws = node->sway_output->current.active_workspace; + break; + case N_WORKSPACE: + ws = node->sway_workspace; + break; + case N_CONTAINER: + ws = node->sway_container->current.workspace; + break; + } + + if (ws) workspace_check_background_covered(ws); + } } static void transaction_commit_pending(void); diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 571338a4..f9bdcfae 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -16,6 +16,7 @@ #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "sway/output.h" +#include "sway/layers.h" #include "sway/input/input-manager.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" @@ -360,6 +361,9 @@ static void ipc_json_describe_enabled_output(struct sway_output *output, json_object_object_add(object, "current_workspace", json_object_new_string(ws->name)); + json_object_object_add(object, "covered", + json_object_new_boolean(ws->background_covered)); + json_object *modes_array = json_object_new_array(); struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 7f353c0e..f3093505 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -362,6 +362,20 @@ void ipc_event_bar_state_update(struct bar_config *bar) { json_object_put(json); } +void ipc_event_background_covered(struct sway_output *output, bool covered) { + if (!ipc_has_event_listeners(IPC_EVENT_BACKGROUND_COVERED)) { + return; + } + sway_log(SWAY_DEBUG, "Sending background_covered event"); + + json_object *json = json_object_new_object(); + json_object_object_add(json, "output", json_object_new_string(output->wlr_output->name)); + json_object_object_add(json, "covered", json_object_new_boolean(covered)); + const char *json_string = json_object_to_json_string(json); + ipc_send_event(json_string, IPC_EVENT_BACKGROUND_COVERED); + json_object_put(json); +} + void ipc_event_mode(const char *mode, bool pango) { if (!ipc_has_event_listeners(IPC_EVENT_MODE)) { return; @@ -744,6 +758,8 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); } else if (strcmp(event_type, "bar_state_update") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_BAR_STATE_UPDATE); + } else if (strcmp(event_type, "background_covered") == 0) { + client->subscribed_events |= event_mask(IPC_EVENT_BACKGROUND_COVERED); } else if (strcmp(event_type, "mode") == 0) { client->subscribed_events |= event_mask(IPC_EVENT_MODE); } else if (strcmp(event_type, "shutdown") == 0) { diff --git a/sway/tree/output.c b/sway/tree/output.c index 44b941ca..9004c3cb 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -142,6 +142,7 @@ struct sway_output *output_create(struct wlr_output *wlr_output) { output->workspaces = create_list(); output->current.workspaces = create_list(); + output->background_covered = false; return output; } diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index f8709a4c..1234c1cd 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -88,8 +88,12 @@ struct sway_workspace *workspace_create(struct sway_output *output, ws->layout = output_get_default_layout(output); ws->floating = create_list(); ws->tiling = create_list(); + ws->sorted = create_list(); ws->output_priority = create_list(); + ws->background_covered = false; + ws->pending_background_check = true; + ws->gaps_outer = config->gaps_outer; ws->gaps_inner = config->gaps_inner; if (name) { @@ -153,6 +157,7 @@ void workspace_destroy(struct sway_workspace *workspace) { list_free_items_and_destroy(workspace->output_priority); list_free(workspace->floating); list_free(workspace->tiling); + list_free(workspace->sorted); list_free(workspace->current.floating); list_free(workspace->current.tiling); free(workspace); @@ -616,6 +621,75 @@ bool workspace_is_empty(struct sway_workspace *ws) { return true; } +static bool x_covered_at_y(list_t *containers, double y, double x, double width) +{ + double end = x + width; + double extent = x; + for (int i = 0; i < containers->length; ++i) { + struct sway_container *n = containers->items[i]; + if (!(n->current.y <= y && n->current.y + n->current.height >= y)) { + continue; + } + if (n->current.x > extent) return false; + x = n->current.x + n->current.width; + if (x >= end) return true; + if (x > extent) extent = x; + } + return false; +} + +static int containers_x_compare(const void *a, const void *b) { + return (*(struct sway_container **)a)->current.x - (*(struct sway_container **)b)->current.x; +} + +#define TERMINAL_APP_ID "kitty" + +static void add_con_and_or_children_to_list(list_t *result, list_t *list) +{ + struct sway_container *con; + for (int i = 0; i < list->length; ++i) { + con = list->items[i]; + if (con->current.children) { + add_con_and_or_children_to_list(result, con->current.children); + if (con->current.layout != L_NONE) return; + } + if (!con->view || !con->view->wlr_xdg_toplevel) continue; // Hack. + const char *app_id = view_get_app_id(con->view); + if (app_id == NULL || strcmp(app_id, TERMINAL_APP_ID) != 0) { + list_add(result, con); + } + } +} + +void workspace_check_background_covered(struct sway_workspace *ws) { + if (!ws->output || !ws->pending_background_check) return; + ws->sorted->length = 0; + add_con_and_or_children_to_list(ws->sorted, ws->tiling); + add_con_and_or_children_to_list(ws->sorted, ws->floating); + list_stable_sort(ws->sorted, containers_x_compare); + ws->background_covered = ws->sorted->length > 0; + struct sway_container *con; + for (int i = 0; i < ws->sorted->length; ++i) { + con = ws->sorted->items[i]; + double y = con->current.y - 1.0; + if (y < ws->y) y = ws->y; + double span = con->current.y + con->current.height + 1.0; + if (span > ws->y + ws->height) span = ws->y + ws->height; + bool top = x_covered_at_y(ws->sorted, y, ws->x, ws->width); + if (!top || !x_covered_at_y(ws->sorted, span, ws->x, ws->width)) { + ws->background_covered = false; + break; + } + } + if (ws == ws->output->current.active_workspace) { + if (ws->background_covered != ws->output->background_covered) { + ws->output->background_covered = ws->background_covered; + ipc_event_background_covered(ws->output, ws->output->background_covered); + } + ws->pending_background_check = false; + } +} + static int find_output(const void *id1, const void *id2) { return strcmp(id1, id2); }