Allow resizing tiled windows

8fcdc95a442dfff66c8f8fbbddce51c6ae3c4c13
Alexis Sellier committed ago 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
}