Allow resizing tiled windows
8fcdc95a442dfff66c8f8fbbddce51c6ae3c4c13
1 parent
b52eb82e
config.def.h
+2 -0
| 91 | 91 | static const int repeat_rate = 40; |
|
| 92 | 92 | /* Wait this many milliseconds before a held key starts repeating. */ |
|
| 93 | 93 | static const int repeat_delay = 180; |
|
| 94 | 94 | ||
| 95 | 95 | /* Pointer and touchpad behavior. Unsupported options are ignored per device. */ |
|
| 96 | + | /* Resize a tiled split within this distance from its boundary, in pixels. */ |
|
| 97 | + | static const unsigned tiled_resize_margin = 4; |
|
| 96 | 98 | /* Tap the touchpad to produce a click. */ |
|
| 97 | 99 | static const bool tap_to_click = true; |
|
| 98 | 100 | /* Keep a tapped item grabbed while the finger moves. */ |
|
| 99 | 101 | static const bool tap_and_drag = true; |
|
| 100 | 102 | /* Keep dragging after the finger is briefly lifted and placed down again. */ |
swm.c
+147 -5
| 119 | 119 | #define MAX_COMMAND_SIZE 1024 |
|
| 120 | 120 | #define MAX_COMMAND_ARGS 64 |
|
| 121 | 121 | #define LISTEN_STATIC(E, H) listen_static((E), (H)) |
|
| 122 | 122 | ||
| 123 | 123 | /* Cursor interaction state. */ |
|
| 124 | - | enum { CURSOR_NORMAL, CURSOR_PRESSED, CURSOR_MOVE, CURSOR_RESIZE }; |
|
| 124 | + | enum { CURSOR_NORMAL, CURSOR_PRESSED, CURSOR_MOVE, CURSOR_RESIZE, CURSOR_TILE_RESIZE }; |
|
| 125 | 125 | ||
| 126 | 126 | /* Master-stack configuration commands. */ |
|
| 127 | 127 | enum { |
|
| 128 | 128 | MASTER_SHRINK, |
|
| 129 | 129 | MASTER_GROW, |
| 437 | 437 | ); |
|
| 438 | 438 | static void arrange_layers(monitor_t *m); |
|
| 439 | 439 | static void autostart_exec(void); |
|
| 440 | 440 | static void axis_notify(struct wl_listener *listener, void *data); |
|
| 441 | 441 | static void button_press(struct wl_listener *listener, void *data); |
|
| 442 | + | static bool tiled_resize_boundary(monitor_t *m, int x, int y, bool *horizontal); |
|
| 443 | + | static void tiled_resize_cursor(bool active, bool horizontal); |
|
| 444 | + | static int tiled_resize_value(int offset, int size, bool flip); |
|
| 445 | + | static void tiled_resize_update(void); |
|
| 442 | 446 | static void change_vt(const arg_t *arg); |
|
| 443 | 447 | static void check_idle_inhibitor(struct wlr_surface *exclude); |
|
| 444 | 448 | static void cleanup(void); |
|
| 445 | 449 | static void cleanup_monitor(struct wl_listener *listener, void *data); |
|
| 446 | 450 | static void cleanup_listeners(void); |
| 703 | 707 | static client_t *grabc; |
|
| 704 | 708 | static int grabcx, grabcy; /* Pointer position within the grabbed window. */ |
|
| 705 | 709 | static int grabx, graby; /* Pointer position within the display layout. */ |
|
| 706 | 710 | static uint32_t grabedges; |
|
| 707 | 711 | static struct wlr_box grabgeom; |
|
| 712 | + | static workspace_t *grabws; /* Workspace changed by a tiled resize. */ |
|
| 713 | + | static bool grab_horizontal; /* Tiled resize changes a horizontal boundary. */ |
|
| 714 | + | static bool tiled_resize_hover; /* Pointer uses a tiled resize cursor. */ |
|
| 715 | + | static bool tiled_resize_hover_horizontal; /* Hovered boundary orientation. */ |
|
| 708 | 716 | ||
| 709 | 717 | static struct wlr_output_layout *output_layout; |
|
| 710 | 718 | static struct wlr_box sgeom; |
|
| 711 | 719 | static struct wl_list mons; |
|
| 712 | 720 | static struct wl_list ws_managers; /* workspace_manager_t.link */ |
| 1653 | 1661 | j++; |
|
| 1654 | 1662 | } |
|
| 1655 | 1663 | return count; |
|
| 1656 | 1664 | } |
|
| 1657 | 1665 | ||
| 1666 | + | /* Return the active master boundary for a tiled workspace. */ |
|
| 1667 | + | static bool tiled_resize_boundary(monitor_t *m, int x, int y, bool *horizontal) { |
|
| 1668 | + | client_t *c; |
|
| 1669 | + | workspace_t *ws; |
|
| 1670 | + | stack_state_t *st; |
|
| 1671 | + | int boundary, count = 0, masters; |
|
| 1672 | + | bool rotate, flip; |
|
| 1673 | + | ||
| 1674 | + | if (!m || !(ws = m->ws) || !ws->lt || !ws->lt->arrange || ws->lt->arrange == max_stack || |
|
| 1675 | + | x < m->w.x || x >= m->w.x + m->w.width || y < m->w.y || y >= m->w.y + m->w.height) |
|
| 1676 | + | return false; |
|
| 1677 | + | ||
| 1678 | + | rotate = ws->lt->arrange == master_top || ws->lt->arrange == master_bottom; |
|
| 1679 | + | flip = ws->lt->arrange == master_right || ws->lt->arrange == master_bottom; |
|
| 1680 | + | ||
| 1681 | + | if (!rotate && ws->lt->arrange != master_left && ws->lt->arrange != master_right) |
|
| 1682 | + | return false; |
|
| 1683 | + | ||
| 1684 | + | wl_list_for_each(c, &clients, link) if (VISIBLEON(c, m) && !c->is_floating && !c->is_fullscreen) |
|
| 1685 | + | count++; |
|
| 1686 | + | ||
| 1687 | + | st = rotate ? &ws->h : &ws->v; |
|
| 1688 | + | masters = MIN(MAX(st->mwin, 0), count); |
|
| 1689 | + | ||
| 1690 | + | if (!masters || masters == count) |
|
| 1691 | + | return false; |
|
| 1692 | + | ||
| 1693 | + | if (rotate) { |
|
| 1694 | + | boundary = flip ? m->w.y + m->w.height - m->w.height / SLICE * st->msize |
|
| 1695 | + | : m->w.y + m->w.height / SLICE * st->msize; |
|
| 1696 | + | if ((unsigned int)abs(y - boundary) > tiled_resize_margin) |
|
| 1697 | + | return false; |
|
| 1698 | + | } else { |
|
| 1699 | + | boundary = flip ? m->w.x + m->w.width - m->w.width / SLICE * st->msize |
|
| 1700 | + | : m->w.x + m->w.width / SLICE * st->msize; |
|
| 1701 | + | if ((unsigned int)abs(x - boundary) > tiled_resize_margin) |
|
| 1702 | + | return false; |
|
| 1703 | + | } |
|
| 1704 | + | *horizontal = rotate; |
|
| 1705 | + | return true; |
|
| 1706 | + | } |
|
| 1707 | + | ||
| 1708 | + | /* Convert a pointer offset to a tiled master size. */ |
|
| 1709 | + | static int tiled_resize_value(int offset, int size, bool flip) { |
|
| 1710 | + | int step, value; |
|
| 1711 | + | ||
| 1712 | + | if (size < SLICE || (step = size / SLICE) == 0) |
|
| 1713 | + | return -1; |
|
| 1714 | + | ||
| 1715 | + | value = (offset + step / 2) / step; |
|
| 1716 | + | ||
| 1717 | + | if (flip) |
|
| 1718 | + | value = SLICE - value; |
|
| 1719 | + | return MIN(MAX(value, 1), SLICE - 1); |
|
| 1720 | + | } |
|
| 1721 | + | ||
| 1658 | 1722 | /* Function implementations. */ |
|
| 1659 | 1723 | /* Clamp client geometry to the supplied bounds. */ |
|
| 1660 | 1724 | void apply_bounds(client_t *c, struct wlr_box *bbox) { |
|
| 1661 | 1725 | swm_box_apply_bounds((struct swm_box *)&c->geom, (const struct swm_box *)bbox, c->bw); |
|
| 1662 | 1726 | } |
| 1920 | 1984 | } |
|
| 1921 | 1985 | ||
| 1922 | 1986 | keyboard = wlr_seat_get_keyboard(seat); |
|
| 1923 | 1987 | mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; |
|
| 1924 | 1988 | ||
| 1989 | + | if (event->button == BTN_LEFT && CLEANMASK(mods) == CLEANMASK(MOD) && |
|
| 1990 | + | tiled_resize_boundary( |
|
| 1991 | + | selmon, (int)round(cursor->x), (int)round(cursor->y), &grab_horizontal |
|
| 1992 | + | )) { |
|
| 1993 | + | grabws = selmon->ws; |
|
| 1994 | + | cursor_mode = CURSOR_TILE_RESIZE; |
|
| 1995 | + | tiled_resize_cursor(true, grab_horizontal); |
|
| 1996 | + | return; |
|
| 1997 | + | } |
|
| 1998 | + | tiled_resize_cursor(false, false); |
|
| 1999 | + | ||
| 1925 | 2000 | for (b = buttons; b < END(buttons); b++) { |
|
| 1926 | 2001 | if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && b->func) { |
|
| 1927 | 2002 | b->func(&b->arg); |
|
| 1928 | 2003 | return; |
|
| 1929 | 2004 | } |
| 1931 | 2006 | break; |
|
| 1932 | 2007 | case WL_POINTER_BUTTON_STATE_RELEASED: |
|
| 1933 | 2008 | /* Releasing any button ends an interactive move or resize. */ |
|
| 1934 | 2009 | /* TODO: Restore the cursor requested by the window under the pointer. */ |
|
| 1935 | 2010 | if (!locked && cursor_mode != CURSOR_NORMAL && cursor_mode != CURSOR_PRESSED) { |
|
| 2011 | + | if (cursor_mode == CURSOR_TILE_RESIZE) { |
|
| 2012 | + | cursor_mode = CURSOR_NORMAL; |
|
| 2013 | + | grabws = nullptr; |
|
| 2014 | + | tiled_resize_update(); |
|
| 2015 | + | return; |
|
| 2016 | + | } |
|
| 1936 | 2017 | if (cursor_mode == CURSOR_RESIZE) |
|
| 1937 | 2018 | client_set_resizing(grabc, 0); |
|
| 1938 | 2019 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); |
|
| 1939 | 2020 | cursor_mode = CURSOR_NORMAL; |
|
| 1940 | 2021 | /* Move the window to the workspace on its new display. The |
| 1945 | 2026 | remember_client(grabc); |
|
| 1946 | 2027 | grabc = nullptr; |
|
| 1947 | 2028 | return; |
|
| 1948 | 2029 | } |
|
| 1949 | 2030 | cursor_mode = CURSOR_NORMAL; |
|
| 2031 | + | tiled_resize_update(); |
|
| 1950 | 2032 | break; |
|
| 1951 | 2033 | } |
|
| 1952 | 2034 | /* Forward buttons that were not handled here to the application under the pointer. */ |
|
| 1953 | 2035 | wlr_seat_pointer_notify_button(seat, event->time_msec, event->button, event->state); |
|
| 1954 | 2036 | } |
| 3681 | 3763 | keyboard_group_t *group = wl_container_of(listener, group, modifiers); |
|
| 3682 | 3764 | ||
| 3683 | 3765 | wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); |
|
| 3684 | 3766 | /* Forward the new modifier state to the focused application. */ |
|
| 3685 | 3767 | wlr_seat_keyboard_notify_modifiers(seat, &group->wlr_group->keyboard.modifiers); |
|
| 3768 | + | tiled_resize_update(); |
|
| 3686 | 3769 | } |
|
| 3687 | 3770 | ||
| 3688 | 3771 | /* Repeat the active shortcut at the keyboard's configured rate. */ |
|
| 3689 | 3772 | int key_repeat(void *data) { |
|
| 3690 | 3773 | keyboard_group_t *group = data; |
| 4111 | 4194 | ); |
|
| 4112 | 4195 | ||
| 4113 | 4196 | if (constraint && constraint->surface != seat->pointer_state.focused_surface) |
|
| 4114 | 4197 | constraint = nullptr; |
|
| 4115 | 4198 | ||
| 4116 | - | if (constraint && cursor_mode != CURSOR_RESIZE && cursor_mode != CURSOR_MOVE) { |
|
| 4199 | + | if (constraint && cursor_mode != CURSOR_RESIZE && cursor_mode != CURSOR_MOVE && |
|
| 4200 | + | cursor_mode != CURSOR_TILE_RESIZE) { |
|
| 4117 | 4201 | toplevel_from_wlr_surface(constraint->surface, &c, nullptr); |
|
| 4118 | 4202 | ||
| 4119 | 4203 | if (c) { |
|
| 4120 | 4204 | sx = cursor->x - c->geom.x - c->bw; |
|
| 4121 | 4205 | sy = cursor->y - c->geom.y - c->bw; |
| 4172 | 4256 | swm_box_resize((const struct swm_box *)&grabgeom, movedx, movedy, grabedges, grabc->bw); |
|
| 4173 | 4257 | struct wlr_box geo = { pure.x, pure.y, pure.width, pure.height }; |
|
| 4174 | 4258 | grabc->resize_edges = grabedges; |
|
| 4175 | 4259 | resize(grabc, geo, 1); |
|
| 4176 | 4260 | return; |
|
| 4261 | + | } else if (cursor_mode == CURSOR_TILE_RESIZE) { |
|
| 4262 | + | tiled_resize_update(); |
|
| 4263 | + | return; |
|
| 4177 | 4264 | } |
|
| 4265 | + | tiled_resize_update(); |
|
| 4178 | 4266 | /* Show the default cursor over the background and window borders. */ |
|
| 4179 | - | if (!surface && !seat->drag) |
|
| 4267 | + | if (!surface && !seat->drag && !tiled_resize_hover) |
|
| 4180 | 4268 | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); |
|
| 4181 | 4269 | ||
| 4182 | 4270 | pointer_focus(c, surface, sx, sy, time, refocus); |
|
| 4183 | 4271 | } |
|
| 4184 | 4272 |
| 4198 | 4286 | event->unaccel_dy, |
|
| 4199 | 4287 | 1 |
|
| 4200 | 4288 | ); |
|
| 4201 | 4289 | } |
|
| 4202 | 4290 | ||
| 4291 | + | /* Set or clear the cursor for a tiled master boundary. */ |
|
| 4292 | + | static void tiled_resize_cursor(bool active, bool horizontal) { |
|
| 4293 | + | if (active) { |
|
| 4294 | + | if (!tiled_resize_hover || tiled_resize_hover_horizontal != horizontal) |
|
| 4295 | + | wlr_cursor_set_xcursor(cursor, cursor_mgr, horizontal ? "ns-resize" : "ew-resize"); |
|
| 4296 | + | tiled_resize_hover_horizontal = horizontal; |
|
| 4297 | + | } else if (tiled_resize_hover) { |
|
| 4298 | + | wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); |
|
| 4299 | + | } |
|
| 4300 | + | tiled_resize_hover = active; |
|
| 4301 | + | } |
|
| 4302 | + | ||
| 4303 | + | /* Update the tiled master size or its hover cursor. */ |
|
| 4304 | + | static void tiled_resize_update(void) { |
|
| 4305 | + | monitor_t *m = point_to_monitor(cursor->x, cursor->y); |
|
| 4306 | + | struct wlr_keyboard *keyboard; |
|
| 4307 | + | workspace_t *ws; |
|
| 4308 | + | stack_state_t *st; |
|
| 4309 | + | int size, offset, value; |
|
| 4310 | + | bool horizontal, flip; |
|
| 4311 | + | ||
| 4312 | + | if (cursor_mode != CURSOR_TILE_RESIZE) { |
|
| 4313 | + | if (cursor_mode == CURSOR_PRESSED) |
|
| 4314 | + | return; |
|
| 4315 | + | keyboard = wlr_seat_get_keyboard(seat); |
|
| 4316 | + | if (!keyboard || CLEANMASK(wlr_keyboard_get_modifiers(keyboard)) != CLEANMASK(MOD)) { |
|
| 4317 | + | tiled_resize_cursor(false, false); |
|
| 4318 | + | return; |
|
| 4319 | + | } |
|
| 4320 | + | if (tiled_resize_boundary(m, (int)round(cursor->x), (int)round(cursor->y), &horizontal)) |
|
| 4321 | + | tiled_resize_cursor(true, horizontal); |
|
| 4322 | + | else |
|
| 4323 | + | tiled_resize_cursor(false, false); |
|
| 4324 | + | return; |
|
| 4325 | + | } |
|
| 4326 | + | ||
| 4327 | + | if (!grabws || !grabws->mon) |
|
| 4328 | + | return; |
|
| 4329 | + | ||
| 4330 | + | ws = grabws; |
|
| 4331 | + | m = ws->mon; |
|
| 4332 | + | st = grab_horizontal ? &ws->h : &ws->v; |
|
| 4333 | + | flip = ws->lt->arrange == master_right || ws->lt->arrange == master_bottom; |
|
| 4334 | + | size = grab_horizontal ? m->w.height : m->w.width; |
|
| 4335 | + | ||
| 4336 | + | offset = grab_horizontal ? (int)round(cursor->y) - m->w.y : (int)round(cursor->x) - m->w.x; |
|
| 4337 | + | value = tiled_resize_value(offset, size, flip); |
|
| 4338 | + | ||
| 4339 | + | if (value >= 0 && st->msize != value) { |
|
| 4340 | + | st->msize = value; |
|
| 4341 | + | arrange(m); |
|
| 4342 | + | } |
|
| 4343 | + | } |
|
| 4344 | + | ||
| 4203 | 4345 | /* Forward touchpad swipe gestures to clients of the focused surface. */ |
|
| 4204 | 4346 | void gesture_swipe_begin(struct wl_listener *listener, void *data) { |
|
| 4205 | 4347 | struct wlr_pointer_swipe_begin_event *event = data; |
|
| 4206 | 4348 | ||
| 4207 | 4349 | wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); |
| 4781 | 4923 | /* An application supplied a new cursor image. */ |
|
| 4782 | 4924 | struct wlr_seat_pointer_request_set_cursor_event *event = data; |
|
| 4783 | 4925 | ||
| 4784 | 4926 | /* Keep the move or resize cursor until the pointer grab ends. The |
|
| 4785 | 4927 | * application will request its cursor again when the pointer re-enters. */ |
|
| 4786 | - | if (cursor_mode != CURSOR_NORMAL && cursor_mode != CURSOR_PRESSED) |
|
| 4928 | + | if ((cursor_mode != CURSOR_NORMAL && cursor_mode != CURSOR_PRESSED) || tiled_resize_hover) |
|
| 4787 | 4929 | return; |
|
| 4788 | 4930 | /* This can be sent by any client, so we check to make sure this one |
|
| 4789 | 4931 | * actually has pointer focus first. If so, we can tell the cursor to |
|
| 4790 | 4932 | * use the provided surface as the cursor image. It will set the |
|
| 4791 | 4933 | * hardware cursor on the output that it's currently on and continue to |
| 4796 | 4938 | ||
| 4797 | 4939 | /* Use the named cursor shape requested by the application under the pointer. */ |
|
| 4798 | 4940 | void set_cursor_shape(struct wl_listener *listener, void *data) { |
|
| 4799 | 4941 | struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; |
|
| 4800 | 4942 | ||
| 4801 | - | if (cursor_mode != CURSOR_NORMAL && cursor_mode != CURSOR_PRESSED) |
|
| 4943 | + | if ((cursor_mode != CURSOR_NORMAL && cursor_mode != CURSOR_PRESSED) || tiled_resize_hover) |
|
| 4802 | 4944 | return; |
|
| 4803 | 4945 | /* This can be sent by any client, so we check to make sure this one |
|
| 4804 | 4946 | * actually has pointer focus first. If so, we can tell the cursor to |
|
| 4805 | 4947 | * use the provided cursor shape. */ |
|
| 4806 | 4948 | if (event->seat_client == seat->pointer_state.focused_client) |
test/unit.c
+6 -0
| 101 | 101 | visible[2] = true; |
|
| 102 | 102 | assert(swm_workspace_next(0, 5, 1, false, visible, occupied) == 4); |
|
| 103 | 103 | assert(swm_workspace_next(0, 5, 1, true, visible, occupied) == 1); |
|
| 104 | 104 | assert(swm_stack_configure(&state, SWM_MASTER_SHRINK, SWM_MASTER_LEFT, &side)); |
|
| 105 | 105 | assert(state.msize == 15 && side == SWM_MASTER_LEFT); |
|
| 106 | + | assert(tiled_resize_value(500, 1000, false) == 16); |
|
| 107 | + | assert(tiled_resize_value(250, 1000, false) == 8); |
|
| 108 | + | assert(tiled_resize_value(250, 1000, true) == 24); |
|
| 109 | + | assert(tiled_resize_value(-100, 1000, false) == 1); |
|
| 110 | + | assert(tiled_resize_value(1100, 1000, false) == 31); |
|
| 111 | + | assert(tiled_resize_value(10, 16, false) == -1); |
|
| 106 | 112 | ||
| 107 | 113 | for (int command = SWM_MASTER_GROW; command <= SWM_FLIP_LAYOUT; command++) |
|
| 108 | 114 | assert(swm_stack_configure(&state, command, SWM_MASTER_LEFT, &side)); |
|
| 109 | 115 | assert(!swm_stack_configure(&state, 99, SWM_MASTER_LEFT, &side)); |
|
| 110 | 116 | } |