324 lines
12 KiB
C
324 lines
12 KiB
C
#define _POSIX_C_SOURCE 200112L
|
|
#include <wlr/util/log.h>
|
|
#include <assert.h>
|
|
#include "cursor.h"
|
|
#include "view.h"
|
|
#include "seat.h"
|
|
#include "node.h"
|
|
|
|
void diyac_cursor_focus(struct diyac_server *server)
|
|
{
|
|
double sx, sy;
|
|
struct wlr_surface *surface = NULL;
|
|
struct diyac_node_descriptor *desc = diyac_node_at(server,
|
|
server->seat.cursor->x, server->seat.cursor->y, &surface, &sx, &sy);
|
|
struct diyac_layer_surface *layer;
|
|
struct diyac_view *root = NULL;
|
|
if (!desc)
|
|
{
|
|
return;
|
|
}
|
|
/* Focus that client if the button was _pressed_ */
|
|
switch (desc->type)
|
|
{
|
|
case DIYAC_NODE_VIEW:
|
|
diyac_focus_view(desc->data, true);
|
|
/*
|
|
root = diyac_get_root_view(desc->data);
|
|
if (root)
|
|
{
|
|
diyac_focus_view(root, true);
|
|
}
|
|
else
|
|
{
|
|
diyac_focus_view(desc->data, true);
|
|
}
|
|
*/
|
|
break;
|
|
case DIYAC_NODE_LAYER_SURFACE:
|
|
layer = desc->data;
|
|
assert(layer);
|
|
assert(layer->scene_layer_surface);
|
|
diyac_seat_focus_layer(&server->seat, layer->scene_layer_surface->layer_surface);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void diyac_reset_cursor_mode(struct diyac_server *server)
|
|
{
|
|
/* Reset the cursor mode to passthrough. */
|
|
server->seat.cursor_mode = DIYAC_CURSOR_PASSTHROUGH;
|
|
server->grabbed_view = NULL;
|
|
}
|
|
|
|
static void process_cursor_move(struct diyac_server *server, uint32_t time)
|
|
{
|
|
struct diyac_view *toplevel = server->grabbed_view;
|
|
/* Move the grabbed toplevel to the new position. */
|
|
if (toplevel->state.fullscreen)
|
|
{
|
|
return;
|
|
}
|
|
if (toplevel->state.maximized)
|
|
{
|
|
toplevel->requested.maximized = false;
|
|
// move the windows to cursor
|
|
server->grab_x = toplevel->original.width * server->grab_x / toplevel->output->usable_area.width;
|
|
}
|
|
toplevel->pending_size.y = server->seat.cursor->y - server->grab_y;
|
|
toplevel->pending_size.x = server->seat.cursor->x - server->grab_x;
|
|
|
|
diyac_view_update_geometry(toplevel, true);
|
|
/*
|
|
wlr_scene_node_set_position(&toplevel->scene_tree->node,
|
|
toplevel->current.x ,
|
|
toplevel->current.y);
|
|
*/
|
|
}
|
|
|
|
static void process_cursor_resize(struct diyac_server *server, uint32_t time)
|
|
{
|
|
/*
|
|
* Resizing the grabbed toplevel can be a little bit complicated, because we
|
|
* could be resizing from any corner or edge. This not only resizes the
|
|
* toplevel on one or two axes, but can also move the toplevel if you resize
|
|
* from the top or left edges (or top-left corner).
|
|
*
|
|
* Note that some shortcuts are taken 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 diyac_view *toplevel = server->grabbed_view;
|
|
if(toplevel->state.fullscreen)
|
|
{
|
|
return;
|
|
}
|
|
double border_x = server->seat.cursor->x - server->grab_x;
|
|
double border_y = server->seat.cursor->y - server->grab_y;
|
|
int new_left = server->grab_geobox.x;
|
|
int new_right = server->grab_geobox.x + server->grab_geobox.width;
|
|
int new_top = server->grab_geobox.y;
|
|
int new_bottom = server->grab_geobox.y + server->grab_geobox.height;
|
|
|
|
if (server->resize_edges & WLR_EDGE_TOP)
|
|
{
|
|
new_top = border_y;
|
|
if (new_top >= new_bottom)
|
|
{
|
|
new_top = new_bottom - 1;
|
|
}
|
|
}
|
|
else if (server->resize_edges & WLR_EDGE_BOTTOM)
|
|
{
|
|
new_bottom = border_y;
|
|
if (new_bottom <= new_top)
|
|
{
|
|
new_bottom = new_top + 1;
|
|
}
|
|
}
|
|
if (server->resize_edges & WLR_EDGE_LEFT)
|
|
{
|
|
new_left = border_x;
|
|
if (new_left >= new_right)
|
|
{
|
|
new_left = new_right - 1;
|
|
}
|
|
}
|
|
else if (server->resize_edges & WLR_EDGE_RIGHT)
|
|
{
|
|
new_right = border_x;
|
|
if (new_right <= new_left)
|
|
{
|
|
new_right = new_left + 1;
|
|
}
|
|
}
|
|
|
|
struct wlr_box geo_box;
|
|
wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box);
|
|
toplevel->pending_size.x = new_left - geo_box.x;
|
|
toplevel->pending_size.y = new_top - geo_box.y;
|
|
int new_width = new_right - new_left;
|
|
int new_height = new_bottom - new_top;
|
|
toplevel->pending_size.width = new_width;
|
|
toplevel->pending_size.height = new_height;
|
|
toplevel->requested.maximized = false;
|
|
diyac_view_update_geometry(toplevel, false);
|
|
/*
|
|
wlr_scene_node_set_position(&toplevel->scene_tree->node,
|
|
toplevel->current.x, toplevel->current.y);
|
|
wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_width, new_height);
|
|
*/
|
|
}
|
|
|
|
static void process_cursor_motion(struct diyac_server *server, uint32_t time)
|
|
{
|
|
/* If the mode is non-passthrough, delegate to those functions. */
|
|
if (server->seat.cursor_mode == DIYAC_CURSOR_MOVE)
|
|
{
|
|
process_cursor_move(server, time);
|
|
return;
|
|
}
|
|
else if (server->seat.cursor_mode == DIYAC_CURSOR_RESIZE)
|
|
{
|
|
process_cursor_resize(server, time);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, find the toplevel under the pointer and send the event along. */
|
|
double sx, sy;
|
|
struct wlr_seat *seat = server->seat.wlr_seat;
|
|
struct wlr_surface *surface = NULL;
|
|
struct diyac_node_descriptor *desc = diyac_node_at(server,
|
|
server->seat.cursor->x, server->seat.cursor->y, &surface, &sx, &sy);
|
|
if (!desc)
|
|
{
|
|
/* If there's no toplevel 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 toplevels. */
|
|
wlr_cursor_set_xcursor(server->seat.cursor, server->seat.cursor_mgr, "default");
|
|
}
|
|
if (surface)
|
|
{
|
|
/*
|
|
* Send pointer enter and motion events.
|
|
*
|
|
* The enter event gives the surface "pointer focus", which is distinct
|
|
* from keyboard focus. You get pointer focus by moving the pointer over
|
|
* a window.
|
|
*
|
|
* Note that wlroots will avoid sending duplicate enter/motion events if
|
|
* the surface has already has pointer focus or if the client is already
|
|
* aware of the coordinates passed.
|
|
*/
|
|
wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
|
|
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 diyac_seat *seat =
|
|
wl_container_of(listener, seat, cursor_motion);
|
|
struct wlr_pointer_motion_event *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(seat->cursor, &event->pointer->base,
|
|
event->delta_x, event->delta_y);
|
|
process_cursor_motion(seat->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 diyac_seat *seat =
|
|
wl_container_of(listener, seat, cursor_motion_absolute);
|
|
struct wlr_pointer_motion_absolute_event *event = data;
|
|
wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x,
|
|
event->y);
|
|
process_cursor_motion(seat->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 diyac_seat *seat =
|
|
wl_container_of(listener, seat, cursor_button);
|
|
struct wlr_pointer_button_event *event = data;
|
|
/* Notify the client with pointer focus that a button press has occurred */
|
|
wlr_seat_pointer_notify_button(seat->wlr_seat,
|
|
event->time_msec, event->button, event->state);
|
|
|
|
if (event->state == WLR_BUTTON_RELEASED)
|
|
{
|
|
/* If you released any buttons, we exit interactive move/resize mode. */
|
|
diyac_reset_cursor_mode(seat->server);
|
|
}
|
|
else
|
|
{
|
|
diyac_cursor_focus(seat->server);
|
|
}
|
|
}
|
|
|
|
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 diyac_seat *seat =
|
|
wl_container_of(listener, seat, cursor_axis);
|
|
struct wlr_pointer_axis_event *event = data;
|
|
/* Notify the client with pointer focus of the axis event. */
|
|
wlr_seat_pointer_notify_axis(seat->wlr_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 diyac_seat *seat =
|
|
wl_container_of(listener, seat, cursor_frame);
|
|
/* Notify the client with pointer focus of the frame event. */
|
|
wlr_seat_pointer_notify_frame(seat->wlr_seat);
|
|
}
|
|
|
|
void diyac_init_cursor_manager(struct diyac_server *server)
|
|
{
|
|
/*
|
|
* Creates a cursor, which is a wlroots utility for tracking the cursor
|
|
* image shown on screen.
|
|
*/
|
|
server->seat.cursor = wlr_cursor_create();
|
|
wlr_cursor_attach_output_layout(server->seat.cursor, server->output_layout);
|
|
|
|
/* Creates an xcursor manager, another wlroots utility which loads up
|
|
* Xcursor themes to source cursor images from and makes sure that cursor
|
|
* images are available at all scale factors on the screen (necessary for
|
|
* HiDPI support). */
|
|
server->seat.cursor_mgr = wlr_xcursor_manager_create(NULL, 24);
|
|
|
|
/*
|
|
* wlr_cursor *only* displays an image on screen. It does not move around
|
|
* when the pointer moves. However, we can attach input devices to it, and
|
|
* it will generate aggregate events for all of them. In these events, we
|
|
* can choose how we want to process them, forwarding them to clients and
|
|
* moving the cursor around. More detail on this process is described in
|
|
* https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html.
|
|
*
|
|
* And more comments are sprinkled throughout the notify functions above.
|
|
*/
|
|
server->seat.cursor_mode = DIYAC_CURSOR_PASSTHROUGH;
|
|
server->seat.cursor_motion.notify = server_cursor_motion;
|
|
wl_signal_add(&server->seat.cursor->events.motion, &server->seat.cursor_motion);
|
|
server->seat.cursor_motion_absolute.notify = server_cursor_motion_absolute;
|
|
wl_signal_add(&server->seat.cursor->events.motion_absolute,
|
|
&server->seat.cursor_motion_absolute);
|
|
server->seat.cursor_button.notify = server_cursor_button;
|
|
wl_signal_add(&server->seat.cursor->events.button, &server->seat.cursor_button);
|
|
server->seat.cursor_axis.notify = server_cursor_axis;
|
|
wl_signal_add(&server->seat.cursor->events.axis, &server->seat.cursor_axis);
|
|
server->seat.cursor_frame.notify = server_cursor_frame;
|
|
wl_signal_add(&server->seat.cursor->events.frame, &server->seat.cursor_frame);
|
|
} |