diff --git a/input.c b/input.c index 8766aa96..951397d3 100644 --- a/input.c +++ b/input.c @@ -156,6 +156,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, } } + else if (effective_mods == (shift | ctrl)) { + if (sym == XKB_KEY_C) { + selection_to_clipboard(term, serial); + found_map = true; + } + } + for (size_t i = 0; i < sizeof(key_map) / sizeof(key_map[0]) && !found_map; i++) { const struct key_map *k = &key_map[i]; if (k->sym != sym) diff --git a/main.c b/main.c index 888bdae3..4c4eff4a 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,7 @@ #include "slave.h" #include "terminal.h" #include "vt.h" +#include "selection.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -125,6 +126,11 @@ handle_global(void *data, struct wl_registry *registry, wl_seat_add_listener(term->wl.seat, &seat_listener, term); wl_display_roundtrip(term->wl.display); } + + else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + term->wl.data_device_manager = wl_registry_bind( + term->wl.registry, name, &wl_data_device_manager_interface, 1); + } } static void @@ -388,6 +394,20 @@ main(int argc, char *const *argv) LOG_ERR("compositor does not support ARGB surfaces"); goto out; } + if (term.wl.seat == NULL) { + LOG_ERR("no seat available"); + goto out; + } + if (term.wl.data_device_manager == NULL) { + LOG_ERR("no clipboard available " + "(wl_data_device_manager not implemented by server)"); + goto out; + } + + /* Clipboard */ + term.wl.data_device = wl_data_device_manager_get_data_device( + term.wl.data_device_manager, term.wl.seat); + wl_data_device_add_listener(term.wl.data_device, &data_device_listener, &term); /* Cursor */ term.wl.pointer.surface = wl_compositor_create_surface(term.wl.compositor); @@ -603,6 +623,20 @@ out: wl_surface_destroy(term.wl.pointer.surface); if (term.wl.keyboard != NULL) wl_keyboard_destroy(term.wl.keyboard); + if (term.selection.primary.data_source != NULL) + wl_data_source_destroy(term.selection.primary.data_source); + if (term.selection.primary.data_offer != NULL) + wl_data_offer_destroy(term.selection.primary.data_offer); + free(term.selection.primary.text); + if (term.selection.clipboard.data_source != NULL) + wl_data_source_destroy(term.selection.clipboard.data_source); + if (term.selection.clipboard.data_offer != NULL) + wl_data_offer_destroy(term.selection.clipboard.data_offer); + free(term.selection.clipboard.text); + if (term.wl.data_device != NULL) + wl_data_device_destroy(term.wl.data_device); + if (term.wl.data_device_manager != NULL) + wl_data_device_manager_destroy(term.wl.data_device_manager); if (term.wl.seat != NULL) wl_seat_destroy(term.wl.seat); if (term.wl.surface != NULL) diff --git a/selection.c b/selection.c index cedf8d10..99688e01 100644 --- a/selection.c +++ b/selection.c @@ -1,9 +1,13 @@ #include "selection.h" +#include +#include + #define LOG_MODULE "selection" #define LOG_ENABLE_DBG 0 #include "log.h" #include "render.h" +#include "grid.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -14,6 +18,63 @@ selection_enabled(const struct terminal *term) return term->mouse_tracking == MOUSE_NONE; } +static char * +extract_selection(const struct terminal *term) +{ + const struct coord *start = &term->selection.start; + const struct coord *end = &term->selection.end; + + if (start->row > end->row || (start->row == end->row && start->col > end->col)) { + const struct coord *tmp = start; + start = end; + end = tmp; + } + + assert(start->row <= end->row); + + /* TODO: calculate length required */ + char buf[4096]; + int idx = 0; + + int start_col = start->col; + for (int r = start->row; r < end->row; r++) { + const struct row *row = grid_row_in_view(term->grid, r - term->grid->view); + if (row == NULL) + continue; + + for (int col = start_col; col < term->cols; col++) { + const struct cell *cell = &row->cells[col]; + if (cell->c[0] == '\0') + continue; + + size_t len = strnlen(cell->c, 4); + memcpy(&buf[idx], cell->c, len); + idx += len; + } + + if (r != end->row - 1) + buf[idx++] = '\n'; + + start_col = 0; + } + + { + const struct row *row = grid_row_in_view(term->grid, end->row - term->grid->view); + for (int col = start_col; row != NULL && col <= end->col; col++) { + const struct cell *cell = &row->cells[col]; + if (cell->c[0] == '\0') + continue; + + size_t len = strnlen(cell->c, 4); + memcpy(&buf[idx], cell->c, len); + idx += len; + } + } + + buf[idx] = '\0'; + return strdup(buf); +} + void selection_start(struct terminal *term, int col, int row) { @@ -71,6 +132,12 @@ selection_finalize(struct terminal *term) assert(term->selection.start.row != -1); assert(term->selection.end.row != -1); + + /* Kill old primary selection */ + free(term->selection.primary.text); + + /* TODO: primary data source */ + term->selection.primary.text = extract_selection(term); } void @@ -99,3 +166,168 @@ selection_cancel(struct terminal *term) grid_render(term); } } + +static void +target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) +{ + LOG_WARN("TARGET: mime-type=%s", mime_type); +} + +static void +send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, + int32_t fd) +{ + const struct clipboard *clipboard = wl_data_source_get_user_data(wl_data_source); + + LOG_WARN("SENDING CLIPBOARD!"); + assert(clipboard != NULL); + assert(clipboard->text != NULL); + + write(fd, clipboard->text, strlen(clipboard->text)); + close(fd); +} + +static void +cancelled(void *data, struct wl_data_source *wl_data_source) +{ + struct clipboard *clipboard = wl_data_source_get_user_data(wl_data_source); + assert(clipboard->data_source == wl_data_source); + + wl_data_source_destroy(clipboard->data_source); + clipboard->data_source = NULL; + clipboard->serial = 0; + + free(clipboard->text); + clipboard->text = NULL; +} + +static void +dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) +{ +} + +static void +dnd_finished(void *data, struct wl_data_source *wl_data_source) +{ +} + +static void +action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) +{ +} + +static const struct wl_data_source_listener data_source_listener = { + .target = &target, + .send = &send, + .cancelled = &cancelled, + .dnd_drop_performed = &dnd_drop_performed, + .dnd_finished = &dnd_finished, + .action = &action, +}; + +void +selection_to_clipboard(struct terminal *term, uint32_t serial) +{ + if (term->selection.clipboard.data_source != NULL) { + /* Kill previous data source */ + struct clipboard *clipboard = &term->selection.clipboard; + + assert(clipboard->serial != 0); + wl_data_device_set_selection(term->wl.data_device, NULL, clipboard->serial); + wl_data_source_destroy(clipboard->data_source); + free(clipboard->text); + + clipboard->data_source = NULL; + clipboard->serial = 0; + } + + struct clipboard *clipboard = &term->selection.clipboard; + + clipboard->text = extract_selection(term); + + clipboard->data_source + = wl_data_device_manager_create_data_source(term->wl.data_device_manager); + + assert(clipboard->data_source != NULL); + + wl_data_source_offer(clipboard->data_source, "text/plain;charset=utf-8"); + wl_data_source_add_listener(clipboard->data_source, &data_source_listener, term); + wl_data_device_set_selection(term->wl.data_device, clipboard->data_source, serial); + wl_data_source_set_user_data(clipboard->data_source, clipboard); + clipboard->serial = serial; +} + +static void +offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) +{ + LOG_ERR("OFFER: %s", mime_type); +} + +static void +source_actions(void *data, struct wl_data_offer *wl_data_offer, + uint32_t source_actions) +{ +} + +static void +offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) +{ +} + +static const struct wl_data_offer_listener data_offer_listener = { + .offer = &offer, + .source_actions = &source_actions, + .action = &offer_action, +}; + +static void +data_offer(void *data, struct wl_data_device *wl_data_device, + struct wl_data_offer *id) +{ +} + +static void +enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, + struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, + struct wl_data_offer *id) +{ +} + +static void +leave(void *data, struct wl_data_device *wl_data_device) +{ +} + +static void +motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, + wl_fixed_t x, wl_fixed_t y) +{ +} + +static void +drop(void *data, struct wl_data_device *wl_data_device) +{ +} + +static void +selection(void *data, struct wl_data_device *wl_data_device, + struct wl_data_offer *id) +{ + struct terminal *term = data; + struct clipboard *clipboard = &term->selection.clipboard; + + if (clipboard->data_offer != NULL) + wl_data_offer_destroy(clipboard->data_offer); + + clipboard->data_offer = id; + wl_data_offer_add_listener(id, &data_offer_listener, term); +} + +const struct wl_data_device_listener data_device_listener = { + .data_offer = &data_offer, + .enter = &enter, + .leave = &leave, + .motion = &motion, + .drop = &drop, + .selection = &selection, +}; diff --git a/selection.h b/selection.h index 84aed3f0..81eb3174 100644 --- a/selection.h +++ b/selection.h @@ -1,8 +1,13 @@ #pragma once +#include + #include "terminal.h" +extern const struct wl_data_device_listener data_device_listener; + void selection_start(struct terminal *term, int col, int row); void selection_update(struct terminal *term, int col, int row); void selection_finalize(struct terminal *term); void selection_cancel(struct terminal *term); +void selection_to_clipboard(struct terminal *term, uint32_t serial); diff --git a/terminal.h b/terminal.h index 8b080ad9..4d73f998 100644 --- a/terminal.h +++ b/terminal.h @@ -23,6 +23,8 @@ struct wayland { struct wl_surface *surface; struct wl_shm *shm; struct wl_seat *seat; + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; struct wl_keyboard *keyboard; struct { struct wl_pointer *pointer; @@ -184,6 +186,13 @@ enum mouse_reporting { MOUSE_URXVT, /* ?1015h */ }; +struct clipboard { + struct wl_data_source *data_source; + struct wl_data_offer *data_offer; + char *text; + uint32_t serial; +}; + struct terminal { pid_t slave; int ptmx; @@ -231,6 +240,8 @@ struct terminal { struct { struct coord start; struct coord end; + struct clipboard primary; + struct clipboard clipboard; } selection; struct grid normal;