diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst new file mode 100644 index 000000000..80678d6b6 --- /dev/null +++ b/docs/dnd-protocol.rst @@ -0,0 +1,54 @@ +The Drag and Drop protocol +============================================== + +.. versionadded:: 0.47.0 + +This protocol enables drag and drop functionality for terminal programs +that is as good as the drag and drop functionality available for GUI +programs. + +There is one central escape code used for this protocol, which is of the form:: + + OSC _dnd_code ; metadata ; base64 encoded payload ST + +Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)``. The ``metadata`` is a colon +separated list of ``key=value`` pairs. The final part of the escape code is the +:rfc:`base64 <4648>` encoded payload data, whose meaning depends on the +metadata. The payload must be no more than 4096 bytes *before base64 encoding*. + +Accepting drops +----------------- + +In order to inform the terminal emulator that the program accepts drops, it +must, send the following escape code:: + + OSC _dnd_code ; t=a ; payload ST + +The payload here is a space separated list of MIME types the program accepts. +The list of MIME types is optional, it is needed if the program wants to accept +exotic or private use MIME types on platforms such as macOS, where the system +does not deliver drop events unless the MIME type is registered. + +When the program is done accepting drops, or at exit, it should send the escape +code:: + + OSC _dnd_code ; t=A ST + +to inform the terminal that it no longer wants drops. + +Metadata reference +--------------------------- + +The table below shows all the metadata keys as well as what values they can +take, and the default value they take when missing. All integers are 32-bit. + +======= ==================== ========= ================= +Key Value Default Description +======= ==================== ========= ================= +``t`` Single character. ``a`` The overall action this graphics command is performing. + ``(a, A, ``t`` - transmit data, ``T`` - transmit data and display image, + )`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image, + ``d`` - delete image, ``f`` - transmit data for animation frames, + ``a`` - control animation, ``c`` - compose animation frames + + diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index c250d4e62..91adfd059 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -331,7 +331,7 @@ def parsers() -> None: write_header(text, 'kitty/parse-multicell-command.h') keymap = { - 't': ('type', flag('ae')), + 't': ('type', flag('aA')), 'm': ('more', 'uint'), } text = generate( diff --git a/kitty/glfw.c b/kitty/glfw.c index 75f8b232b..9710a0d9d 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -765,6 +765,29 @@ read_drop_data(GLFWwindow *window, GLFWDropEvent *ev) { #undef finish } +void +register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on) { + Window *w = window_for_window_id(window_id); + OSWindow *osw = os_window_for_kitty_window(window_id); + if (w && osw && osw->handle) { + w->accepts_drops = on; + if (on && payload && payload_sz) { +#ifdef __APPLE__ + RAII_ALLOC(char, copy, malloc(payload_sz + 1)); if (!copy) return; + RAII_ALLOC(const char*, mimes, calloc(payload_sz, sizeof(char*))); if (!mimes) return; + memcpy(copy, payload, payload_sz); copy[payload_sz] = 0; + size_t num = 0; + char* token = strtok(copy, " "); + while (token != NULL) { + mimes[num++] = token; + token = strtok(NULL, " "); + } + glfwCocoaRegisterMIMETypes(osw->handle, mimes, num); +#endif + } + } +} + static void on_drop(GLFWwindow *window, GLFWDropEvent *ev) { if (!set_callback_window(window)) return; diff --git a/kitty/screen.c b/kitty/screen.c index 066e95574..b912750c2 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1511,11 +1511,8 @@ void screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *payload) { if (!self->window_id) return; switch(cmd->type) { - case 'a': - (void)payload; - break; - case 'e': - break; + case 'a': register_drop_window(self->window_id, payload, cmd->payload_sz, true); break; + case 'A': register_drop_window(self->window_id, NULL, 0, false); break; } } diff --git a/kitty/screen.h b/kitty/screen.h index 337433d55..b2b05315a 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -323,6 +323,7 @@ bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms); void screen_check_pause_rendering(Screen *self, monotonic_t now); void screen_designate_charset(Screen *self, uint32_t which, uint32_t as); void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params); +void register_drop_window(id_type window_id, const uint8_t *payload, size_t payload_sz, bool on); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/state.h b/kitty/state.h index 04f045251..d51dc98e2 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -206,7 +206,7 @@ typedef struct WindowBarData { typedef struct Window { id_type id; - bool visible; + bool visible, accepts_drops; PyObject *title; WindowRenderData render_data; WindowRenderData window_title_render_data;