diff --git a/labwc.h b/labwc.h index 07789538..acb4b694 100644 --- a/labwc.h +++ b/labwc.h @@ -138,10 +138,17 @@ void focus_view(struct tinywl_view *view, struct wlr_surface *surface); void view_focus_next_toplevel(struct tinywl_server *server); void begin_interactive(struct tinywl_view *view, enum tinywl_cursor_mode mode, uint32_t edges); bool is_toplevel(struct tinywl_view *view); +struct tinywl_view *desktop_view_at(struct tinywl_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy); /* TODO: try to refactor to remove from header file */ struct tinywl_view *first_toplevel(struct tinywl_server *server); +void server_cursor_motion(struct wl_listener *listener, void *data); +void server_cursor_motion_absolute(struct wl_listener *listener, void *data); +void server_cursor_button(struct wl_listener *listener, void *data); +void server_cursor_axis(struct wl_listener *listener, void *data); +void server_cursor_frame(struct wl_listener *listener, void *data); void server_new_output(struct wl_listener *listener, void *data); void output_frame(struct wl_listener *listener, void *data); diff --git a/main.c b/main.c index 136730d0..36f14584 100644 --- a/main.c +++ b/main.c @@ -273,227 +273,6 @@ static void seat_request_cursor(struct wl_listener *listener, void *data) { } } -bool view_at(struct tinywl_view *view, - double lx, double ly, struct wlr_surface **surface, - double *sx, double *sy) { - /* - * XDG toplevels may have nested surfaces, such as popup windows for context - * menus or tooltips. This function tests if any of those are underneath the - * coordinates lx and ly (in output Layout Coordinates). If so, it sets the - * surface pointer to that wlr_surface and the sx and sy coordinates to the - * coordinates relative to that surface's top-left corner. - */ - double view_sx = lx - view->x; - double view_sy = ly - view->y; - double _sx, _sy; - struct wlr_surface *_surface = NULL; - - switch (view->type) { - case LAB_XDG_SHELL_VIEW: - _surface = wlr_xdg_surface_surface_at( - view->xdg_surface, view_sx, view_sy, &_sx, &_sy); - break; - case LAB_XWAYLAND_VIEW: - if (!view->xwayland_surface->surface) - return false; - _surface = wlr_surface_surface_at( - view->xwayland_surface->surface, - view_sx, view_sy, &_sx, &_sy); - break; - } - - if (_surface != NULL) { - *sx = _sx; - *sy = _sy; - *surface = _surface; - return true; - } - return false; -} - -static struct tinywl_view *desktop_view_at( - struct tinywl_server *server, double lx, double ly, - struct wlr_surface **surface, double *sx, double *sy) { - /* This iterates over all of our surfaces and attempts to find one under the - * cursor. This relies on server->views being ordered from top-to-bottom. */ - struct tinywl_view *view; - wl_list_for_each(view, &server->views, link) { - if (view_at(view, lx, ly, surface, sx, sy)) { - return view; - } - } - return NULL; -} - -static void process_cursor_move(struct tinywl_server *server, uint32_t time) { - /* Move the grabbed view to the new position. */ - server->grabbed_view->x = server->cursor->x - server->grab_x; - server->grabbed_view->y = server->cursor->y - server->grab_y; -} - -static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { - /* - * Resizing the grabbed view can be a little bit complicated, because we - * could be resizing from any corner or edge. This not only resizes the view - * on one or two axes, but can also move the view if you resize from the top - * or left edges (or top-left corner). - * - * Note that I took some shortcuts here. In a more fleshed-out compositor, - * you'd wait for the client to prepare a buffer at the new size, then - * commit any movement that was prepared. - */ - struct tinywl_view *view = server->grabbed_view; - double dx = server->cursor->x - server->grab_x; - double dy = server->cursor->y - server->grab_y; - double x = view->x; - double y = view->y; - int width = server->grab_width; - int height = server->grab_height; - if (server->resize_edges & WLR_EDGE_TOP) { - y = server->grab_y + dy; - height -= dy; - if (height < 1) { - y += height; - } - } else if (server->resize_edges & WLR_EDGE_BOTTOM) { - height += dy; - } - if (server->resize_edges & WLR_EDGE_LEFT) { - x = server->grab_x + dx; - width -= dx; - if (width < 1) { - x += width; - } - } else if (server->resize_edges & WLR_EDGE_RIGHT) { - width += dx; - } - view->x = x; - view->y = y; - wlr_xdg_toplevel_set_size(view->xdg_surface, width, height); -} - -static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { - /* If the mode is non-passthrough, delegate to those functions. */ - if (server->cursor_mode == TINYWL_CURSOR_MOVE) { - process_cursor_move(server, time); - return; - } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) { - process_cursor_resize(server, time); - return; - } - - /* Otherwise, find the view under the pointer and send the event along. */ - double sx, sy; - struct wlr_seat *seat = server->seat; - struct wlr_surface *surface = NULL; - struct tinywl_view *view = desktop_view_at(server, - server->cursor->x, server->cursor->y, &surface, &sx, &sy); - if (!view) { - /* If there's no view under the cursor, set the cursor image to a - * default. This is what makes the cursor image appear when you move it - * around the screen, not over any views. */ - wlr_xcursor_manager_set_cursor_image( - server->cursor_mgr, "left_ptr", server->cursor); - } - if (surface) { - bool focus_changed = seat->pointer_state.focused_surface != surface; - /* - * "Enter" the surface if necessary. This lets the client know that the - * cursor has entered one of its surfaces. - * - * Note that this gives the surface "pointer focus", which is distinct - * from keyboard focus. You get pointer focus by moving the pointer over - * a window. - */ - wlr_seat_pointer_notify_enter(seat, surface, sx, sy); - if (!focus_changed) { - /* The enter event contains coordinates, so we only need to notify - * on motion if the focus did not change. */ - wlr_seat_pointer_notify_motion(seat, time, sx, sy); - } - } else { - /* Clear pointer focus so future button events and such are not sent to - * the last client to have the cursor over it. */ - wlr_seat_pointer_clear_focus(seat); - } -} - -static void server_cursor_motion(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits a _relative_ - * pointer motion event (i.e. a delta) */ - struct tinywl_server *server = - wl_container_of(listener, server, cursor_motion); - struct wlr_event_pointer_motion *event = data; - /* The cursor doesn't move unless we tell it to. The cursor automatically - * handles constraining the motion to the output layout, as well as any - * special configuration applied for the specific input device which - * generated the event. You can pass NULL for the device if you want to move - * the cursor around without any input. */ - wlr_cursor_move(server->cursor, event->device, - event->delta_x, event->delta_y); - process_cursor_motion(server, event->time_msec); -} - -static void server_cursor_motion_absolute( - struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an _absolute_ - * motion event, from 0..1 on each axis. This happens, for example, when - * wlroots is running under a Wayland window rather than KMS+DRM, and you - * move the mouse over the window. You could enter the window from any edge, - * so we have to warp the mouse there. There is also some hardware which - * emits these events. */ - struct tinywl_server *server = - wl_container_of(listener, server, cursor_motion_absolute); - struct wlr_event_pointer_motion_absolute *event = data; - wlr_cursor_warp_absolute(server->cursor, event->device, event->x, event->y); - process_cursor_motion(server, event->time_msec); -} - -static void server_cursor_button(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits a button - * event. */ - struct tinywl_server *server = - wl_container_of(listener, server, cursor_button); - struct wlr_event_pointer_button *event = data; - /* Notify the client with pointer focus that a button press has occurred */ - wlr_seat_pointer_notify_button(server->seat, - event->time_msec, event->button, event->state); - double sx, sy; - struct wlr_surface *surface; - struct tinywl_view *view = desktop_view_at(server, - server->cursor->x, server->cursor->y, &surface, &sx, &sy); - if (event->state == WLR_BUTTON_RELEASED) { - /* If you released any buttons, we exit interactive move/resize mode. */ - server->cursor_mode = TINYWL_CURSOR_PASSTHROUGH; - } else { - /* Focus that client if the button was _pressed_ */ - focus_view(view, surface); - } -} - -static void server_cursor_axis(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an axis event, - * for example when you move the scroll wheel. */ - struct tinywl_server *server = - wl_container_of(listener, server, cursor_axis); - struct wlr_event_pointer_axis *event = data; - /* Notify the client with pointer focus of the axis event. */ - wlr_seat_pointer_notify_axis(server->seat, - event->time_msec, event->orientation, event->delta, - event->delta_discrete, event->source); -} - -static void server_cursor_frame(struct wl_listener *listener, void *data) { - /* This event is forwarded by the cursor when a pointer emits an frame - * event. Frame events are sent after regular pointer events to group - * multiple events together. For instance, two axis events may happen at the - * same time, in which case a frame event won't be sent in between. */ - struct tinywl_server *server = - wl_container_of(listener, server, cursor_frame); - /* Notify the client with pointer focus of the frame event. */ - wlr_seat_pointer_notify_frame(server->seat); -} - int main(int argc, char *argv[]) { wlr_log_init(WLR_ERROR, NULL); char *startup_cmd = NULL; diff --git a/server.c b/server.c index 7b0b60a0..bd5044d8 100644 --- a/server.c +++ b/server.c @@ -1,5 +1,179 @@ #include "labwc.h" +static void process_cursor_move(struct tinywl_server *server, uint32_t time) +{ + /* Move the grabbed view to the new position. */ + server->grabbed_view->x = server->cursor->x - server->grab_x; + server->grabbed_view->y = server->cursor->y - server->grab_y; +} + +static void process_cursor_resize(struct tinywl_server *server, uint32_t time) +{ + /* + * Resizing the grabbed view can be a little bit complicated, because we + * could be resizing from any corner or edge. This not only resizes the view + * on one or two axes, but can also move the view if you resize from the top + * or left edges (or top-left corner). + * + * Note that I took some shortcuts here. In a more fleshed-out compositor, + * you'd wait for the client to prepare a buffer at the new size, then + * commit any movement that was prepared. + */ + struct tinywl_view *view = server->grabbed_view; + double dx = server->cursor->x - server->grab_x; + double dy = server->cursor->y - server->grab_y; + double x = view->x; + double y = view->y; + int width = server->grab_width; + int height = server->grab_height; + if (server->resize_edges & WLR_EDGE_TOP) { + y = server->grab_y + dy; + height -= dy; + if (height < 1) { + y += height; + } + } else if (server->resize_edges & WLR_EDGE_BOTTOM) { + height += dy; + } + if (server->resize_edges & WLR_EDGE_LEFT) { + x = server->grab_x + dx; + width -= dx; + if (width < 1) { + x += width; + } + } else if (server->resize_edges & WLR_EDGE_RIGHT) { + width += dx; + } + view->x = x; + view->y = y; + wlr_xdg_toplevel_set_size(view->xdg_surface, width, height); +} + +static void process_cursor_motion(struct tinywl_server *server, uint32_t time) +{ + /* If the mode is non-passthrough, delegate to those functions. */ + if (server->cursor_mode == TINYWL_CURSOR_MOVE) { + process_cursor_move(server, time); + return; + } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) { + process_cursor_resize(server, time); + return; + } + + /* Otherwise, find the view under the pointer and send the event along. */ + double sx, sy; + struct wlr_seat *seat = server->seat; + struct wlr_surface *surface = NULL; + struct tinywl_view *view = desktop_view_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (!view) { + /* If there's no view under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * around the screen, not over any views. */ + wlr_xcursor_manager_set_cursor_image( + server->cursor_mgr, "left_ptr", server->cursor); + } + if (surface) { + bool focus_changed = seat->pointer_state.focused_surface != surface; + /* + * "Enter" the surface if necessary. This lets the client know that the + * cursor has entered one of its surfaces. + * + * Note that this gives the surface "pointer focus", which is distinct + * from keyboard focus. You get pointer focus by moving the pointer over + * a window. + */ + wlr_seat_pointer_notify_enter(seat, surface, sx, sy); + if (!focus_changed) { + /* The enter event contains coordinates, so we only need to notify + * on motion if the focus did not change. */ + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + } else { + /* Clear pointer focus so future button events and such are not sent to + * the last client to have the cursor over it. */ + wlr_seat_pointer_clear_focus(seat); + } +} + +void server_cursor_motion(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_motion); + struct wlr_event_pointer_motion *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + wlr_cursor_move(server->cursor, event->device, + event->delta_x, event->delta_y); + process_cursor_motion(server, event->time_msec); +} + +void server_cursor_motion_absolute(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_motion_absolute); + struct wlr_event_pointer_motion_absolute *event = data; + wlr_cursor_warp_absolute(server->cursor, event->device, event->x, event->y); + process_cursor_motion(server, event->time_msec); +} + +void server_cursor_button(struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a button + * event. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_button); + struct wlr_event_pointer_button *event = data; + /* Notify the client with pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(server->seat, + event->time_msec, event->button, event->state); + double sx, sy; + struct wlr_surface *surface; + struct tinywl_view *view = desktop_view_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (event->state == WLR_BUTTON_RELEASED) { + /* If you released any buttons, we exit interactive move/resize mode. */ + server->cursor_mode = TINYWL_CURSOR_PASSTHROUGH; + } else { + /* Focus that client if the button was _pressed_ */ + focus_view(view, surface); + } +} + +void server_cursor_axis(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits an axis event, + * for example when you move the scroll wheel. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_axis); + struct wlr_event_pointer_axis *event = data; + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(server->seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source); +} + +void server_cursor_frame(struct wl_listener *listener, void *data) { + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + struct tinywl_server *server = + wl_container_of(listener, server, cursor_frame); + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame(server->seat); +} + void server_new_output(struct wl_listener *listener, void *data) { /* This event is rasied by the backend when a new output (aka a display or diff --git a/view.c b/view.c index 808aef2b..f4918eba 100644 --- a/view.c +++ b/view.c @@ -150,3 +150,54 @@ struct tinywl_view *first_toplevel(struct tinywl_server *server) { return NULL; } +static bool view_at(struct tinywl_view *view, + double lx, double ly, struct wlr_surface **surface, + double *sx, double *sy) { + /* + * XDG toplevels may have nested surfaces, such as popup windows for context + * menus or tooltips. This function tests if any of those are underneath the + * coordinates lx and ly (in output Layout Coordinates). If so, it sets the + * surface pointer to that wlr_surface and the sx and sy coordinates to the + * coordinates relative to that surface's top-left corner. + */ + double view_sx = lx - view->x; + double view_sy = ly - view->y; + double _sx, _sy; + struct wlr_surface *_surface = NULL; + + switch (view->type) { + case LAB_XDG_SHELL_VIEW: + _surface = wlr_xdg_surface_surface_at( + view->xdg_surface, view_sx, view_sy, &_sx, &_sy); + break; + case LAB_XWAYLAND_VIEW: + if (!view->xwayland_surface->surface) + return false; + _surface = wlr_surface_surface_at( + view->xwayland_surface->surface, + view_sx, view_sy, &_sx, &_sy); + break; + } + + if (_surface != NULL) { + *sx = _sx; + *sy = _sy; + *surface = _surface; + return true; + } + return false; +} + +struct tinywl_view *desktop_view_at(struct tinywl_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + /* This iterates over all of our surfaces and attempts to find one under the + * cursor. This relies on server->views being ordered from top-to-bottom. */ + struct tinywl_view *view; + wl_list_for_each(view, &server->views, link) { + if (view_at(view, lx, ly, surface, sx, sy)) { + return view; + } + } + return NULL; +} +