spmenu/libs/wl/wayland.c
speedie dd44bc56ab Add clipboard support for Wayland
This commit adds clipboard support for Wayland. The implementation is
quite ugly though, as it requires the use of an external program and
shell (wl-clipboard). It doesn't add a hard dependency though, as if
the user doesn't want pasting the dependency is not required.
2023-07-22 04:04:23 +02:00

714 lines
22 KiB
C

/* See LICENSE file for copyright and license details. */
#include "shm.c"
struct wl_buffer *create_buffer(struct state *state) {
int32_t width = state->width;
int32_t height = state->height;
uint32_t stride = width * 4;
size_t siz = stride * height;
int fd = create_shm_file(siz);
assert(fd != -1);
void *data = mmap(NULL, siz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, siz);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
wl_buffer_add_listener(buffer, &buffer_listener, state);
state->data = data;
return buffer;
}
/* I fucking hate this code, but I'm not sure how else to do it.
*
* PLEASE submit a patch if you have an improvement.
*/
int is_correct_modifier(struct state *state, char *modifier) {
int altPress = xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
int shiftPress = xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
int ctrlPress = xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
int logoPress = xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);
if (!strcmp(modifier, WL_CtrlShift) && shiftPress && ctrlPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlShiftSuper) && shiftPress && ctrlPress && logoPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlSuper) && ctrlPress && logoPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlAlt) && altPress && ctrlPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlAltShift) && ctrlPress && altPress && shiftPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlAltShiftSuper) && ctrlPress && altPress && shiftPress && logoPress) {
return 0;
} else if (!strcmp(modifier, WL_CtrlAltSuper) && ctrlPress && altPress && logoPress) {
return 0;
} else if (!strcmp(modifier, WL_AltShift) && altPress && shiftPress) {
return 0;
} else if (!strcmp(modifier, WL_Shift) && shiftPress) {
return 0;
} else if (!strcmp(modifier, WL_Ctrl) && ctrlPress) {
return 0;
} else if (!strcmp(modifier, WL_Alt) && altPress) {
return 0;
} else if (!strcmp(modifier, WL_Super) && logoPress) {
return 0;
} else if (!strcmp(modifier, WL_None) && !altPress && !shiftPress && !ctrlPress && !logoPress) {
return 0;
}
return 1;
}
/* This function is pretty garbage. However I don't feel like implementing all the garbage necessary to paste properly.
* If anyone wants to do it, feel free to pull request.
*/
char *wl_clipboard(void) {
FILE *fp;
char output_text[1024];
char *clipboard = malloc(sizeof(output_text));
clipboard[0] = '\0';
fp = popen("which wl-paste > /dev/null && wl-paste -t text/plain", "r");
if (fp == NULL) {
fprintf(stderr, "spmenu: Failed to open command\n");
return NULL;
}
while (fgets(output_text, sizeof(output_text), fp) != NULL) {
strcat(clipboard, output_text);
}
pclose(fp);
return clipboard;
}
void paste_wl(void) {
char *p, *q;
fprintf(stderr, "gentoo");
p = wl_clipboard();
insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); // insert selection
// draw the menu
drawmenu();
}
void keypress_wl(struct state *state, enum wl_keyboard_key_state key_state, xkb_keysym_t sym) {
int i = 0;
char buf[8];
if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) { // Only activate on press, not release
return;
}
if (xkb_keysym_to_lower(sym) == wlhkeys[0].keysym && !is_correct_modifier(state, wlhkeys[0].modifier) && wlhkeys[0].func) {
wlhkeys[0].func(&(wlhkeys[0].arg));
}
for (i = 0; i < LENGTH(wl_keys); i++) {
if (sp.ignoreglobalkeys) break;
if (xkb_keysym_to_lower(sym) == wl_keys[i].keysym && !is_correct_modifier(state, wl_keys[i].modifier) && wl_keys[i].func) {
if ((wl_keys[i].mode && sp.mode) || wl_keys[i].mode == -1) {
wl_keys[i].func(&(wl_keys[i].arg));
return;
} else if (!wl_keys[i].mode && !sp.mode) {
wl_keys[i].func(&(wl_keys[i].arg));
}
}
}
for (i = 0; i < LENGTH(wl_ckeys); i++) {
if (sp.ignoreconfkeys) break;
if (xkb_keysym_to_lower(sym) == wl_ckeys[i].keysym && !is_correct_modifier(state, wl_ckeys[i].modifier) && wl_ckeys[i].func) {
if ((wl_ckeys[i].mode && sp.mode) || wl_ckeys[i].mode == -1) {
wl_ckeys[i].func(&(wl_ckeys[i].arg));
return;
} else if (!wl_ckeys[i].mode && !sp.mode) {
wl_ckeys[i].func(&(wl_ckeys[i].arg));
}
}
}
if (xkb_keysym_to_lower(sym) == XKB_KEY_Escape) {
return;
}
if (!type || !sp.mode) {
return;
}
if (xkb_keysym_to_utf8(sym, buf, 8)) {
if (sp.allowkeys) {
insert(buf, strnlen(buf, 8));
} else {
sp.allowkeys = !sp.allowkeys;
}
drawmenu();
}
}
void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
struct state *state = data;
int ocapslockstate = sp.capslockstate;
xkb_state_update_mask(state->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
if (xkb_state_mod_name_is_active(state->xkb_state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE)) {
sp.capslockstate = 1;
} else {
sp.capslockstate = 0;
}
if (ocapslockstate != sp.capslockstate) {
strncpy(tx.capstext, sp.capslockstate ? capslockontext : capslockofftext, 15);
drawmenu();
}
}
void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
struct state *state = data;
state->delay = delay;
if (rate > 0) {
state->period = 1000 / rate;
} else {
state->period = -1;
}
}
void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t _key_state) {
struct state *state = data;
enum wl_keyboard_key_state key_state = _key_state;
strncpy(tx.capstext, sp.capslockstate ? capslockontext : capslockofftext, 15);
xkb_keysym_t sym = xkb_state_key_get_one_sym(state->xkb_state, key + 8);
keypress_wl(state, key_state, sym);
if (key_state == WL_KEYBOARD_KEY_STATE_PRESSED && state->period >= 0) {
state->repeat_key_state = key_state;
state->repeat_sym = sym;
struct itimerspec spec = { 0 };
spec.it_value.tv_sec = state->delay / 1000;
spec.it_value.tv_nsec = (state->delay % 1000) * 1000000l;
timerfd_settime(state->timer, 0, &spec, NULL);
} else if (key_state == WL_KEYBOARD_KEY_STATE_RELEASED) {
struct itimerspec spec = { 0 };
timerfd_settime(state->timer, 0, &spec, NULL);
}
}
void keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {
struct state *state = data;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
exit(1);
return;
}
char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (map_shm == MAP_FAILED) {
close(fd);
die("MAP_FAILED");
return;
}
state->xkb_keymap = xkb_keymap_new_from_string(state->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
munmap(map_shm, size);
close(fd);
state->xkb_state = xkb_state_new(state->xkb_keymap);
}
void buttonpress_wl(uint32_t button, double ex, double ey) {
int x = 0;
int y = 0;
int w;
int h = sp.bh;
int xpad = 0;
int item_num = 0;
int yp = 0;
unsigned int i, click;
struct item *item;
if (ex == 0 && ey == 0) {
return; // While it is possible to click at this position, usually it means we're outside the window area.
}
if (!hidepowerline) {
x = xpad = sp.plw;
}
x += menumarginh;
int larroww = 0;
int rarroww = 0;
int numberw = 0;
int modew = 0;
int capsw = 0;
if (!hidelarrow) larroww = pango_leftarrow ? TEXTWM(leftarrow) : TEXTW(leftarrow);
if (!hiderarrow) rarroww = pango_rightarrow ? TEXTWM(rightarrow) : TEXTW(rightarrow);
if (!hidematchcount) numberw = pango_numbers ? TEXTWM(tx.numbers) : TEXTW(tx.numbers);
if (!hidemode) modew = pango_mode ? TEXTWM(tx.modetext) : TEXTW(tx.modetext);
if (!hidecaps) capsw = pango_caps ? TEXTWM(tx.capstext) : TEXTW(tx.capstext);
if (!strcmp(tx.capstext, ""))
capsw = 0;
if ((hideprompt && hideinput && hidemode && hidematchcount && hidecaps) && lines) {
yp = 1;
} else if (lines && ey < h + menumarginv && ey > menumarginv) {
yp = 1;
} else if (!lines) {
yp = 1;
}
click = ClickWindow; // clicking anywhere, we use this and override it if we clicked on something specific
// check click position and override the value of click
if (yp && ex < x + sp.promptw + powerlineprompt ? sp.plw : 0) { // prompt
click = ClickPrompt;
} else if (yp && (ex > sp.mw - capsw - 2 * sp.sp - 2 * borderwidth - menumarginh) && !hidecaps && capsw) { // caps lock indicator
click = ClickCaps;
} else if (yp && ex > sp.mw - modew - capsw - 2 * sp.sp - 2 * borderwidth - menumarginh) { // mode indicator
click = ClickMode;
} else if (yp && ex > sp.mw - modew - numberw - capsw - 2 * sp.sp - 2 * borderwidth - menumarginh) { // match count
click = ClickNumber;
} else if (yp && !hideinput) { // input
w = (lines > 0 || !matches) ? sp.mw - x : sp.inputw;
if ((lines <= 0 && ex >= 0 && ex <= x + w + sp.promptw +
((!previousitem || !currentitem->left) ? larroww : 0)) ||
(lines > 0 && ey >= y && ey <= y + h)) {
click = ClickInput;
}
}
#if USEIMAGE
if (!hideimage && img.longestedge != 0 && imagetype) {
x += MAX((img.imagegaps * 2) + img.imagewidth, indentitems ? sp.promptw : 0);
}
#endif
// item click
if (lines > 0) {
w = sp.mw - x;
ey -= menumarginv;
if (hideprompt && hideinput && hidemode && hidematchcount && hidecaps) {
ey += h;
}
for (item = currentitem; item != nextitem; item = item->right) {
if (item_num++ == lines) {
item_num = 1;
x += w / columns;
y = 0;
}
y += h;
if (ey >= y && ey <= (y + h) && ex >= x + (powerlineitems ? sp.plw : 0) && ex <= (x + w / columns) + (powerlineitems ? sp.plw : 0)) {
click = ClickItem;
mouseitem = item;
#if USEIMAGE
} else if (ey >= y && ey <= (y + h) && ex >= x + (powerlineitems ? sp.plw : 0) - MAX((img.imagegaps * 2) + img.imagewidth, indentitems ? sp.promptw : 0) && ex <= (x - MAX((img.imagegaps * 2) + img.imagewidth, indentitems ? sp.promptw : 0) + w / columns) + (powerlineitems ? sp.plw : 0)) {
click = ClickImage;
mouseitem = item;
#endif
}
}
} else if (matches) { // a single line, meaning it could be arrows too, so we check that here
x += sp.inputw;
w = larroww;
if (previousitem && currentitem->left) {
if (ex >= x && ex <= x + w) {
click = ClickLArrow;
}
}
// right arrow
w = rarroww;
x = sp.mw - w;
if (nextitem && ex >= x && ex <= x + w) {
click = ClickRArrow;
}
}
// go through mouse button array and run function
for (i = 0; i < LENGTH(wl_buttons); i++) {
if (sp.ignoreglobalmouse) break;
if ((click == wl_buttons[i].click || wl_buttons[i].click == ClickNone) && wl_buttons[i].func && wl_buttons[i].button == button)
wl_buttons[i].func(&wl_buttons[i].arg);
}
// go through mouse config array and run function
for (i = 0; i < LENGTH(wl_cbuttons); i++) {
if (sp.ignoreconfmouse) break;
if ((click == wl_cbuttons[i].click || wl_cbuttons[i].click == ClickNone) && wl_cbuttons[i].func && wl_cbuttons[i].button == button)
wl_cbuttons[i].func(&wl_cbuttons[i].arg);
}
}
void pointer_motion_handler(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
mouse_x = wl_fixed_to_int(x);
mouse_y = wl_fixed_to_int(y);
}
void pointer_axis_handler(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
mouse_scroll = 1;
if (value > 0) {
mouse_scroll_direction = 0;
} else {
mouse_scroll_direction = 1;
}
buttonpress_wl(mouse_scroll_direction, mouse_x, mouse_y);
mouse_scroll = 0;
mouse_scroll_direction = -1;
}
void pointer_button_handler(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
return; // We don't want a release event to count as a click, only the initial press.
}
buttonpress_wl(button, mouse_x, mouse_y);
}
void seat_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) {
struct state *state = data;
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
state->keyboard = wl_seat_get_keyboard(seat);
state->pointer = wl_seat_get_pointer(seat);
wl_keyboard_add_listener(state->keyboard, &keyboard_listener, state);
wl_pointer_add_listener(state->pointer, &pointer_listener, state);
}
}
void output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t output_transform) {
output_physical_width = physical_width;
output_physical_height = physical_height;
}
void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
output_width = width;
output_height = height;
}
void output_scale(void *data, struct wl_output *wl_output, int32_t factor) {
struct output *output = data;
output->scale = factor;
}
void output_name(void *data, struct wl_output *wl_output, const char *name) {
struct output *output = data;
struct state *state = output->state;
char *outname = state->output_name;
if (!state->output && outname && strcmp(outname, name) == 0) {
state->output = output;
}
}
void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) {
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) {
cleanup();
exit(0);
}
void zero() {
// Nothing.
}
void draw_sf(struct state *state) {
if (!allow_draw) { // No drawing if disabled
return;
}
// create buffer to draw on
state->buffer = create_buffer(state);
if (draw == NULL) {
die("spmenu: draw == NULL");
}
if (state->buffer == NULL) {
die("state->buffer == NULL");
}
draw_create_surface_wl(draw, state->data, state->width, state->height);
drawmenu_layer();
wl_surface_set_buffer_scale(state->surface, 1);
wl_surface_attach(state->surface, state->buffer, 0, 0);
wl_surface_damage(state->surface, 0, 0, state->width, state->height);
wl_surface_commit(state->surface);
}
void global_handler(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
struct state *state = data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
state->compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 4);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, wl_seat_interface.name) == 0 && has_keys) {
struct wl_seat *seat = wl_registry_bind(registry, name,
&wl_seat_interface, 4);
wl_seat_add_listener(seat, &seat_listener, state);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
state->layer_shell = wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
struct output *output = calloc(1, sizeof(struct output));
output->output = wl_registry_bind(registry, name,
&wl_output_interface, 4);
output->state = state;
output->scale = 1;
wl_output_set_user_data(output->output, output);
wl_output_add_listener(output->output, &output_listener, output);
}
}
void surface_enter(void *data, struct wl_surface *surface, struct wl_output *wl_output) {
struct state *state = data;
state->output = wl_output_get_user_data(wl_output);
match();
draw_sf(state);
}
int roundtrip(struct state *state) {
if (wl_display_roundtrip(state->display)) {
return 0;
} else {
return 1;
}
}
int connect_display(struct state *state) {
state->display = wl_display_connect(NULL);
if (state->display) {
return 0;
} else {
return 1;
}
}
/* If this function returns 1, something went wrong.
* This may be that the user is using X11, or a compositor like Mutter.
* In this case, it may be a good idea to fall back to X11.
*/
int init_disp(struct state *state) {
if (!state->display) {
return 1;
} else {
no_display = 0;
}
struct wl_registry *registry = wl_display_get_registry(state->display);
wl_registry_add_listener(registry, &registry_listener, state);
wl_display_roundtrip(state->display);
assert(state->compositor != NULL);
if (state->layer_shell == NULL) {
die("spmenu: Your compositor does not implement the wlr-layer-shell protocol. Run spmenu in X11 mode.");
}
assert(state->shm != NULL);
wl_display_roundtrip(state->display);
if (state->output_name && !state->output) {
return 1;
}
return 0;
}
void resizeclient_wl(struct state *state) {
struct item *item;
int mh = sp.mh;
int ic = 0;
// walk through all items
for (item = items; item && item->text; item++)
ic++;
lines = MIN(ic, MAX(lines, 0));
#if USEIMAGE
img.setlines = lines;
#endif
get_mh();
if (hideprompt && hideinput && hidemode && hidematchcount && hidecaps) {
sp.mh -= sp.bh;
}
if (sp.mh == mh) {
return;
}
state->width = sp.mw;
state->height = sp.mh;
state->buffer = create_buffer(state);
if (draw == NULL) {
die("spmenu: draw == NULL");
}
if (state->buffer == NULL) {
die("state->buffer == null");
}
draw_create_surface_wl(draw, state->data, state->width, state->height);
set_layer_size(state, state->width, state->height);
wl_surface_set_buffer_scale(state->surface, 1);
wl_surface_attach(state->surface, state->buffer, 0, 0);
wl_surface_damage(state->surface, 0, 0, state->width, state->height);
wl_surface_commit(state->surface);
}
/* It is advised you call this function right before calling init_disp()
* It sets up keybinds for you.
*/
int init_keys(struct state *state) {
state->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!state->xkb_context) {
return 1;
} else {
has_keys = 1;
}
state->timer = timerfd_create(CLOCK_MONOTONIC, 0);
if (state->timer < 0) {
has_keys = 0;
fprintf(stderr, "timerfd_create() failed\n");
return 1;
}
return 0;
}
int create_layer(struct state *state, char *name) {
assert(state->compositor != NULL);
state->surface = wl_compositor_create_surface(state->compositor);
assert(state->surface != NULL);
assert(state->layer_shell != NULL);
wl_surface_add_listener(state->surface, &surface_listener, state);
state->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
state->layer_shell,
state->surface,
NULL,
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
name);
assert(state->layer_surface != NULL);
return 0;
}
int anchor_layer(struct state *state, int position) {
assert(state->layer_surface != NULL);
zwlr_layer_surface_v1_set_anchor(
state->layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
(position == 0 ? ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM :
position == 1 ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP :
0));
return 0;
}
int set_layer_size(struct state *state, int32_t width, int32_t height) {
assert(state->layer_surface != NULL);
zwlr_layer_surface_v1_set_size(state->layer_surface, width, height);
return 0;
}
int set_exclusive_zone(struct state *state, unsigned int val) {
assert(state->layer_surface != NULL);
zwlr_layer_surface_v1_set_exclusive_zone(state->layer_surface, val);
return 0;
}
int set_keyboard(struct state *state, int interactivity) {
assert(state->layer_surface != NULL);
zwlr_layer_surface_v1_set_keyboard_interactivity(state->layer_surface, interactivity ? true : false);
return 0;
}
int add_layer_listener(struct state *state) {
assert(state->layer_surface != NULL);
zwlr_layer_surface_v1_add_listener(state->layer_surface, &layer_surface_listener, state);
return 0;
}
int set_visible_layer(struct state *state) {
assert(state->layer_surface != NULL);
assert(state->surface != NULL);
wl_surface_commit(state->surface);
return 0;
}