/* 获取或创建指定 monitor 某个 tag 的 scroller 状态 */ static struct TagScrollerState *ensure_scroller_state(Monitor *m, uint32_t tag) { if (!m->pertag->scroller_state[tag]) { struct TagScrollerState *st = calloc(1, sizeof(struct TagScrollerState)); m->pertag->scroller_state[tag] = st; } return m->pertag->scroller_state[tag]; } /* 在 tag 状态中查找客户端对应的节点(无则返回 NULL) */ static struct ScrollerStackNode *find_scroller_node(struct TagScrollerState *st, Client *c) { if (!st) return NULL; for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) if (n->client == c) return n; return NULL; } /* 创建一个新节点并插入到 tag 状态的 all 链表中 */ static struct ScrollerStackNode * scroller_node_create(struct TagScrollerState *st, Client *c) { struct ScrollerStackNode *n = calloc(1, sizeof(*n)); n->client = c; n->scroller_proportion = c->scroller_proportion; n->stack_proportion = c->stack_proportion; n->scroller_proportion_single = c->scroller_proportion_single; n->next_in_stack = NULL; n->prev_in_stack = NULL; n->all_next = st->all_first; st->all_first = n; st->count++; return n; } /* 从 tag 状态中移除一个节点并释放 */ static void scroller_node_remove(struct TagScrollerState *st, struct ScrollerStackNode *target) { if (!st || !target) return; /* 保存邻居 */ struct ScrollerStackNode *prev = target->prev_in_stack; struct ScrollerStackNode *next = target->next_in_stack; /* 从堆叠链表中摘除 */ if (prev) prev->next_in_stack = next; if (next) next->prev_in_stack = prev; /* 清空目标客户端的堆叠指针,使其彻底脱离 */ if (target->client) { target->client->prev_in_stack = NULL; target->client->next_in_stack = NULL; } /* 从 all 链表摘除 */ struct ScrollerStackNode **indirect = &st->all_first; while (*indirect && *indirect != target) indirect = &(*indirect)->all_next; if (*indirect == target) { *indirect = target->all_next; st->count--; } free(target); } /* 清空一个 tag 的全部 scroller 状态 */ static void clear_scroller_state(struct TagScrollerState *st) { if (!st) return; struct ScrollerStackNode *n = st->all_first; while (n) { struct ScrollerStackNode *next = n->all_next; free(n); n = next; } free(st); } /* 在 Monitor 销毁时清理所有 tag 的 scroller 状态 */ static void cleanup_monitor_scroller(Monitor *m) { for (int t = 0; t < LENGTH(tags) + 1; t++) { if (m->pertag->scroller_state[t]) { clear_scroller_state(m->pertag->scroller_state[t]); m->pertag->scroller_state[t] = NULL; } } } /* 将某个 tag 的状态同步回所有客户端的全局字段 */ static void sync_scroller_state_to_clients(Monitor *m, uint32_t tag) { struct TagScrollerState *st = m->pertag->scroller_state[tag]; if (!st) return; for (struct ScrollerStackNode *n = st->all_first; n; n = n->all_next) { Client *c = n->client; c->scroller_proportion = n->scroller_proportion; c->stack_proportion = n->stack_proportion; c->scroller_proportion_single = n->scroller_proportion_single; c->prev_in_stack = n->prev_in_stack ? n->prev_in_stack->client : NULL; c->next_in_stack = n->next_in_stack ? n->next_in_stack->client : NULL; } } void vertical_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { Monitor *m = c->mon; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; cur_gappiv = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappiv; cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; if (c->isfullscreen) { target_geom->width = m->m.width; target_geom->height = m->m.height; target_geom->x = m->m.x; return; } if (c->ismaximizescreen) { target_geom->width = m->w.width - 2 * cur_gappoh; target_geom->height = m->w.height - 2 * cur_gappov; target_geom->x = m->w.x + cur_gappoh; return; } target_geom->width = m->w.width - 2 * cur_gappoh; target_geom->x = m->w.x + (m->w.width - target_geom->width) / 2; } void vertical_check_scroller_root_inside_mon(Client *c, struct wlr_box *geometry) { if (!GEOMINSIDEMON(geometry, c->mon)) { geometry->y = c->mon->w.y + (c->mon->w.height - geometry->height) / 2; } } void horizontal_scroll_adjust_fullandmax(Client *c, struct wlr_box *target_geom) { Monitor *m = c->mon; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; cur_gappih = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappih; cur_gappoh = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappoh; cur_gappov = config.smartgaps && m->visible_scroll_tiling_clients == 1 ? 0 : cur_gappov; if (c->isfullscreen) { target_geom->height = m->m.height; target_geom->width = m->m.width; target_geom->y = m->m.y; return; } if (c->ismaximizescreen) { target_geom->height = m->w.height - 2 * cur_gappov; target_geom->width = m->w.width - 2 * cur_gappoh; target_geom->y = m->w.y + cur_gappov; return; } target_geom->height = m->w.height - 2 * cur_gappov; target_geom->y = m->w.y + (m->w.height - target_geom->height) / 2; } void horizontal_check_scroller_root_inside_mon(Client *c, struct wlr_box *geometry) { if (!GEOMINSIDEMON(geometry, c->mon)) { geometry->x = c->mon->w.x + (c->mon->w.width - geometry->width) / 2; } } void arrange_stack_node(struct ScrollerStackNode *head, struct wlr_box geometry, int32_t gappiv) { int32_t stack_size = 0; struct ScrollerStackNode *iter = head; while (iter) { stack_size++; iter = iter->next_in_stack; } if (stack_size == 0) return; /* 归一化比例 */ float total_proportion = 0.0f; iter = head; while (iter) { if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) iter->stack_proportion = stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); total_proportion += iter->stack_proportion; iter = iter->next_in_stack; } iter = head; while (iter) { iter->stack_proportion /= total_proportion; iter = iter->next_in_stack; } /* 竖向排列(水平堆叠) */ int32_t client_height; int32_t current_y = geometry.y; int32_t remain_client_height = geometry.height - (stack_size - 1) * gappiv; float remain_proportion = 1.0f; iter = head; while (iter) { client_height = remain_client_height * (iter->stack_proportion / remain_proportion); struct wlr_box client_geom = {.x = geometry.x, .y = current_y, .width = geometry.width, .height = client_height}; resize(iter->client, client_geom, 0); remain_proportion -= iter->stack_proportion; remain_client_height -= client_height; current_y += client_height + gappiv; iter = iter->next_in_stack; } } void arrange_stack_vertical_node(struct ScrollerStackNode *head, struct wlr_box geometry, int32_t gappih) { int32_t stack_size = 0; struct ScrollerStackNode *iter = head; while (iter) { stack_size++; iter = iter->next_in_stack; } if (stack_size == 0) return; /* 归一化比例 */ float total_proportion = 0.0f; iter = head; while (iter) { if (iter->stack_proportion <= 0.0f || iter->stack_proportion >= 1.0f) iter->stack_proportion = stack_size == 1 ? 1.0f : 1.0f / (stack_size - 1); total_proportion += iter->stack_proportion; iter = iter->next_in_stack; } iter = head; while (iter) { iter->stack_proportion /= total_proportion; iter = iter->next_in_stack; } /* 横向排列(垂直堆叠) */ int32_t client_width; int32_t current_x = geometry.x; int32_t remain_client_width = geometry.width - (stack_size - 1) * gappih; float remain_proportion = 1.0f; iter = head; while (iter) { client_width = remain_client_width * (iter->stack_proportion / remain_proportion); struct wlr_box client_geom = {.y = geometry.y, .x = current_x, .height = geometry.height, .width = client_width}; resize(iter->client, client_geom, 0); remain_proportion -= iter->stack_proportion; remain_client_width -= client_width; current_x += client_width + gappih; iter = iter->next_in_stack; } } void scroller(Monitor *m) { uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = ensure_scroller_state(m, tag); /* 收集当前可见的所有 scroller 平铺窗口 */ Client *vis[512]; int32_t count = 0; Client *c; wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISSCROLLTILED(c)) vis[count++] = c; if (count == 512) break; } /* 移除不再可见的节点 */ struct ScrollerStackNode *n = st->all_first; while (n) { bool found = false; for (int i = 0; i < count; i++) { if (vis[i] == n->client) { found = true; break; } } struct ScrollerStackNode *next = n->all_next; if (!found) scroller_node_remove(st, n); n = next; } /* 为新的可见窗口创建节点,并尝试恢复堆叠关系 */ for (int i = 0; i < count; i++) { if (!find_scroller_node(st, vis[i])) { struct ScrollerStackNode *new_node = scroller_node_create(st, vis[i]); Client *prev = vis[i]->prev_in_stack; if (prev) { struct ScrollerStackNode *prev_node = find_scroller_node(st, prev); if (prev_node) { new_node->prev_in_stack = prev_node; prev_node->next_in_stack = new_node; } } } } /* 按全局客户端链表顺序收集所有堆叠头,确保视觉顺序正确 */ struct ScrollerStackNode *heads[64]; int32_t n_heads = 0; wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { struct ScrollerStackNode *node = find_scroller_node(st, c); if (node && !node->prev_in_stack) { bool already = false; for (int k = 0; k < n_heads; k++) { if (heads[k] == node) { already = true; break; } } if (!already) heads[n_heads++] = node; } } } if (n_heads == 0) { sync_scroller_state_to_clients(m, tag); return; } m->visible_scroll_tiling_clients = n_heads; int32_t cur_gappih = enablegaps ? m->gappih : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; if (config.smartgaps && n_heads == 1) { cur_gappih = cur_gappoh = cur_gappov = 0; } int32_t max_client_width = m->w.width - 2 * config.scroller_structs - cur_gappih; /* 单客户端特例 */ if (n_heads == 1 && !config.scroller_ignore_proportion_single && !heads[0]->client->isfullscreen && !heads[0]->client->ismaximizescreen) { struct ScrollerStackNode *head = heads[0]; float single_proportion = head->scroller_proportion_single > 0.0f ? head->scroller_proportion_single : config.scroller_default_proportion_single; struct wlr_box target_geom; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = (m->w.width - 2 * cur_gappoh) * single_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; horizontal_check_scroller_root_inside_mon(head->client, &target_geom); arrange_stack_node(head, target_geom, cur_gappiv); sync_scroller_state_to_clients(m, tag); return; } struct ScrollerStackNode *root_node = NULL; if (m->sel && ISSCROLLTILED(m->sel)) { root_node = find_scroller_node(st, m->sel); if (root_node) { while (root_node->prev_in_stack) root_node = root_node->prev_in_stack; } } if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { root_node = find_scroller_node(st, m->prevsel); if (root_node) { while (root_node->prev_in_stack) root_node = root_node->prev_in_stack; } } if (!root_node) root_node = heads[n_heads / 2]; /* 简单回退 */ int32_t focus_index = -1; for (int i = 0; i < n_heads; i++) { if (heads[i] == root_node) { focus_index = i; break; } } if (focus_index < 0) focus_index = n_heads / 2; /* 判断是否需要滚动、overspread、center */ bool need_scroller = false; bool over_overspread_to_left = false; Client *root_client = root_node->client; if (root_client->geom.x >= m->w.x + config.scroller_structs && root_client->geom.x + root_client->geom.width <= m->w.x + m->w.width - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; } bool need_apply_overspread = config.scroller_prefer_overspread && n_heads > 1 && (focus_index == 0 || focus_index == n_heads - 1) && heads[focus_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { if (focus_index == 0) { over_overspread_to_left = true; } else { over_overspread_to_left = false; } if (over_overspread_to_left && (!INSIDEMON(heads[1]->client) || (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= 1.0f))) { need_scroller = true; } else if (!over_overspread_to_left && (!INSIDEMON(heads[focus_index - 1]->client) || (heads[focus_index - 1]->scroller_proportion + heads[focus_index]->scroller_proportion >= 1.0f))) { need_scroller = true; } else { need_apply_overspread = false; } } bool need_apply_center = config.scroller_focus_center || n_heads == 1 || (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_width) + (heads[focus_index]->scroller_proportion * max_client_width) > m->w.width - 2 * config.scroller_structs - cur_gappih))); if (n_heads == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } if (start_drag_window) need_scroller = false; struct wlr_box target_geom; target_geom.height = m->w.height - 2 * cur_gappov; target_geom.width = max_client_width * heads[focus_index]->scroller_proportion; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; horizontal_scroll_adjust_fullandmax(heads[focus_index]->client, &target_geom); if (heads[focus_index]->client->isfullscreen) { target_geom.x = m->m.x; horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); } else if (heads[focus_index]->client->ismaximizescreen) { target_geom.x = m->w.x + cur_gappoh; horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); } else if (need_scroller) { if (need_apply_center) { target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; } else if (need_apply_overspread) { if (over_overspread_to_left) { target_geom.x = m->w.x + config.scroller_structs; } else { target_geom.x = m->w.x + (m->w.width - heads[focus_index]->scroller_proportion * max_client_width - config.scroller_structs); } } else { target_geom.x = root_client->geom.x > m->w.x + (m->w.width) / 2 ? m->w.x + (m->w.width - heads[focus_index]->scroller_proportion * max_client_width - config.scroller_structs) : m->w.x + config.scroller_structs; } horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); } else { target_geom.x = root_client->geom.x; horizontal_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_node(heads[focus_index], target_geom, cur_gappiv); } /* 排列左侧的堆叠 */ for (int i = 1; i <= focus_index; i++) { struct ScrollerStackNode *cur = heads[focus_index - i]; struct wlr_box left_geom; left_geom.height = m->w.height - 2 * cur_gappov; left_geom.width = max_client_width * cur->scroller_proportion; horizontal_scroll_adjust_fullandmax(cur->client, &left_geom); left_geom.x = heads[focus_index - i + 1]->client->geom.x - cur_gappih - left_geom.width; arrange_stack_node(cur, left_geom, cur_gappiv); } /* 排列右侧的堆叠 */ for (int i = 1; i < n_heads - focus_index; i++) { struct ScrollerStackNode *cur = heads[focus_index + i]; struct wlr_box right_geom; right_geom.height = m->w.height - 2 * cur_gappov; right_geom.width = max_client_width * cur->scroller_proportion; horizontal_scroll_adjust_fullandmax(cur->client, &right_geom); right_geom.x = heads[focus_index + i - 1]->client->geom.x + cur_gappih + heads[focus_index + i - 1]->client->geom.width; arrange_stack_node(cur, right_geom, cur_gappiv); } sync_scroller_state_to_clients(m, tag); } void vertical_scroller(Monitor *m) { uint32_t tag = m->pertag->curtag; struct TagScrollerState *st = ensure_scroller_state(m, tag); Client *vis[512]; int32_t count = 0; Client *c; wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISSCROLLTILED(c)) vis[count++] = c; if (count == 512) break; } struct ScrollerStackNode *n = st->all_first; while (n) { bool found = false; for (int i = 0; i < count; i++) { if (vis[i] == n->client) { found = true; break; } } struct ScrollerStackNode *next = n->all_next; if (!found) scroller_node_remove(st, n); n = next; } for (int i = 0; i < count; i++) { if (!find_scroller_node(st, vis[i])) { struct ScrollerStackNode *new_node = scroller_node_create(st, vis[i]); Client *prev = vis[i]->prev_in_stack; if (prev) { struct ScrollerStackNode *prev_node = find_scroller_node(st, prev); if (prev_node) { new_node->prev_in_stack = prev_node; prev_node->next_in_stack = new_node; } } } } /* 按全局顺序收集堆叠头 */ struct ScrollerStackNode *heads[64]; int32_t n_heads = 0; wl_list_for_each(c, &clients, link) { if (VISIBLEON(c, m) && ISSCROLLTILED(c)) { struct ScrollerStackNode *node = find_scroller_node(st, c); if (node && !node->prev_in_stack) { bool already = false; for (int k = 0; k < n_heads; k++) if (heads[k] == node) already = true; if (!already) heads[n_heads++] = node; } } } if (n_heads == 0) { sync_scroller_state_to_clients(m, tag); return; } m->visible_scroll_tiling_clients = n_heads; int32_t cur_gappiv = enablegaps ? m->gappiv : 0; int32_t cur_gappov = enablegaps ? m->gappov : 0; int32_t cur_gappoh = enablegaps ? m->gappoh : 0; int32_t cur_gappih = enablegaps ? m->gappih : 0; if (config.smartgaps && n_heads == 1) { cur_gappiv = cur_gappov = cur_gappoh = 0; } int32_t max_client_height = m->w.height - 2 * config.scroller_structs - cur_gappiv; if (n_heads == 1 && !config.scroller_ignore_proportion_single && !heads[0]->client->isfullscreen && !heads[0]->client->ismaximizescreen) { struct ScrollerStackNode *head = heads[0]; float single_proportion = head->scroller_proportion_single > 0.0f ? head->scroller_proportion_single : config.scroller_default_proportion_single; struct wlr_box target_geom; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = (m->w.height - 2 * cur_gappov) * single_proportion; target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; vertical_check_scroller_root_inside_mon(head->client, &target_geom); arrange_stack_vertical_node(head, target_geom, cur_gappih); sync_scroller_state_to_clients(m, tag); return; } struct ScrollerStackNode *root_node = NULL; if (m->sel && ISSCROLLTILED(m->sel)) { root_node = find_scroller_node(st, m->sel); if (root_node) { while (root_node->prev_in_stack) root_node = root_node->prev_in_stack; } } if (!root_node && m->prevsel && ISSCROLLTILED(m->prevsel)) { root_node = find_scroller_node(st, m->prevsel); if (root_node) { while (root_node->prev_in_stack) root_node = root_node->prev_in_stack; } } if (!root_node) root_node = heads[n_heads / 2]; int32_t focus_index = -1; for (int i = 0; i < n_heads; i++) { if (heads[i] == root_node) { focus_index = i; break; } } if (focus_index < 0) focus_index = n_heads / 2; bool need_scroller = false; bool over_overspread_to_up = false; Client *root_client = root_node->client; if (root_client->geom.y >= m->w.y + config.scroller_structs && root_client->geom.y + root_client->geom.height <= m->w.y + m->w.height - config.scroller_structs) { need_scroller = false; } else { need_scroller = true; } bool need_apply_overspread = config.scroller_prefer_overspread && n_heads > 1 && (focus_index == 0 || focus_index == n_heads - 1) && heads[focus_index]->scroller_proportion < 1.0f; if (need_apply_overspread) { if (focus_index == 0) { over_overspread_to_up = true; } else { over_overspread_to_up = false; } if (over_overspread_to_up && (!INSIDEMON(heads[1]->client) || (heads[1]->scroller_proportion + heads[0]->scroller_proportion >= 1.0f))) { need_scroller = true; } else if (!over_overspread_to_up && (!INSIDEMON(heads[focus_index - 1]->client) || (heads[focus_index - 1]->scroller_proportion + heads[focus_index]->scroller_proportion >= 1.0f))) { need_scroller = true; } else { need_apply_overspread = false; } } bool need_apply_center = config.scroller_focus_center || n_heads == 1 || (config.scroller_prefer_center && !need_apply_overspread && (!m->prevsel || (ISSCROLLTILED(m->prevsel) && (m->prevsel->scroller_proportion * max_client_height) + (heads[focus_index]->scroller_proportion * max_client_height) > m->w.height - 2 * config.scroller_structs - cur_gappiv))); if (n_heads == 1 && config.scroller_ignore_proportion_single) { need_scroller = true; } if (start_drag_window) need_scroller = false; struct wlr_box target_geom; target_geom.width = m->w.width - 2 * cur_gappoh; target_geom.height = max_client_height * heads[focus_index]->scroller_proportion; target_geom.x = m->w.x + (m->w.width - target_geom.width) / 2; vertical_scroll_adjust_fullandmax(heads[focus_index]->client, &target_geom); if (heads[focus_index]->client->isfullscreen) { target_geom.y = m->m.y; vertical_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_vertical_node(heads[focus_index], target_geom, cur_gappih); } else if (heads[focus_index]->client->ismaximizescreen) { target_geom.y = m->w.y + cur_gappov; vertical_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_vertical_node(heads[focus_index], target_geom, cur_gappih); } else if (need_scroller) { if (need_apply_center) { target_geom.y = m->w.y + (m->w.height - target_geom.height) / 2; } else if (need_apply_overspread) { if (over_overspread_to_up) { target_geom.y = m->w.y + config.scroller_structs; } else { target_geom.y = m->w.y + (m->w.height - heads[focus_index]->scroller_proportion * max_client_height - config.scroller_structs); } } else { target_geom.y = root_client->geom.y > m->w.y + (m->w.height) / 2 ? m->w.y + (m->w.height - heads[focus_index]->scroller_proportion * max_client_height - config.scroller_structs) : m->w.y + config.scroller_structs; } vertical_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_vertical_node(heads[focus_index], target_geom, cur_gappih); } else { target_geom.y = root_client->geom.y; vertical_check_scroller_root_inside_mon(heads[focus_index]->client, &target_geom); arrange_stack_vertical_node(heads[focus_index], target_geom, cur_gappih); } for (int i = 1; i <= focus_index; i++) { struct ScrollerStackNode *cur = heads[focus_index - i]; struct wlr_box up_geom; up_geom.width = m->w.width - 2 * cur_gappoh; up_geom.height = max_client_height * cur->scroller_proportion; vertical_scroll_adjust_fullandmax(cur->client, &up_geom); up_geom.y = heads[focus_index - i + 1]->client->geom.y - cur_gappiv - up_geom.height; arrange_stack_vertical_node(cur, up_geom, cur_gappih); } for (int i = 1; i < n_heads - focus_index; i++) { struct ScrollerStackNode *cur = heads[focus_index + i]; struct wlr_box down_geom; down_geom.width = m->w.width - 2 * cur_gappoh; down_geom.height = max_client_height * cur->scroller_proportion; vertical_scroll_adjust_fullandmax(cur->client, &down_geom); down_geom.y = heads[focus_index + i - 1]->client->geom.y + cur_gappiv + heads[focus_index + i - 1]->client->geom.height; arrange_stack_vertical_node(cur, down_geom, cur_gappih); } sync_scroller_state_to_clients(m, tag); } void scroller_remove_client(Client *c) { Monitor *m; wl_list_for_each(m, &mons, link) { for (uint32_t t = 0; t < LENGTH(tags) + 1; t++) { struct TagScrollerState *st = m->pertag->scroller_state[t]; if (!st) continue; struct ScrollerStackNode *node = find_scroller_node(st, c); if (node) { scroller_node_remove(st, node); } } } }