diff --git a/doc/rofi-keys.5 b/doc/rofi-keys.5 index 8a20e17c..593f2f74 100644 --- a/doc/rofi-keys.5 +++ b/doc/rofi-keys.5 @@ -96,6 +96,13 @@ Paste clipboard .PP \fBDefault\fP: Control+v,Insert +.SS \fBkb-secondary-copy\fP +.PP +Copy current selection to clipboard + +.PP +\fBDefault\fP: Control+c + .SS \fBkb-clear-line\fP .PP Clear input line diff --git a/doc/rofi-keys.5.markdown b/doc/rofi-keys.5.markdown index ab4c7e7d..aef2bd87 100644 --- a/doc/rofi-keys.5.markdown +++ b/doc/rofi-keys.5.markdown @@ -65,6 +65,12 @@ Paste clipboard **Default**: Control+v,Insert +### **kb-secondary-copy** + +Copy current selection to clipboard + +**Default**: Control+c + ### **kb-clear-line** Clear input line diff --git a/include/keyb.h b/include/keyb.h index ad34e5e5..12418c4c 100644 --- a/include/keyb.h +++ b/include/keyb.h @@ -60,6 +60,8 @@ typedef enum { PASTE_PRIMARY = 1, /** Paste from secondary clipboard */ PASTE_SECONDARY, + /** Copy to secondary clipboard */ + COPY_SECONDARY, /** Clear the entry box. */ CLEAR_LINE, /** Move to front of text */ diff --git a/include/xcb-internal.h b/include/xcb-internal.h index e3e788fb..f2d78ca1 100644 --- a/include/xcb-internal.h +++ b/include/xcb-internal.h @@ -61,6 +61,7 @@ struct _xcb_stuff { NkBindingsSeat *bindings_seat; gboolean mouse_seen; xcb_window_t focus_revert; + char *clipboard; }; #endif diff --git a/include/xcb.h b/include/xcb.h index 69cad53e..afc9c5ec 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -41,6 +41,8 @@ typedef struct _xcb_stuff xcb_stuff; */ extern xcb_stuff *xcb; +void xcb_stuff_set_clipboard(char *data); + /** * Get the root window. * @@ -77,9 +79,9 @@ void window_set_atom_prop(xcb_window_t w, xcb_atom_t prop, xcb_atom_t *atoms, /** Atoms we want to pre-load */ #define EWMH_ATOMS(X) \ - X(_NET_WM_WINDOW_OPACITY), X(I3_SOCKET_PATH), X(UTF8_STRING), X(STRING), \ - X(CLIPBOARD), X(WM_WINDOW_ROLE), X(_XROOTPMAP_ID), X(_MOTIF_WM_HINTS), \ - X(WM_TAKE_FOCUS), X(ESETROOT_PMAP_ID) + X(_NET_WM_WINDOW_OPACITY), X(I3_SOCKET_PATH), X(TARGETS), X(UTF8_STRING), \ + X(STRING), X(CLIPBOARD), X(WM_WINDOW_ROLE), X(_XROOTPMAP_ID), \ + X(_MOTIF_WM_HINTS), X(WM_TAKE_FOCUS), X(ESETROOT_PMAP_ID) /** enumeration of the atoms. */ enum { EWMH_ATOMS(ATOM_ENUM), NUM_NETATOMS }; diff --git a/source/keyb.c b/source/keyb.c index 34878ee0..16a5c595 100644 --- a/source/keyb.c +++ b/source/keyb.c @@ -24,12 +24,11 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ - #include +#include #include "nkutils-bindings.h" #include "rofi.h" #include "xrmoptions.h" -#include typedef struct { guint id; @@ -51,6 +50,10 @@ ActionBindingEntry rofi_bindings[] = { .name = "kb-secondary-paste", .binding = "Control+v,Insert", .comment = "Paste clipboard"}, + {.id = COPY_SECONDARY, + .name = "kb-secondary-copy", + .binding = "Control+c", + .comment = "Copy to clipboard"}, {.id = CLEAR_LINE, .name = "kb-clear-line", .binding = "Control+w", diff --git a/source/view.c b/source/view.c index c3dd9e9a..cfa96438 100644 --- a/source/view.c +++ b/source/view.c @@ -1374,6 +1374,21 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) { xcb->ewmh.UTF8_STRING, XCB_CURRENT_TIME); xcb_flush(xcb->connection); break; + case COPY_SECONDARY: { + char *data = NULL; + unsigned int selected = listview_get_selected(state->list_view); + if (selected < state->filtered_lines) { + data = mode_get_completion(state->sw, state->line_map[selected]); + } else if (state->text && state->text->text) { + data = g_strdup(state->text->text); + } + if (data) { + xcb_stuff_set_clipboard(data); + xcb_set_selection_owner(xcb->connection, CacheState.main_window, + netatoms[CLIPBOARD], XCB_CURRENT_TIME); + xcb_flush(xcb->connection); + } + } break; case SCREENSHOT: rofi_capture_screenshot(); break; @@ -2265,6 +2280,8 @@ void rofi_view_hide(void) { } void rofi_view_cleanup() { + // Clear clipboard data. + xcb_stuff_set_clipboard(NULL); g_debug("Cleanup."); if (CacheState.idle_timeout > 0) { g_source_remove(CacheState.idle_timeout); diff --git a/source/xcb.c b/source/xcb.c index 64d39ef3..e7959b04 100644 --- a/source/xcb.c +++ b/source/xcb.c @@ -87,7 +87,8 @@ struct _xcb_stuff xcb_int = {.connection = NULL, .screen_nbr = -1, .sndisplay = NULL, .sncontext = NULL, - .monitors = NULL}; + .monitors = NULL, + .clipboard = NULL}; xcb_stuff *xcb = &xcb_int; /** @@ -1216,6 +1217,52 @@ static void main_loop_x11_event_handler_view(xcb_generic_event_t *event) { } break; } + case XCB_SELECTION_CLEAR: { + g_debug("Selection Clear."); + xcb_stuff_set_clipboard(NULL); + } break; + case XCB_SELECTION_REQUEST: { + g_debug("Selection Request."); + xcb_selection_request_event_t *req = (xcb_selection_request_event_t *)event; + if (req->selection == netatoms[CLIPBOARD]) { + xcb_atom_t targets[2]; + xcb_selection_notify_event_t selection_notify = { + .response_type = XCB_SELECTION_NOTIFY, + .sequence = 0, + .time = req->time, + .requestor = req->requestor, + .selection = req->selection, + .target = req->target, + .property = XCB_ATOM_NONE, + }; + // If no clipboard, we return NONE. + if (xcb->clipboard) { + // Request for UTF-8 + if (req->target == netatoms[UTF8_STRING]) { + g_debug("Selection Request UTF-8."); + xcb_change_property(xcb->connection, XCB_PROP_MODE_REPLACE, + req->requestor, req->property, + netatoms[UTF8_STRING], 8, + strlen(xcb->clipboard) + 1, xcb->clipboard); + selection_notify.property = req->property; + } else if (req->target == netatoms[TARGETS]) { + g_debug("Selection Request Targets."); + // We currently only support UTF8 from clipboard. So indicate this. + targets[0] = netatoms[UTF8_STRING]; + xcb_change_property(xcb->connection, XCB_PROP_MODE_REPLACE, + req->requestor, req->property, XCB_ATOM_ATOM, 32, + 1, targets); + selection_notify.property = req->property; + } + } + + xcb_send_event(xcb->connection, + 0, // propagate + req->requestor, XCB_EVENT_MASK_NO_EVENT, + (const char *)&selection_notify); + xcb_flush(xcb->connection); + } + } break; case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *bre = (xcb_button_release_event_t *)event; NkBindingsMouseButton button; @@ -1257,14 +1304,13 @@ static void main_loop_x11_event_handler_view(xcb_generic_event_t *event) { gchar *text; xcb->last_timestamp = xkpe->time; - if ( config.xserver_i300_workaround ) { + if (config.xserver_i300_workaround) { text = nk_bindings_seat_handle_key_with_modmask( xcb->bindings_seat, NULL, xkpe->state, xkpe->detail, NK_BINDINGS_KEY_STATE_PRESS); } else { - text = nk_bindings_seat_handle_key( - xcb->bindings_seat, NULL, xkpe->detail, - NK_BINDINGS_KEY_STATE_PRESS); + text = nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkpe->detail, + NK_BINDINGS_KEY_STATE_PRESS); } if (text != NULL) { rofi_view_handle_text(state, text); @@ -1294,9 +1340,10 @@ static gboolean main_loop_x11_event_handler(xcb_generic_event_t *ev, g_main_loop_quit(xcb->main_loop); return G_SOURCE_REMOVE; } - // DD: it seems this handler often gets dispatched while the queue in GWater is empty. - // resulting in a NULL for ev. This seems not an error. - //g_warning("main_loop_x11_event_handler: ev == NULL, status == %d", status); + // DD: it seems this handler often gets dispatched while the queue in GWater + // is empty. resulting in a NULL for ev. This seems not an error. + // g_warning("main_loop_x11_event_handler: ev == NULL, status == %d", + // status); return G_SOURCE_CONTINUE; } uint8_t type = ev->response_type & ~0x80; @@ -1815,3 +1862,7 @@ void x11_set_cursor(xcb_window_t window, X11CursorType type) { xcb_change_window_attributes(xcb->connection, window, XCB_CW_CURSOR, &(cursors[type])); } +void xcb_stuff_set_clipboard(char *data) { + g_free(xcb->clipboard); + xcb->clipboard = data; +}