summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--meson.build1
-rw-r--r--src/clipboard.c23
-rw-r--r--src/clipboard.h15
-rw-r--r--src/input.c44
-rw-r--r--src/input.h1
-rw-r--r--src/main.c259
-rw-r--r--src/tofi.h5
-rw-r--r--src/unicode.c5
-rw-r--r--src/unicode.h1
9 files changed, 341 insertions, 13 deletions
diff --git a/meson.build b/meson.build
index 0f4c74d..3071aed 100644
--- a/meson.build
+++ b/meson.build
@@ -92,6 +92,7 @@ add_project_arguments(
)
common_sources = files(
+ 'src/clipboard.c',
'src/color.c',
'src/compgen.c',
'src/config.c',
diff --git a/src/clipboard.c b/src/clipboard.c
new file mode 100644
index 0000000..a046055
--- /dev/null
+++ b/src/clipboard.c
@@ -0,0 +1,23 @@
+#include <unistd.h>
+#include "clipboard.h"
+
+void clipboard_finish_paste(struct clipboard *clipboard)
+{
+ if (clipboard->fd > 0) {
+ close(clipboard->fd);
+ clipboard->fd = 0;
+ }
+}
+
+void clipboard_reset(struct clipboard *clipboard)
+{
+ if (clipboard->wl_data_offer != NULL) {
+ wl_data_offer_destroy(clipboard->wl_data_offer);
+ clipboard->wl_data_offer = NULL;
+ }
+ if (clipboard->fd > 0) {
+ close(clipboard->fd);
+ clipboard->fd = 0;
+ }
+ clipboard->mime_type = NULL;
+}
diff --git a/src/clipboard.h b/src/clipboard.h
new file mode 100644
index 0000000..c5f3a4e
--- /dev/null
+++ b/src/clipboard.h
@@ -0,0 +1,15 @@
+#ifndef CLIPBOARD_H
+#define CLIPBOARD_H
+
+#include <wayland-client.h>
+
+struct clipboard {
+ struct wl_data_offer *wl_data_offer;
+ const char *mime_type;
+ int fd;
+};
+
+void clipboard_finish_paste(struct clipboard *clipboard);
+void clipboard_reset(struct clipboard *clipboard);
+
+#endif /* CLIPBOARD_H */
diff --git a/src/input.c b/src/input.c
index 0e1a5f9..4d82017 100644
--- a/src/input.c
+++ b/src/input.c
@@ -1,5 +1,8 @@
+#include <fcntl.h>
#include <linux/input-event-codes.h>
+#include <unistd.h>
#include "input.h"
+#include "log.h"
#include "nelem.h"
#include "tofi.h"
#include "unicode.h"
@@ -9,10 +12,10 @@ static void add_character(struct tofi *tofi, xkb_keycode_t keycode);
static void delete_character(struct tofi *tofi);
static void delete_word(struct tofi *tofi);
static void clear_input(struct tofi *tofi);
+static void paste(struct tofi *tofi);
static void select_previous_result(struct tofi *tofi);
static void select_next_result(struct tofi *tofi);
static void reset_selection(struct tofi *tofi);
-static void refresh_results(struct tofi *tofi);
void input_handle_keypress(struct tofi *tofi, xkb_keycode_t keycode)
{
@@ -50,6 +53,14 @@ void input_handle_keypress(struct tofi *tofi, xkb_keycode_t keycode)
)
{
clear_input(tofi);
+ } else if (key == KEY_V
+ && xkb_state_mod_name_is_active(
+ tofi->xkb_state,
+ XKB_MOD_NAME_CTRL,
+ XKB_STATE_MODS_EFFECTIVE)
+ )
+ {
+ paste(tofi);
} else if (sym == XKB_KEY_Up || sym == XKB_KEY_Left || sym == XKB_KEY_ISO_Left_Tab
|| (key == KEY_K
&& xkb_state_mod_name_is_active(
@@ -130,7 +141,7 @@ void add_character(struct tofi *tofi, xkb_keycode_t keycode)
reset_selection(tofi);
}
-void refresh_results(struct tofi *tofi)
+void input_refresh_results(struct tofi *tofi)
{
struct entry *entry = &tofi->window.entry;
@@ -164,7 +175,7 @@ void delete_character(struct tofi *tofi)
entry->input_utf32_length--;
entry->input_utf32[entry->input_utf32_length] = U'\0';
- refresh_results(tofi);
+ input_refresh_results(tofi);
}
void delete_word(struct tofi *tofi)
@@ -184,7 +195,7 @@ void delete_word(struct tofi *tofi)
}
entry->input_utf32[entry->input_utf32_length] = U'\0';
- refresh_results(tofi);
+ input_refresh_results(tofi);
}
void clear_input(struct tofi *tofi)
@@ -194,7 +205,30 @@ void clear_input(struct tofi *tofi)
entry->input_utf32_length = 0;
entry->input_utf32[0] = U'\0';
- refresh_results(tofi);
+ input_refresh_results(tofi);
+}
+
+void paste(struct tofi *tofi)
+{
+ if (tofi->clipboard.wl_data_offer == NULL || tofi->clipboard.mime_type == NULL) {
+ return;
+ }
+
+ /*
+ * Create a pipe, and give the write end to the compositor to give to
+ * the clipboard manager.
+ */
+ errno = 0;
+ int fildes[2];
+ if (pipe2(fildes, O_CLOEXEC | O_NONBLOCK) == -1) {
+ log_error("Failed to open pipe for clipboard: %s\n", strerror(errno));
+ return;
+ }
+ wl_data_offer_receive(tofi->clipboard.wl_data_offer, tofi->clipboard.mime_type, fildes[1]);
+ close(fildes[1]);
+
+ /* Keep the read end for reading in the main loop. */
+ tofi->clipboard.fd = fildes[0];
}
void select_previous_result(struct tofi *tofi)
diff --git a/src/input.h b/src/input.h
index f78c365..cb6c456 100644
--- a/src/input.h
+++ b/src/input.h
@@ -5,5 +5,6 @@
#include "tofi.h"
void input_handle_keypress(struct tofi *tofi, xkb_keycode_t keycode);
+void input_refresh_results(struct tofi *tofi);
#endif /* INPUT_H */
diff --git a/src/main.c b/src/main.c
index 142991b..3783937 100644
--- a/src/main.c
+++ b/src/main.c
@@ -26,6 +26,7 @@
#include "shm.h"
#include "string_vec.h"
#include "string_vec.h"
+#include "unicode.h"
#include "xmalloc.h"
#undef MAX
@@ -33,6 +34,9 @@
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
+static const char *mime_type_text_plain = "text/plain";
+static const char *mime_type_text_plain_utf8 = "text/plain;charset=utf-8";
+
static uint32_t gettime_ms() {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
@@ -371,6 +375,123 @@ static const struct wl_seat_listener wl_seat_listener = {
.name = wl_seat_name,
};
+static void wl_data_offer_offer(
+ void *data,
+ struct wl_data_offer *wl_data_offer,
+ const char *mime_type)
+{
+ struct clipboard *clipboard = data;
+
+ /* Only accept plain text, and prefer utf-8. */
+ if (!strcmp(mime_type, mime_type_text_plain)) {
+ if (clipboard->mime_type != NULL) {
+ clipboard->mime_type = mime_type_text_plain;
+ }
+ } else if (!strcmp(mime_type, mime_type_text_plain_utf8)) {
+ clipboard->mime_type = mime_type_text_plain_utf8;
+ }
+}
+
+static void wl_data_offer_source_actions(
+ void *data,
+ struct wl_data_offer *wl_data_offer,
+ uint32_t source_actions)
+{
+ /* Deliberately left blank */
+}
+
+static void wl_data_offer_action(
+ void *data,
+ struct wl_data_offer *wl_data_offer,
+ uint32_t action)
+{
+ /* Deliberately left blank */
+}
+
+static const struct wl_data_offer_listener wl_data_offer_listener = {
+ .offer = wl_data_offer_offer,
+ .source_actions = wl_data_offer_source_actions,
+ .action = wl_data_offer_action
+};
+
+static void wl_data_device_data_offer(
+ void *data,
+ struct wl_data_device *wl_data_device,
+ struct wl_data_offer *wl_data_offer)
+{
+ struct clipboard *clipboard = data;
+ clipboard_reset(clipboard);
+ clipboard->wl_data_offer = wl_data_offer;
+ wl_data_offer_add_listener(
+ wl_data_offer,
+ &wl_data_offer_listener,
+ clipboard);
+}
+
+static void wl_data_device_enter(
+ void *data,
+ struct wl_data_device *wl_data_device,
+ uint32_t serial,
+ struct wl_surface *wl_surface,
+ int32_t x,
+ int32_t y,
+ struct wl_data_offer *wl_data_offer)
+{
+ /* Drag-and-drop is just ignored for now. */
+ wl_data_offer_accept(
+ wl_data_offer,
+ serial,
+ NULL);
+ wl_data_offer_set_actions(
+ wl_data_offer,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE);
+}
+
+static void wl_data_device_leave(
+ void *data,
+ struct wl_data_device *wl_data_device)
+{
+ /* Deliberately left blank */
+}
+
+static void wl_data_device_motion(
+ void *data,
+ struct wl_data_device *wl_data_device,
+ uint32_t time,
+ int32_t x,
+ int32_t y)
+{
+ /* Deliberately left blank */
+}
+
+static void wl_data_device_drop(
+ void *data,
+ struct wl_data_device *wl_data_device)
+{
+ /* Deliberately left blank */
+}
+
+static void wl_data_device_selection(
+ void *data,
+ struct wl_data_device *wl_data_device,
+ struct wl_data_offer *wl_data_offer)
+{
+ struct clipboard *clipboard = data;
+ if (wl_data_offer == NULL) {
+ clipboard_reset(clipboard);
+ }
+}
+
+static const struct wl_data_device_listener wl_data_device_listener = {
+ .data_offer = wl_data_device_data_offer,
+ .enter = wl_data_device_enter,
+ .leave = wl_data_device_leave,
+ .motion = wl_data_device_motion,
+ .drop = wl_data_device_drop,
+ .selection = wl_data_device_selection
+};
+
static void output_geometry(
void *data,
struct wl_output *wl_output,
@@ -518,6 +639,13 @@ static void registry_global(
&wl_shm_interface,
1);
log_debug("Bound to shm %u.\n", name);
+ } else if (!strcmp(interface, wl_data_device_manager_interface.name)) {
+ tofi->wl_data_device_manager = wl_registry_bind(
+ wl_registry,
+ name,
+ &wl_data_device_manager_interface,
+ 3);
+ log_debug("Bound to data device manager %u.\n", name);
} else if (!strcmp(interface, zwlr_layer_shell_v1_interface.name)) {
if (version < 4) {
log_warning("Using an outdated compositor, "
@@ -857,6 +985,70 @@ static bool do_submit(struct tofi *tofi)
return true;
}
+static void read_clipboard(struct tofi *tofi)
+{
+ struct entry *entry = &tofi->window.entry;
+ /* Buffer for 4 UTF-8 bytes plus a null terminator. */
+ char buffer[5];
+ memset(buffer, 0, N_ELEM(buffer));
+ errno = 0;
+ bool eof = false;
+ while (entry->input_utf32_length < N_ELEM(entry->input_utf32)) {
+ for (size_t i = 0; i < 4; i++) {
+ /*
+ * Read input 1 byte at a time. This is slow, but easy,
+ * and speed of pasting shouldn't really matter.
+ */
+ int res = read(tofi->clipboard.fd, &buffer[i], 1);
+ if (res == 0) {
+ eof = true;
+ break;
+ } else if (res == -1) {
+ if (errno == EAGAIN) {
+ /*
+ * There was no more data to be read,
+ * but EOF hasn't been reached yet.
+ *
+ * This could mean more than a pipe's
+ * capacity (64k) of data was sent, in
+ * which case we'd potentially skip
+ * a character, but we should hit the
+ * input length limit long before that.
+ */
+ input_refresh_results(tofi);
+ tofi->window.surface.redraw = true;
+ return;
+ }
+ log_error("Failed to read clipboard: %s\n", strerror(errno));
+ clipboard_finish_paste(&tofi->clipboard);
+ return;
+ }
+ uint32_t unichar = utf8_to_utf32_validate(buffer);
+ if (unichar == (uint32_t)-2) {
+ /* The current character isn't complete yet. */
+ continue;
+ } else if (unichar == (uint32_t)-1) {
+ log_error("Invalid UTF-8 character in clipboard: %s\n", buffer);
+ break;
+ } else {
+ entry->input_utf32[entry->input_utf32_length] = unichar;
+ entry->input_utf32_length++;
+ break;
+ }
+ }
+ memset(buffer, 0, N_ELEM(buffer));
+ if (eof) {
+ break;
+ }
+ }
+ entry->input_utf32[MIN(entry->input_utf32_length, N_ELEM(entry->input_utf32) - 1)] = U'\0';
+
+ clipboard_finish_paste(&tofi->clipboard);
+
+ input_refresh_results(tofi);
+ tofi->window.surface.redraw = true;
+}
+
int main(int argc, char *argv[])
{
/* Call log_debug to initialise the timers we use for perf checking. */
@@ -1201,6 +1393,19 @@ int main(int argc, char *argv[])
wl_surface_commit(tofi.window.surface.wl_surface);
/*
+ * Create a data device and setup a listener for data offers. This is
+ * required for clipboard support.
+ */
+ tofi.wl_data_device = wl_data_device_manager_get_data_device(
+ tofi.wl_data_device_manager,
+ tofi.wl_seat);
+ wl_data_device_add_listener(
+ tofi.wl_data_device,
+ &wl_data_device_listener,
+ &tofi.clipboard);
+
+
+ /*
* Now that we've done all our Wayland-related setup, we do another
* roundtrip. This should cause the layer surface window to be
* configured, after which we're ready to start drawing to the screen.
@@ -1286,8 +1491,8 @@ int main(int argc, char *argv[])
* order of the various functions called here.
*/
while (!tofi.closed) {
- struct pollfd pollfd;
- pollfd.fd = wl_display_get_fd(tofi.wl_display);
+ struct pollfd pollfds[2] = {{0}, {0}};
+ pollfds[0].fd = wl_display_get_fd(tofi.wl_display);
/* Make sure we're ready to receive events on the main queue. */
while (wl_display_prepare_read(tofi.wl_display) != 0) {
@@ -1296,8 +1501,8 @@ int main(int argc, char *argv[])
/* Make sure all our requests have been sent to the server. */
while (wl_display_flush(tofi.wl_display) != 0) {
- pollfd.events = POLLOUT;
- poll(&pollfd, 1, -1);
+ pollfds[0].events = POLLOUT;
+ poll(&pollfds[0], 1, -1);
}
/*
@@ -1314,8 +1519,20 @@ int main(int argc, char *argv[])
}
}
- pollfd.events = POLLIN | POLLPRI;
- int res = poll(&pollfd, 1, timeout);
+ pollfds[0].events = POLLIN | POLLPRI;
+ int res;
+ if (tofi.clipboard.fd == 0) {
+ res = poll(&pollfds[0], 1, timeout);
+ } else {
+ /*
+ * We're trying to paste from the clipboard, which is
+ * done by reading from a pipe, so poll that file
+ * descriptor as well.
+ */
+ pollfds[1].fd = tofi.clipboard.fd;
+ pollfds[1].events = POLLIN | POLLPRI;
+ res = poll(pollfds, 2, timeout);
+ }
if (res == 0) {
/*
* No events to process and no error - we presumably
@@ -1333,8 +1550,29 @@ int main(int argc, char *argv[])
/* There was an error polling the display. */
wl_display_cancel_read(tofi.wl_display);
} else {
- /* Events to read, so put them on the queue. */
- wl_display_read_events(tofi.wl_display);
+ if (pollfds[0].revents & (POLLIN | POLLPRI)) {
+ /* Events to read, so put them on the queue. */
+ wl_display_read_events(tofi.wl_display);
+ } else {
+ /*
+ * No events to read - we were woken up to
+ * handle clipboard data.
+ */
+ wl_display_cancel_read(tofi.wl_display);
+ }
+ if (pollfds[1].revents & (POLLIN | POLLPRI)) {
+ /* Read clipboard data. */
+ if (tofi.clipboard.fd > 0) {
+ read_clipboard(&tofi);
+ }
+ }
+ if (pollfds[1].revents & POLLHUP) {
+ /*
+ * The other end of the clipboard pipe has
+ * closed, cleanup.
+ */
+ clipboard_finish_paste(&tofi.clipboard);
+ }
}
/* Handle any events we read. */
@@ -1373,6 +1611,11 @@ int main(int argc, char *argv[])
wl_pointer_release(tofi.wl_pointer);
}
wl_compositor_destroy(tofi.wl_compositor);
+ if (tofi.clipboard.wl_data_offer != NULL) {
+ wl_data_offer_destroy(tofi.clipboard.wl_data_offer);
+ }
+ wl_data_device_release(tofi.wl_data_device);
+ wl_data_device_manager_destroy(tofi.wl_data_device_manager);
wl_seat_release(tofi.wl_seat);
{
struct output_list_element *el;
diff --git a/src/tofi.h b/src/tofi.h
index d558a9b..1608314 100644
--- a/src/tofi.h
+++ b/src/tofi.h
@@ -3,7 +3,9 @@
#include <stdbool.h>
#include <stdint.h>
+#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
+#include "clipboard.h"
#include "color.h"
#include "entry.h"
#include "image.h"
@@ -30,6 +32,8 @@ struct tofi {
struct wl_compositor *wl_compositor;
struct wl_seat *wl_seat;
struct wl_shm *wl_shm;
+ struct wl_data_device_manager *wl_data_device_manager;
+ struct wl_data_device *wl_data_device;
struct zwlr_layer_shell_v1 *zwlr_layer_shell;
struct wl_list output_list;
struct output_list_element *default_output;
@@ -49,6 +53,7 @@ struct tofi {
bool closed;
int32_t output_width;
int32_t output_height;
+ struct clipboard clipboard;
struct {
struct surface surface;
struct zwlr_layer_surface_v1 *zwlr_layer_surface;
diff --git a/src/unicode.c b/src/unicode.c
index ad8a777..3833fb6 100644
--- a/src/unicode.c
+++ b/src/unicode.c
@@ -12,6 +12,11 @@ uint32_t utf8_to_utf32(const char *s)
return g_utf8_get_char(s);
}
+uint32_t utf8_to_utf32_validate(const char *s)
+{
+ return g_utf8_get_char_validated(s, -1);
+}
+
uint32_t utf32_isprint(uint32_t c)
{
return g_unichar_isprint(c);
diff --git a/src/unicode.h b/src/unicode.h
index 45631d9..e198231 100644
--- a/src/unicode.h
+++ b/src/unicode.h
@@ -6,6 +6,7 @@
uint8_t utf32_to_utf8(uint32_t c, char *buf);
uint32_t utf8_to_utf32(const char *s);
+uint32_t utf8_to_utf32_validate(const char *s);
uint32_t utf32_isprint(uint32_t c);
uint32_t utf32_isspace(uint32_t c);