diff --git a/glfw/glfw3.h b/glfw/glfw3.h index f6c5db0c1..a19bf1a7c 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1729,6 +1729,7 @@ typedef enum { GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION } GLFWClipboardType; typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype); +typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz); /*! @brief Video mode type. * @@ -5231,32 +5232,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid); */ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state); -/*! @brief Sets the clipboard to the specified string. - * - * This function sets the system clipboard to the specified, UTF-8 encoded - * string. - * - * @param[in] window Deprecated. Any valid window or `NULL`. - * @param[in] string A UTF-8 encoded string. - * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. - * - * @remark @wayland Clipboard is currently unimplemented. - * - * @pointer_lifetime The specified string is copied before this function - * returns. - * - * @thread_safety This function must only be called from the main thread. - * - * @sa @ref clipboard - * @sa @ref glfwGetClipboardString - * - * @since Added in version 3.0. - * - * @ingroup input - */ GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_iter); +GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object); /*! @brief Returns the GLFW time. * diff --git a/glfw/input.c b/glfw/input.c index a26b8d1b8..b6fa66552 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -1534,6 +1534,11 @@ void _glfw_free_clipboard_data(_GLFWClipboardData *cd) { memset(cd, 0, sizeof(cd[0])); } +GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { + _GLFW_REQUIRE_INIT(); + _glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object); +} + GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_data) { assert(mime_types != NULL); assert(get_data != NULL); @@ -1552,20 +1557,6 @@ GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const c _glfwPlatformSetClipboard(clipboard_type); } -GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle UNUSED) -{ - _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - return _glfwPlatformGetClipboardString(); -} - -#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) || defined(__APPLE__) -GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle UNUSED) -{ - _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - return _glfwPlatformGetPrimarySelectionString(); -} -#endif - GLFWAPI monotonic_t glfwGetTime(void) { _GLFW_REQUIRE_INIT_OR_RETURN(0); diff --git a/glfw/internal.h b/glfw/internal.h index 07efdf367..5873c5ec9 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -687,8 +687,7 @@ bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp); void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp); void _glfwPlatformSetClipboard(GLFWClipboardType t); -const char* _glfwPlatformGetClipboardString(void); -const char* _glfwPlatformGetPrimarySelectionString(void); +void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object); bool _glfwPlatformInitJoysticks(void); void _glfwPlatformTerminateJoysticks(void); diff --git a/glfw/x11_window.c b/glfw/x11_window.c index a12d0b9c7..25212c0e3 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -922,28 +922,28 @@ static void handleSelectionRequest(XEvent* event) XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } -static const char* getSelectionString(Atom selection) +static void +getSelectionString(Atom selection, const char *mime_type, Atom *targets, size_t num_targets, GLFWclipboardwritedatafun write_data, void *object) { - char* selectionString = NULL; - const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING }; - const size_t targetCount = sizeof(targets) / sizeof(targets[0]); - +#define XFREE(x) { if (x) XFree(x); x = NULL; } if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) { // Instead of doing a large number of X round-trips just to put this // string into a window property and then read it back, just return it _GLFWClipboardData *cd = selection == _glfw.x11.PRIMARY ? &_glfw.primary : &_glfw.clipboard; - char *data = NULL; size_t sz = get_clipboard_data(cd, "text/plain", &data); - if (data && sz) return data; + char *data = NULL; size_t sz = get_clipboard_data(cd, mime_type, &data); + write_data(object, data, sz); free(data); + return; } - for (size_t i = 0; i < targetCount; i++) + bool found = false; + for (size_t i = 0; !found && i < num_targets; i++) { - char* data; - Atom actualType; - int actualFormat; - unsigned long itemCount, bytesAfter; + char* data = NULL; + Atom actualType = None; + int actualFormat = 0; + unsigned long itemCount = 0, bytesAfter = 0; monotonic_t start = glfwGetTime(); XEvent notification, dummy; @@ -960,8 +960,7 @@ static const char* getSelectionString(Atom selection) ¬ification)) { monotonic_t time = glfwGetTime(); - if (time - start > s_to_monotonic_t(2ll)) - return ""; + if (time - start > s_to_monotonic_t(2ll)) return; waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } @@ -988,9 +987,6 @@ static const char* getSelectionString(Atom selection) if (actualType == _glfw.x11.INCR) { - size_t size = 1; - char* string = NULL; - for (;;) { start = glfwGetTime(); @@ -1001,13 +997,12 @@ static const char* getSelectionString(Atom selection) { monotonic_t time = glfwGetTime(); if (time - start > s_to_monotonic_t(2ll)) { - free(string); - return ""; + return; } waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } - XFree(data); + XFREE(data); XGetWindowProperty(_glfw.x11.display, notification.xselection.requestor, notification.xselection.property, @@ -1023,47 +1018,37 @@ static const char* getSelectionString(Atom selection) if (itemCount) { - size += itemCount; - string = realloc(string, size); - string[size - itemCount - 1] = '\0'; - strcat(string, data); - } - - if (!itemCount) - { - if (targets[i] == XA_STRING) - { - selectionString = convertLatin1toUTF8(string); - free(string); + const char *string = data; + if (targets[i] == XA_STRING) { + string = convertLatin1toUTF8(data); + itemCount = strlen(string); } - else - selectionString = string; + bool ok = write_data(object, string, itemCount); + if (string != data) free((void*)string); + if (!ok) { XFREE(data); break; } + } else { found = true; break; } - break; - } } } else if (actualType == targets[i]) { - if (targets[i] == XA_STRING) - selectionString = convertLatin1toUTF8(data); - else - selectionString = _glfw_strdup(data); + if (targets[i] == XA_STRING) { + const char *string = convertLatin1toUTF8(data); + write_data(object, string, strlen(string)); free((void*)string); + } else write_data(object, data, itemCount); + found = true; } - XFree(data); + XFREE(data); - if (selectionString) - break; } - if (!selectionString) + if (!found) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "X11: Failed to convert selection to string"); } - - return selectionString; +#undef XFREE } // Make the specified window and its video mode active on its monitor @@ -2914,14 +2899,18 @@ void _glfwPlatformSetClipboard(GLFWClipboardType t) { } } -const char* _glfwPlatformGetClipboardString(void) -{ - return getSelectionString(_glfw.x11.CLIPBOARD); -} - -const char* _glfwPlatformGetPrimarySelectionString(void) -{ - return getSelectionString(_glfw.x11.PRIMARY); +void +_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { + // TODO: Handle NULL mime_type + Atom atoms[2], which = clipboard_type == GLFW_PRIMARY_SELECTION ? _glfw.x11.PRIMARY : _glfw.x11.CLIPBOARD; + size_t count = 1; + if (strcmp(mime_type, "text/plain") == 0) { + atoms[0] = _glfw.x11.UTF8_STRING; + atoms[count++] = XA_STRING; + } else { + atoms[0] = atom_for_mime(mime_type).atom; + } + getSelectionString(which, mime_type, atoms, count, write_data, object); } EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) diff --git a/kitty/clipboard.py b/kitty/clipboard.py index 56a43cf4b..14df027aa 100644 --- a/kitty/clipboard.py +++ b/kitty/clipboard.py @@ -2,10 +2,10 @@ # License: GPLv3 Copyright: 2022, Kovid Goyal import io -from typing import IO, Callable, Dict, Union +from typing import IO, Callable, Dict, Union, List from .constants import supports_primary_selection -from .fast_data_types import GLFW_CLIPBOARD, get_boss, set_clipboard_data_types +from .fast_data_types import GLFW_CLIPBOARD, get_boss, set_clipboard_data_types, get_clipboard_mime DataType = Union[bytes, 'IO[bytes]'] @@ -26,7 +26,12 @@ class Clipboard: set_clipboard_data_types(self.clipboard_type, tuple(self.data)) def get_text(self) -> str: - raise NotImplementedError('TODO: Implement this') + parts: List[bytes] = [] + self.get_mime("text/plain", parts.append) + return b''.join(parts).decode('utf-8', 'replace') + + def get_mime(self, mime: str, output: Callable[[bytes], None]) -> None: + get_clipboard_mime(self.clipboard_type, mime, output) def __call__(self, mime: str) -> Callable[[], bytes]: data = self.data.get(mime, b'') diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index b874fc96a..f9938c6ac 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1473,3 +1473,4 @@ def set_use_os_log(yes: bool) -> None: ... def get_docs_ref_map() -> bytes: ... def clearenv() -> None: ... def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ... +def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ... diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 84e8d871a..46c3d8048 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -368,6 +368,9 @@ load_glfw(const char* path) { *(void **) (&glfwSetClipboardDataTypes_impl) = dlsym(handle, "glfwSetClipboardDataTypes"); if (glfwSetClipboardDataTypes_impl == NULL) fail("Failed to load glfw function glfwSetClipboardDataTypes with error: %s", dlerror()); + *(void **) (&glfwGetClipboard_impl) = dlsym(handle, "glfwGetClipboard"); + if (glfwGetClipboard_impl == NULL) fail("Failed to load glfw function glfwGetClipboard with error: %s", dlerror()); + *(void **) (&glfwGetTime_impl) = dlsym(handle, "glfwGetTime"); if (glfwGetTime_impl == NULL) fail("Failed to load glfw function glfwGetTime with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 47c280c10..43cc3e6f7 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1467,6 +1467,7 @@ typedef enum { GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION } GLFWClipboardType; typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype); +typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz); /*! @brief Video mode type. * @@ -2096,6 +2097,10 @@ typedef void (*glfwSetClipboardDataTypes_func)(GLFWClipboardType, const char* co GFW_EXTERN glfwSetClipboardDataTypes_func glfwSetClipboardDataTypes_impl; #define glfwSetClipboardDataTypes glfwSetClipboardDataTypes_impl +typedef void (*glfwGetClipboard_func)(GLFWClipboardType, const char*, GLFWclipboardwritedatafun, void*); +GFW_EXTERN glfwGetClipboard_func glfwGetClipboard_impl; +#define glfwGetClipboard glfwGetClipboard_impl + typedef monotonic_t (*glfwGetTime_func)(void); GFW_EXTERN glfwGetTime_func glfwGetTime_impl; #define glfwGetTime glfwGetTime_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index f5c7a63a4..505769dfe 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1590,16 +1590,10 @@ get_clipboard_data(const char *mime_type, void *iter, GLFWClipboardType ct) { if (global_state.boss == NULL) return ans; if (iter == NULL) { PyObject *c = PyObject_GetAttrString(global_state.boss, ct == GLFW_PRIMARY_SELECTION ? "primary_selection" : "clipboard"); - if (c == NULL) { - PyErr_Print(); - return ans; - } + if (c == NULL) { return ans; } PyObject *i = PyObject_CallFunction(c, "s", mime_type); Py_DECREF(c); - if (!i) { - PyErr_Print(); - return ans; - } + if (!i) { return ans; } ans.iter = i; return ans; } @@ -1621,14 +1615,36 @@ set_clipboard_data_types(PyObject *self UNUSED, PyObject *args) { PyObject *mta; int ctype; if (!PyArg_ParseTuple(args, "iO!", &ctype, &PyTuple_Type, &mta)) return NULL; - const char **mime_types = calloc(PyTuple_GET_SIZE(mta), sizeof(char*)); - if (!mime_types) return PyErr_NoMemory(); - for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(mta); i++) mime_types[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(mta, i)); - glfwSetClipboardDataTypes(ctype, mime_types, PyTuple_GET_SIZE(mta), get_clipboard_data); - free(mime_types); + if (glfwSetClipboardDataTypes) { + const char **mime_types = calloc(PyTuple_GET_SIZE(mta), sizeof(char*)); + if (!mime_types) return PyErr_NoMemory(); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(mta); i++) mime_types[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(mta, i)); + glfwSetClipboardDataTypes(ctype, mime_types, PyTuple_GET_SIZE(mta), get_clipboard_data); + free(mime_types); + } else log_error("GLFW not initialized cannot set clipboard data"); + if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } +static bool +write_clipboard_data(void *callback, const char *data, size_t sz) { + Py_ssize_t z = sz; + PyObject *ret = PyObject_CallFunction(callback, "y#", data, z); + bool ok = false; + if (ret != NULL) { ok = true; Py_DECREF(ret); } + return ok; +} + +static PyObject* +get_clipboard_mime(PyObject *self UNUSED, PyObject *args) { + int ctype; + const char *mime; + PyObject *callback; + if (!PyArg_ParseTuple(args, "izO", &ctype, &mime, &callback)) return NULL; + glfwGetClipboard(ctype, mime, write_clipboard_data, callback); + if (PyErr_Occurred()) return NULL; + Py_RETURN_NONE; +} // Boilerplate {{{ static PyMethodDef module_methods[] = { @@ -1636,6 +1652,7 @@ static PyMethodDef module_methods[] = { {"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(set_default_window_icon, METH_VARARGS), METHODB(set_clipboard_data_types, METH_VARARGS), + METHODB(get_clipboard_mime, METH_VARARGS), METHODB(toggle_secure_input, METH_NOARGS), METHODB(get_content_scale_for_window, METH_NOARGS), METHODB(ring_bell, METH_NOARGS),