Compare commits

...

55 Commits

Author SHA1 Message Date
Kovid Goyal
dbb97a62bf version 0.26.3 2022-09-22 13:48:20 +05:30
Kovid Goyal
b1fa1c9820 Make test a bit more robust 2022-09-21 23:16:34 +05:30
Kovid Goyal
2dc9488909 switch python autoformatter to isort+black+autoflake 2022-09-21 13:40:37 +05:30
Kovid Goyal
e2a251224d ssh kitten: Better error message when tar is missing 2022-09-21 07:34:15 +05:30
Kovid Goyal
2a2dca8a58 ... 2022-09-18 08:26:40 +05:30
Kovid Goyal
e1f3cb85c0 Window for id should not fail when not called from a callback 2022-09-18 08:24:02 +05:30
Kovid Goyal
13cf38d6c6 Update changelog 2022-09-17 18:21:28 +05:30
Kovid Goyal
5180a41e87 Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated
Fixes #5506
2022-09-17 18:21:10 +05:30
Kovid Goyal
1c44da2b4a Nicer error message when trying to open the config file and no editor is available 2022-09-13 17:24:11 +05:30
Kovid Goyal
bcd78c3940 Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask.
Fixes #5500
2022-09-13 07:51:32 +05:30
Kovid Goyal
fe2f3d9870 Wayland: Remove the mutter specific ignoring of the first empty surface configure event. It seems to be no longer necessary on current mutter, with the recent Wayland changes 2022-09-11 14:37:50 +05:30
Kovid Goyal
dcf2152a7a Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action 2022-09-11 14:25:49 +05:30
Kovid Goyal
58a3baaf0f Wayland: Use XDG_ACTIVATION_TOKEN when present at launch 2022-09-11 13:43:52 +05:30
Kovid Goyal
b247759d30 DRYer 2022-09-11 13:15:42 +05:30
Kovid Goyal
ba8d30896b Try to use the activation protocol to focus windows 2022-09-11 10:39:28 +05:30
Kovid Goyal
4d30ae55f3 Wayland: Mark windows in which a bell as urgent on compositors that support the xdg-activation protocol 2022-09-11 09:33:41 +05:30
Kovid Goyal
983ab3c67d build wayland xdg-activation protocol 2022-09-10 16:08:38 +05:30
Kovid Goyal
0bf228b030 Wayland: Micro-optimization: Assume window will be shown on primary monitor so create it with its scale set to that of the primary monitor 2022-09-10 15:23:56 +05:30
Kovid Goyal
29b3a92ffc Wayland: swap buffers immediately on scale change to ensure attached buffer is a multiple of the new scale
Earlier we were only swapping buffers when ready to draw, but the
Wayland protocol  requires the attached buffer to be a multiple of the
scale. We cannot guarantee an application side swap will be triggered
before the next commit, so instead we blank the new buffer swap it in
the GLFW backend itself.

Fixes #5467
2022-09-10 15:09:46 +05:30
Kovid Goyal
23d73ec933 ... 2022-09-10 12:52:27 +05:30
Kovid Goyal
bc650f6230 Output initial window creation size in debug 2022-09-10 12:28:53 +05:30
Kovid Goyal
5272318d67 Update changelog 2022-09-10 12:10:30 +05:30
Kovid Goyal
5ba887ff68 Wayland: Fix for bug in NVIDIA drivers that prevents transparency working
Fixes #5479
2022-09-10 11:57:18 +05:30
Kovid Goyal
67d70f8b8f Apparently on Wayland we need to set the swap interval on every window not just the first. It's somehow not shared even though the context is shared? Fixes #5495 2022-09-10 10:41:31 +05:30
Kovid Goyal
0fac6e4de9 render frames should be always respected on macOS
Only Wayland suffers from flicker on resize when using render frames.
2022-09-10 10:25:42 +05:30
Kovid Goyal
1e7a11b278 Wayland: When swapping buffers in response to a configure event ensure the window is the current context otherwise the swap fails
The swap failing will mean the buffer is not attached which might cause
issues with some compsitors.
2022-09-10 10:22:09 +05:30
Kovid Goyal
6741ac2087 Sessions: Allow controlling which OS Window is active via the focus_os_window directive 2022-09-10 08:27:36 +05:30
Kovid Goyal
7b2e29a6a8 ... 2022-09-10 07:52:26 +05:30
Kovid Goyal
6f147544d2 Add ! to env var serialization test 2022-09-10 07:21:15 +05:30
Kovid Goyal
89570d3b52 ssh kitten: Fix KITTY_PUBLIC_KEY not being encoded properly when transmitting
Fixes #5496
2022-09-10 07:09:58 +05:30
Kovid Goyal
dd4a65e0c9 ... 2022-09-09 15:41:45 +05:30
Kovid Goyal
79fba9dd9a Use the dynamic property instead of testing identifier serialization 2022-09-09 15:39:00 +05:30
Kovid Goyal
b123c21010 Update changelog
Fixes #5483
2022-09-09 13:13:58 +05:30
Kovid Goyal
9c5d1f9771 Pass null bytes through the clipboard on cocoa 2022-09-09 13:12:12 +05:30
Kovid Goyal
5131224b59 Convenience code for debugging 2022-09-09 12:20:28 +05:30
Kovid Goyal
be89da2271 Handle dyn prefixes 2022-09-09 11:59:49 +05:30
Kovid Goyal
45b408a78b Allow setting arbitrary mime types 2022-09-09 11:14:12 +05:30
Kovid Goyal
0e7da8643e Get cocoa clipboard backend working 2022-09-09 10:59:10 +05:30
Kovid Goyal
410b1816ad More work on cocoa clipboard backend 2022-09-09 07:39:38 +05:30
Kovid Goyal
bcfa2a64e4 Start work on new clipboard api support for macos 2022-09-08 21:45:13 +05:30
Kovid Goyal
91c00fb5ac Implement the new clipboard API for Wayland 2022-09-08 17:29:18 +05:30
Kovid Goyal
c58d217d32 Only warn about cursor size once 2022-09-08 16:28:40 +05:30
Kovid Goyal
a9b6047741 Simplify handling of self offers 2022-09-08 15:34:14 +05:30
Kovid Goyal
6b432bee4f Cleanup sending/receiving mimetype atoms 2022-09-08 12:45:35 +05:30
Kovid Goyal
087f0b449f ... 2022-09-08 12:45:35 +05:30
Kovid Goyal
96160b52b6 Code to get the available mime types in the clipboard 2022-09-08 12:45:34 +05:30
Kovid Goyal
d17a6cd3a3 Get reading from clipboard working 2022-09-08 12:45:34 +05:30
Kovid Goyal
7e1380cc0d Start work on giving GLFW a proper clipboard API 2022-09-08 12:45:34 +05:30
Kovid Goyal
9a99554ed3 Also have confirm() and get_line() return their results on overlay removal 2022-09-08 12:06:25 +05:30
Kovid Goyal
19799bd538 When choosing an option call the callback after the popup window is removed. This ensures the active window is unchanged when the callback is called. Fixes #5488 2022-09-08 12:01:38 +05:30
Kovid Goyal
902e94ceac Fix #5484 2022-09-07 15:09:24 +05:30
Kovid Goyal
5bb2cb06a9 Fix #5477 2022-09-05 21:43:56 +05:30
Kovid Goyal
1559a2e15a Cleanup previous PR 2022-09-05 21:19:58 +05:30
Kovid Goyal
35f94963cd Merge branch 'skip-tests-bash-debug' of https://github.com/jamessan/kitty 2022-09-05 21:18:52 +05:30
James McCoy
f0d497dfe0 Skip bash shell_integration tests if bash is a debug build
Switch to using BASH_VERSINFO rather than BASH_VERSION, since it
provides structured data.

    BASH_VERSINFO
           A readonly array variable whose members hold version information for this instance of bash.  The values assigned to the array members are as follows:
           BASH_VERSINFO[0]        The major version number (the release).
           BASH_VERSINFO[1]        The minor version number (the version).
           BASH_VERSINFO[2]        The patch level.
           BASH_VERSINFO[3]        The build version.
           BASH_VERSINFO[4]        The release status (e.g., beta1).
           BASH_VERSINFO[5]        The value of MACHTYPE.

When release status is not "release", bash builds are in debug mode and
output extra information which disturbs the integration.

Closes #5473
2022-09-05 11:37:37 -04:00
48 changed files with 1300 additions and 649 deletions

View File

@@ -35,6 +35,31 @@ mouse anywhere in the current command to move the cursor there. See
Detailed list of changes
-------------------------------------
0.26.3 [2022-09-22]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland: Mark windows in which a bell occurs as urgent on compositors that support the xdg-activation protocol
- Allow passing null bytes through the system clipboard (:iss:`5483`)
- ssh kitten: Fix :envvar:`KITTY_PUBLIC_KEY` not being encoded properly when transmitting (:iss:`5496`)
- Sessions: Allow controlling which OS Window is active via the ``focus_os_window`` directive
- Wayland: Fix for bug in NVIDIA drivers that prevents transparency working (:iss:`5479`)
- Wayland: Fix for a bug that could cause kitty to become non-responsive when
using multiple OS windows in a single instance on some compositors (:iss:`5495`)
- Wayland: Fix for a bug preventing kitty from starting on Hyprland when using a non-unit scale (:iss:`5467`)
- Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action
- Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask. (:iss:`5500`)
- Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated (:iss:`5506`)
0.26.2 [2022-09-05]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -163,8 +163,10 @@ option in :file:`kitty.conf`. For example:
launch sh
# Resize the current window (see the resize_window action for details)
resize_window wider 2
# Make the current window the active (focused) window
# Make the current window the active (focused) window in its tab
focus
# Make the current OS Window the globally active window (not supported on Wayland)
focus_os_window
launch emacs
.. note::

View File

@@ -992,8 +992,6 @@ void _glfwPlatformTerminate(void)
_glfw.ns.appleSettings = nil;
}
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }

View File

@@ -183,7 +183,6 @@ typedef struct _GLFWlibraryNS
char keyName[64];
char text[256];
char* clipboardString;
CGPoint cascadePoint;
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;

View File

@@ -30,6 +30,8 @@
#include "../kitty/monotonic.h"
#include <Availability.h>
#import <CoreServices/CoreServices.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <float.h>
#include <string.h>
@@ -2506,55 +2508,150 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
return made_fullscreen;
}
void _glfwPlatformSetClipboardString(const char* string)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[pasteboard setString:@(string) forType:NSPasteboardTypeString];
// Clipboard {{{
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
static NSString*
mime_to_uti(const char *mime) {
if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString;
if (@available(macOS 11.0, *)) {
UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released
if (t != nil && !t.dynamic) return t.identifier;
}
return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released
}
void _glfwPlatformSetPrimarySelectionString(const char* string) {
(void)string;
// Apple doesnt have a primary selection
static const char*
uti_to_mime(NSString *uti) {
if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain";
if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String];
if (@available(macOS 11.0, *)) {
UTType *t = [UTType typeWithIdentifier:uti]; // auto-released
if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String];
}
return "";
}
const char* _glfwPlatformGetPrimarySelectionString(void) { return ""; }
const char* _glfwPlatformGetClipboardString(void)
{
static void
list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) {
#define w(x) { if (ok) ok = write_data(object, x, strlen(x)); }
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
free(_glfw.ns.clipboardString); _glfw.ns.clipboardString = NULL;
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
BOOL has_file_urls = [pasteboard canReadObjectForClasses:@[[NSURL class]] options:options];
BOOL has_strings = [pasteboard canReadObjectForClasses:@[[NSString class]] options:nil];
/* NSLog(@"has_file_urls: %d has_strings: %d", has_file_urls, has_strings); */
bool ok = true;
if (has_strings) w("text/plain");
if (has_file_urls) w("text/local-path-list");
for (NSPasteboardItem * item in pasteboard.pasteboardItems) {
for (NSPasteboardType type in item.types) {
/* NSLog(@"%@", type); */
const char *mime = uti_to_mime(type);
if (mime && mime[0] && ![@(mime) hasPrefix:@"text/plain"]) {
/* NSLog(@"ut: %@ mt: %@ tags: %@", ut, ut.preferredMIMEType, ut.tags); */
w(mime);
}
}
}
#undef w
}
static void
get_text_plain(GLFWclipboardwritedatafun write_data, void *object) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
bool found = false;
if (objs) {
const NSUInteger count = [objs count];
if (count) {
NSMutableString *path_list = [NSMutableString stringWithCapacity:4096]; // auto-released
NSMutableString *text_list = [NSMutableString stringWithCapacity:4096]; // auto-released
NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released
NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released
for (NSUInteger i = 0; i < count; i++) {
id obj = objs[i];
if ([obj isKindOfClass:[NSURL class]]) {
NSURL *url = (NSURL*)obj;
if (url.fileURL && url.fileSystemRepresentation) {
if ([path_list length] > 0) [path_list appendString:@("\n")];
[path_list appendString:@(url.fileSystemRepresentation)];
if ([path_list length] > 0) [path_list appendBytes:"\n" length:1];
[path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)];
}
} else if ([obj isKindOfClass:[NSString class]]) {
if ([text_list length] > 0) [text_list appendString:@("\n")];
[text_list appendString:obj];
if ([text_list length] > 0) [text_list appendBytes:"\n" length:1];
[text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
}
}
const char *text = NULL;
if (path_list.length > 0) text = [path_list UTF8String];
else if (text_list.length > 0) text = [text_list UTF8String];
if (text) _glfw.ns.clipboardString = _glfw_strdup(text);
const NSMutableData *text = nil;
if (path_list.length > 0) text = path_list;
else if (text_list.length > 0) text = text_list;
if (text) {
found = true;
write_data(object, text.mutableBytes, text.length);
}
}
}
if (!_glfw.ns.clipboardString) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve object from pasteboard");
return _glfw.ns.clipboardString;
if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard");
}
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
if (clipboard_type != GLFW_CLIPBOARD) return;
if (mime_type == NULL) {
list_clipboard_mimetypes(write_data, object);
return;
}
if (strcmp(mime_type, "text/plain") == 0) {
get_text_plain(write_data, object);
return;
}
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
/* NSLog(@"mime: %s uti: %@", mime_type, mime_to_uti(mime_type)); */
NSPasteboardType t = [pasteboard availableTypeFromArray:@[mime_to_uti(mime_type)]];
/* NSLog(@"available type: %@", t); */
if (t != nil) {
NSData *data = [pasteboard dataForType:t]; // auto-released
/* NSLog(@"data: %@", data); */
if (data != nil && data.length > 0) {
write_data(object, data.bytes, data.length);
}
}
}
static NSMutableData*
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime) {
NSMutableData *ans = [NSMutableData dataWithCapacity:8192];
if (ans == nil) return nil;
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
void *iter = chunk.iter;
if (!iter) return ans;
while (true) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
[ans appendBytes:chunk.data length:chunk.sz];
if (chunk.free) chunk.free((void*)chunk.free_data);
}
cd->get_data(NULL, iter, cd->ctype);
return ans;
}
void
_glfwPlatformSetClipboard(GLFWClipboardType t) {
if (t != GLFW_CLIPBOARD) return;
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSMutableArray<NSPasteboardType> *ptypes = [NSMutableArray arrayWithCapacity:_glfw.clipboard.num_mime_types]; // auto-released
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
[ptypes addObject:mime_to_uti(_glfw.clipboard.mime_types[i])];
}
[pasteboard declareTypes:ptypes owner:nil];
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
NSMutableData *data = get_clipboard_data(&_glfw.clipboard, _glfw.clipboard.mime_types[i]); // auto-released
/* NSLog(@"putting data: %@ for: %s with UTI: %@", data, _glfw.clipboard.mime_types[i], ptypes[i]); */
if (data != nil) [pasteboard setData:data forType:ptypes[i]];
}
}
// }}}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)

7
glfw/egl_context.c vendored
View File

@@ -600,8 +600,11 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
if (_glfw.egl.KHR_gl_colorspace)
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
if (_glfw.egl.EXT_present_opaque)
setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent);
// Disabled because it prevents transparency from working on NVIDIA drivers under Wayland
// https://github.com/kovidgoyal/kitty/issues/5479
// We anyway dont use the alpha bits for anything.
/* if (_glfw.egl.EXT_present_opaque) */
/* setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent); */
setAttrib(EGL_NONE, EGL_NONE);

View File

@@ -111,7 +111,7 @@ def init_env(
elif module == 'cocoa':
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo UniformTypeIdentifiers'.split():
ans.ldpaths.extend(('-framework', f_))
elif module == 'wayland':
@@ -245,6 +245,8 @@ def generate_wrappers(glfw_header: str) -> None:
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)

73
glfw/glfw3.h vendored
View File

@@ -1716,8 +1716,21 @@ typedef void (* GLFWjoystickfun)(int,int);
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
typedef void (* GLFWtickcallback)(void*);
typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data);
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
typedef char* (* GLFWcurrentselectionfun)(void);
typedef void (* GLFWclipboarddatafreefun)(void* data);
typedef struct GLFWDataChunk {
const char *data;
size_t sz;
GLFWclipboarddatafreefun free;
void *iter, *free_data;
} GLFWDataChunk;
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.
*
@@ -5220,64 +5233,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 glfwSetClipboardString(GLFWwindow* window, const char* string);
/*! @brief Returns the contents of the clipboard as a string.
*
* This function returns the contents of the system clipboard, if it contains
* or is convertible to a UTF-8 encoded string. If the clipboard is empty or
* if its contents cannot be converted, `NULL` is returned and a @ref
* GLFW_FORMAT_UNAVAILABLE error is generated.
*
* @param[in] window Deprecated. Any valid window or `NULL`.
* @return The contents of the clipboard as a UTF-8 encoded string, or `NULL`
* if an [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @remark @wayland Clipboard is currently unimplemented.
*
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
* should not free it yourself. It is valid until the next call to @ref
* glfwGetClipboardString or @ref glfwSetClipboardString, or until the library
* is terminated.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa @ref glfwSetClipboardString
*
* @since Added in version 3.0.
*
* @ingroup input
*/
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
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.
*

2
glfw/init.c vendored
View File

@@ -68,6 +68,8 @@ static void terminate(void)
int i;
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
_glfw_free_clipboard_data(&_glfw.clipboard);
_glfw_free_clipboard_data(&_glfw.primary);
while (_glfw.windowListHead)
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);

51
glfw/input.c vendored
View File

@@ -1526,36 +1526,41 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
return true;
}
GLFWAPI void glfwSetClipboardString(GLFWwindow* handle UNUSED, const char* string)
{
assert(string != NULL);
void _glfw_free_clipboard_data(_GLFWClipboardData *cd) {
if (cd->mime_types) {
for (size_t i = 0; i < cd->num_mime_types; i++) free((void*)cd->mime_types[i]);
free((void*)cd->mime_types);
}
memset(cd, 0, sizeof(cd[0]));
}
GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
_GLFW_REQUIRE_INIT();
_glfwPlatformSetClipboardString(string);
_glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object);
}
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 void glfwSetPrimarySelectionString(GLFWwindow* handle UNUSED, const char* string)
{
assert(string != NULL);
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);
_GLFW_REQUIRE_INIT();
_glfwPlatformSetPrimarySelectionString(string);
_GLFWClipboardData *cd = NULL;
switch(clipboard_type) {
case GLFW_CLIPBOARD: cd = &_glfw.clipboard; break;
case GLFW_PRIMARY_SELECTION: cd = &_glfw.primary; break;
}
_glfw_free_clipboard_data(cd);
cd->get_data = get_data;
cd->mime_types = calloc(num_mime_types, sizeof(char*));
cd->num_mime_types = 0;
cd->ctype = clipboard_type;
for (size_t i = 0; i < num_mime_types; i++) {
if (mime_types[i]) {
cd->mime_types[cd->num_mime_types++] = _glfw_strdup(mime_types[i]);
}
}
_glfwPlatformSetClipboard(clipboard_type);
}
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);

22
glfw/internal.h vendored
View File

@@ -34,6 +34,9 @@
#endif
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define MAX(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a > b ? a : b;})
#if defined(GLFW_INCLUDE_GLCOREARB) || \
defined(GLFW_INCLUDE_ES1) || \
@@ -561,6 +564,13 @@ struct _GLFWmutex
_GLFW_PLATFORM_MUTEX_STATE;
};
typedef struct _GLFWClipboardData {
const char** mime_types;
size_t num_mime_types;
GLFWclipboarditerfun get_data;
GLFWClipboardType ctype;
} _GLFWClipboardData;
// Library global data
//
struct _GLFWlibrary
@@ -575,6 +585,8 @@ struct _GLFWlibrary
int refreshRate;
} hints;
_GLFWClipboardData primary, clipboard;
_GLFWerror* errorListHead;
_GLFWcursor* cursorListHead;
_GLFWwindow* windowListHead;
@@ -674,12 +686,8 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
void _glfwPlatformSetClipboardString(const char* string);
const char* _glfwPlatformGetClipboardString(void);
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) || defined(__APPLE__)
void _glfwPlatformSetPrimarySelectionString(const char* string);
const char* _glfwPlatformGetPrimarySelectionString(void);
#endif
void _glfwPlatformSetClipboard(GLFWClipboardType t);
void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
bool _glfwPlatformInitJoysticks(void);
void _glfwPlatformTerminateJoysticks(void);
@@ -855,3 +863,5 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
char* _glfw_strdup(const char* source);
void _glfw_free_clipboard_data(_GLFWClipboardData *cd);

View File

@@ -77,7 +77,8 @@
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
"unstable/primary-selection/primary-selection-unstable-v1.xml",
"unstable/text-input/text-input-unstable-v3.xml"
"unstable/text-input/text-input-unstable-v3.xml",
"staging/xdg-activation/xdg-activation-v1.xml"
],
"sources": [
"wl_init.c",

2
glfw/window.c vendored
View File

@@ -71,7 +71,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
_glfw.focusedWindowId = window->id;
}
_GLFWwindow* _glfwFocusedWindow() {
_GLFWwindow* _glfwFocusedWindow(void) {
if (_glfw.focusedWindowId) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {

37
glfw/wl_init.c vendored
View File

@@ -99,7 +99,7 @@ static void pointerHandleEnter(void* data UNUSED,
return;
}
window->wl.decorations.focus = focus;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.pointerFocus = window;
window->wl.hovered = true;
@@ -144,8 +144,12 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
if (!image)
return;
if (image->width % scale || image->height % scale) {
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
static uint32_t warned_width = 0, warned_height = 0;
if (warned_width != image->width || warned_height != image->height) {
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
warned_width = image->width; warned_height = image->height;
}
}
buffer = wl_cursor_image_get_buffer(image);
@@ -307,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
if (window->wl.decorations.focus != CENTRAL_WINDOW)
return;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
* codes. */
@@ -459,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
return;
}
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.keyboardFocusId = window->id;
_glfwInputWindowFocus(window, true);
uint32_t* key;
@@ -512,7 +516,7 @@ static void keyboardHandleKey(void* data UNUSED,
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
@@ -535,7 +539,7 @@ static void keyboardHandleModifiers(void* data UNUSED,
uint32_t modsLocked,
uint32_t group)
{
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
}
@@ -715,6 +719,9 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwSetupWaylandPrimarySelectionDevice();
}
}
else if (strstr(interface, "xdg_activation_v1") != 0) {
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
}
}
@@ -878,6 +885,14 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
if (_glfw.wl.activation_requests.array) {
for (size_t i=0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->callback) r->callback(NULL, NULL, r->callback_data);
xdg_activation_token_v1_destroy(r->token);
}
free(_glfw.wl.activation_requests.array);
}
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -922,6 +937,8 @@ void _glfwPlatformTerminate(void)
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
if (_glfw.wl.dataOffers[doi].id) {
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
@@ -935,6 +952,8 @@ void _glfwPlatformTerminate(void)
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
if (_glfw.wl.primarySelectionDeviceManager)
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
if (_glfw.wl.xdg_activation_v1)
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)
@@ -943,10 +962,6 @@ void _glfwPlatformTerminate(void)
wl_display_disconnect(_glfw.wl.display);
}
finalizePollData(&_glfw.wl.eventLoopData);
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
}
const char* _glfwPlatformGetVersionString(void)

21
glfw/wl_platform.h vendored
View File

@@ -58,7 +58,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#include "wl_text_input.h"
#include "wayland-xdg-activation-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -124,6 +126,14 @@ typedef enum WaylandWindowState {
TOPLEVEL_STATE_TILED_BOTTOM = 128,
} WaylandWindowState;
typedef struct glfw_wl_xdg_activation_request {
GLFWid window_id;
GLFWactivationcallback callback;
void *callback_data;
uintptr_t request_id;
void *token;
} glfw_wl_xdg_activation_request;
static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM;
@@ -276,13 +286,14 @@ typedef struct _GLFWlibraryWayland
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice;
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
struct xdg_activation_v1* xdg_activation_v1;
int compositorVersion;
int seatVersion;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t serial;
uint32_t serial, input_serial;
int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay;
@@ -316,12 +327,14 @@ typedef struct _GLFWlibraryWayland
PFN_wl_egl_window_resize window_resize;
} egl;
struct {
glfw_wl_xdg_activation_request *array;
size_t capacity, sz;
} activation_requests;
EventLoopData eventLoopData;
char* pasteString;
char* clipboardString;
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data

503
glfw/wl_window.c vendored
View File

@@ -44,6 +44,59 @@
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
static void
activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, const char *token) {
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->request_id == (uintptr_t)data) {
_GLFWwindow *window = _glfwWindowForId(r->window_id);
if (r->callback) r->callback((GLFWwindow*)window, token, r->callback_data);
remove_i_from_array(_glfw.wl.activation_requests.array, i, _glfw.wl.activation_requests.sz);
break;
}
}
xdg_activation_token_v1_destroy(xdg_token);
}
static const struct
xdg_activation_token_v1_listener activation_token_listener = {
.done = &activation_token_done,
};
static bool
get_activation_token(
_GLFWwindow *window, uint32_t serial, GLFWactivationcallback cb, void *cb_data
) {
#define fail(msg) { _glfwInputError(GLFW_PLATFORM_ERROR, msg); if (cb) cb((GLFWwindow*)window, NULL, cb_data); return false; }
if (_glfw.wl.xdg_activation_v1 == NULL) fail("Wayland: activation requests not supported by this Wayland compositor");
struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(_glfw.wl.xdg_activation_v1);
if (token == NULL) fail("Wayland: failed to create activation request token");
if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) {
_glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2);
_glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity);
if (!_glfw.wl.activation_requests.array) {
_glfw.wl.activation_requests.capacity = 0;
fail("Wayland: Out of memory while allocation activation request");
}
}
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + _glfw.wl.activation_requests.sz++;
memset(r, 0, sizeof(*r));
static uintptr_t rq = 0;
r->window_id = window->id;
r->callback = cb; r->callback_data = cb_data;
r->request_id = ++rq; r->token = token;
if (serial != 0)
xdg_activation_token_v1_set_serial(token, serial, _glfw.wl.seat);
xdg_activation_token_v1_set_surface(token, window->wl.surface);
xdg_activation_token_v1_add_listener(token, &activation_token_listener, (void*)r->request_id);
xdg_activation_token_v1_commit(token);
return true;
#undef fail
}
static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data)
{
struct wl_shm_pool* pool;
@@ -199,7 +252,7 @@ static bool checkScaleChange(_GLFWwindow* window)
}
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
{
struct wl_region* region;
@@ -209,10 +262,22 @@ static void setOpaqueRegion(_GLFWwindow* window)
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region);
wl_surface_commit(window->wl.surface);
if (commit_surface) wl_surface_commit(window->wl.surface);
wl_region_destroy(region);
}
static void
swap_buffers(_GLFWwindow *window) {
// this will attach the buffer to the surface,
// the client is responsible for clearing the buffer to an appropriate blank
window->swaps_disallowed = false;
GLFWwindow *current = glfwGetCurrentContext();
bool context_is_current = ((_GLFWwindow*)current)->id == window->id;
if (!context_is_current) glfwMakeContextCurrent((GLFWwindow*)window);
window->context.swapBuffers(window);
if (!context_is_current) glfwMakeContextCurrent(current);
}
static void
resizeFramebuffer(_GLFWwindow* window) {
@@ -221,7 +286,7 @@ resizeFramebuffer(_GLFWwindow* window) {
int scaledHeight = window->wl.height * scale;
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent) setOpaqueRegion(window);
if (!window->wl.transparent) setOpaqueRegion(window, false);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
}
@@ -236,9 +301,9 @@ clipboard_mime(void) {
}
static bool
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height, bool *scale_changed) {
bool size_changed = width != window->wl.width || height != window->wl.height;
bool scale_changed = checkScaleChange(window);
*scale_changed = checkScaleChange(window);
if (size_changed) {
_glfwInputWindowSize(window, width, height);
@@ -246,7 +311,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
resizeFramebuffer(window);
}
if (scale_changed) {
if (*scale_changed) {
debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale);
if (!size_changed) resizeFramebuffer(window);
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
@@ -254,7 +319,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
_glfwInputWindowDamage(window);
return size_changed || scale_changed;
return size_changed || *scale_changed;
}
static void
@@ -368,9 +433,22 @@ static bool createSurface(_GLFWwindow* window,
wl_surface_set_user_data(window->wl.surface, window);
window->wl.native = wl_egl_window_create(window->wl.surface,
wndconfig->width,
wndconfig->height);
// If we already have been notified of the primary monitor scale, assume
// the window will be created on it and so avoid a rescale roundtrip in the common
// case of the window being shown on the primary monitor or all monitors having the same scale.
// If you change this also change get_window_content_scale() in the kitty code.
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
float xscale = 1.0, yscale = 1.0;
int scale = 1;
if (monitor) {
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
// see wl_monitor.c xscale is always == yscale
if (xscale <= 0.0001 || xscale != xscale || xscale >= 24) xscale = 1.0;
if (xscale > 1) scale = (int)xscale;
}
debug("Creating window at size: %dx%d and scale %d\n", wndconfig->width, wndconfig->height, scale);
window->wl.native = wl_egl_window_create(window->wl.surface, wndconfig->width * scale, wndconfig->height * scale);
if (!window->wl.native)
return false;
@@ -378,11 +456,13 @@ static bool createSurface(_GLFWwindow* window,
window->wl.height = wndconfig->height;
window->wl.user_requested_content_size.width = wndconfig->width;
window->wl.user_requested_content_size.height = wndconfig->height;
window->wl.scale = 1;
window->wl.scale = scale;
if (!window->wl.transparent)
setOpaqueRegion(window);
setOpaqueRegion(window, false);
wl_surface_set_buffer_scale(window->wl.surface, scale);
return true;
}
@@ -496,15 +576,7 @@ static void xdgSurfaceHandleConfigure(void* data,
int height = window->wl.pending.height;
if (!window->wl.surface_configured_once) {
window->wl.surface_configured_once = true;
// this will attach the buffer to the surface, the client is responsible for clearing the buffer to an appropriate blank
window->swaps_disallowed = false;
window->context.swapBuffers(window);
if (!width && !height && !new_states && !window->wl.decorations.serverSide && getenv("XAUTHORITY") && strstr(getenv("XAUTHORITY"), "mutter")) {
// https://github.com/kovidgoyal/kitty/issues/4802
debug("Ignoring first empty surface configure event on mutter.\n");
return;
}
swap_buffers(window);
}
if (new_states != window->wl.current.toplevel_states ||
@@ -528,10 +600,11 @@ static void xdgSurfaceHandleConfigure(void* data,
}
bool resized = false;
bool scale_changed = false;
if (window->wl.pending_state) {
int width = window->wl.pending.width, height = window->wl.pending.height;
set_csd_window_geometry(window, &width, &height);
resized = dispatchChangesAfterConfigure(window, width, height);
resized = dispatchChangesAfterConfigure(window, width, height, &scale_changed);
if (window->wl.decorations.serverSide) {
free_csd_surfaces(window);
} else {
@@ -542,6 +615,10 @@ static void xdgSurfaceHandleConfigure(void* data,
inform_compositor_of_window_geometry(window, "configure");
// we need to swap buffers here to ensure the buffer attached to the surface is a multiple
// of the new scale. See https://github.com/kovidgoyal/kitty/issues/5467
if (scale_changed) swap_buffers(window);
// if a resize happened there will be a commit at the next render frame so
// dont commit here, GNOME doesnt like it and its not really needed anyway
if (!resized) wl_surface_commit(window->wl.surface);
@@ -1073,34 +1150,45 @@ void _glfwPlatformHideWindow(_GLFWwindow* window)
window->wl.visible = false;
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
{
// TODO
static bool notified = false;
if (!notified) {
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window attention request not implemented yet");
notified = true;
static void
request_attention(GLFWwindow *window, const char *token, void *data UNUSED) {
if (window && token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
}
static bool
has_activation_in_flight(_GLFWwindow* window, GLFWactivationcallback callback) {
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->window_id == window->id && r->callback == callback) return true;
}
return false;
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) {
if (!has_activation_in_flight(window, request_attention)) get_activation_token(window, 0, request_attention, NULL);
}
int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
{
// TODO: Use an actual Wayland API to implement this when one becomes available
static char tty[L_ctermid + 1];
int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC);
if (fd > -1) {
int ret = write(fd, "\x07", 1) == 1 ? true : false;
close(fd);
return ret;
}
return false;
}
static void
focus_window(GLFWwindow *window, const char *token, void *data UNUSED) {
if (!window) return;
if (token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Window focus request via xdg-activation protocol was denied by the compositor. Use a better compositor.");
}
}
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the input focus");
// Attempt to focus the window by using the activation protocol, whether it works
// is entirely compositor dependent and as we all know Wayland and its ecosystem is
// the product of morons.
if (_glfw.wl.input_serial && !has_activation_in_flight(window, focus_window)) get_activation_token(window, _glfw.wl.input_serial, focus_window, NULL);
}
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
@@ -1447,53 +1535,68 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
}
}
static void send_text(char *text, int fd)
{
if (text) {
size_t len = strlen(text), pos = 0;
monotonic_t start = glfwGetTime();
while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
ssize_t ret = write(fd, text + pos, len - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
break;
}
if (ret > 0) {
start = glfwGetTime();
pos += ret;
}
static bool
write_all(int fd, const char *data, size_t sz) {
monotonic_t start = glfwGetTime();
size_t pos = 0;
while (pos < sz && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
ssize_t ret = write(fd, data + pos, sz - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
return false;
}
if (ret > 0) {
start = glfwGetTime();
pos += ret;
}
}
return pos >= sz;
}
static void
send_clipboard_data(const _GLFWClipboardData *cd, const char *mime, int fd) {
if (strcmp(mime, "text/plain;charset=utf-8") == 0 || strcmp(mime, "UTF8_STRING") == 0 || strcmp(mime, "TEXT") == 0 || strcmp(mime, "STRING") == 0) mime = "text/plain";
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
void *iter = chunk.iter;
if (!iter) return;
bool keep_going = true;
while (keep_going) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
if (!write_all(fd, chunk.data, chunk.sz)) keep_going = false;
if (chunk.free) chunk.free((void*)chunk.free_data);
}
cd->get_data(NULL, iter, cd->ctype);
}
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type, int fd) {
send_clipboard_data(&_glfw.clipboard, mime_type, fd);
close(fd);
}
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type UNUSED, int fd) {
send_text(_glfw.wl.clipboardString, fd);
}
static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source UNUSED,
const char *mime_type UNUSED, int fd) {
send_text(_glfw.wl.primarySelectionString, fd);
const char *mime_type, int fd) {
send_clipboard_data(&_glfw.primary, mime_type, fd);
close(fd);
}
static char* read_offer_string(int data_pipe, size_t *data_sz) {
static void
read_offer(int data_pipe, GLFWclipboardwritedatafun write_data, void *object) {
wl_display_flush(_glfw.wl.display);
size_t capacity = 0;
char *buf = NULL;
*data_sz = 0;
struct pollfd fds;
fds.fd = data_pipe;
fds.events = POLLIN;
monotonic_t start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(buf); buf = NULL; \
close(data_pipe); \
return NULL; \
return; \
}
char buf[8192];
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
int ret = poll(&fds, 1, 2000);
if (ret == -1) {
@@ -1503,34 +1606,65 @@ static char* read_offer_string(int data_pipe, size_t *data_sz) {
if (!ret) {
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
}
if (capacity <= *data_sz || capacity - *data_sz <= 64) {
capacity += 4096;
buf = realloc(buf, capacity);
if (!buf) {
bail("Wayland: Failed to allocate memory to read clipboard data");
}
}
ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1);
ret = read(data_pipe, buf, sizeof(buf));
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
}
if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
*data_sz += ret;
if (ret == 0) { close(data_pipe); return; }
if (!write_data(object, buf, ret)) bail("Wayland: call to write_data() failed with data from data offer");
start = glfwGetTime();
}
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
#undef bail
}
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
typedef struct chunked_writer {
char *buf; size_t sz, cap;
} chunked_writer;
static bool
write_chunk(void *object, const char *data, size_t sz) {
chunked_writer *cw = object;
if (cw->cap < cw->sz + sz) {
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
cw->buf = realloc(cw->buf, cw->cap);
}
memcpy(cw->buf + cw->sz, data, sz);
cw->sz += sz;
return true;
}
static char*
read_offer_string(int data_pipe, size_t *sz) {
chunked_writer cw = {0};
read_offer(data_pipe, write_chunk, &cw);
if (cw.buf) {
*sz = cw.sz;
return cw.buf;
}
*sz = 0;
return NULL;
}
static void
read_clipboard_data_offer(struct wl_data_offer *data_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
read_offer(pipefd[0], write_data, object);
}
static void
read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
close(pipefd[1]);
size_t sz = 0;
return read_offer_string(pipefd[0], &sz);
read_offer(pipefd[0], write_data, object);
}
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
@@ -1542,24 +1676,24 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime,
}
static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) {
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
if (_glfw.wl.dataSourceForClipboard == wl_data_source) {
_glfw.wl.dataSourceForClipboard = NULL;
_glfw_free_clipboard_data(&_glfw.clipboard);
}
wl_data_source_destroy(wl_data_source);
}
static void primary_selection_source_canceled(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source) {
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) {
_glfw.wl.dataSourceForPrimarySelection = NULL;
_glfw_free_clipboard_data(&_glfw.primary);
}
zwp_primary_selection_source_v1_destroy(primary_selection_source);
}
static void data_source_target(void *data UNUSED, struct wl_data_source *wl_data_source UNUSED, const char* mime UNUSED) {
}
static const struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText,
.cancelled = data_source_canceled,
.target = data_source_target,
};
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
@@ -1836,30 +1970,69 @@ static bool _glfwEnsureDataDevice(void) {
return true;
}
void _glfwPlatformSetClipboardString(const char* string)
{
if (!_glfwEnsureDataDevice()) return;
free(_glfw.wl.clipboardString);
_glfw.wl.clipboardString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.dataSourceForClipboard)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create data source");
return;
typedef void(*add_offer_func)(void*, const char *mime);
void
_glfwPlatformSetClipboard(GLFWClipboardType t) {
_GLFWClipboardData *cd = NULL;
void *data_source;
add_offer_func f;
if (t == GLFW_CLIPBOARD) {
if (!_glfwEnsureDataDevice()) return;
cd = &_glfw.clipboard;
f = (add_offer_func)wl_data_source_offer;
if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.dataSourceForClipboard)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create data source");
return;
}
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
data_source = _glfw.wl.dataSourceForClipboard;
} else {
if (!_glfw.wl.primarySelectionDevice) {
static bool warned_about_primary_selection_device = false;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = true;
}
return;
}
cd = &_glfw.primary;
f = (add_offer_func)zwp_primary_selection_source_v1_offer;
if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
data_source = _glfw.wl.dataSourceForPrimarySelection;
}
f(data_source, clipboard_mime());
for (size_t i = 0; i < cd->num_mime_types; i++) {
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
f(data_source, "TEXT");
f(data_source, "STRING");
f(data_source, "UTF8_STRING");
f(data_source, "text/plain;charset=utf-8");
}
f(data_source, cd->mime_types[i]);
}
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
if (t == GLFW_CLIPBOARD) {
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
} else {
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
}
static bool
@@ -1877,81 +2050,47 @@ plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) {
A("text/plain");
A("UTF8_STRING");
A("STRING");
A("TEXT");
#undef A
return NULL;
}
const char* _glfwPlatformGetClipboardString(void)
{
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
_GLFWWaylandOfferType offer_type = clipboard_type == GLFW_PRIMARY_SELECTION ? PRIMARY_SELECTION : CLIPBOARD;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (d->id && d->offer_type == CLIPBOARD) {
if (d->is_self_offer) return _glfw.wl.clipboardString;
const char *mime = plain_text_mime_for_offer(d);
if (mime) {
free(_glfw.wl.pasteString);
size_t sz = 0;
_glfw.wl.pasteString = read_data_offer(d->id, mime, &sz);
return _glfw.wl.pasteString;
if (d->id && d->offer_type == offer_type) {
if (d->is_self_offer) {
write_data(object, NULL, 1);
return;
}
}
}
return NULL;
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
if (!_glfw.wl.primarySelectionDevice) {
static bool warned_about_primary_selection_device = false;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = true;
}
return;
}
if (_glfw.wl.primarySelectionString == string) return;
free(_glfw.wl.primarySelectionString);
_glfw.wl.primarySelectionString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (d->id && d->is_primary && d->offer_type == PRIMARY_SELECTION) {
if (d->is_self_offer) return _glfw.wl.primarySelectionString;
const char *mime = plain_text_mime_for_offer(d);
if (mime) {
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_primary_selection_offer(d->id, mime);
return _glfw.wl.pasteString;
if (mime_type == NULL) {
bool ok = true;
for (size_t o = 0; o < d->mimes_count; o++) {
const char *q = d->mimes[o];
if (strchr(d->mimes[0], '/')) {
if (strcmp(q, clipboard_mime()) == 0) continue;
if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain";
} else {
if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain";
}
if (ok) ok = write_data(object, q, strlen(q));
}
return;
}
if (strcmp(mime_type, "text/plain") == 0) {
mime_type = plain_text_mime_for_offer(d);
if (!mime_type) return;
}
if (d->is_primary) {
read_primary_selection_offer(d->id, mime_type, write_data, object);
} else {
read_clipboard_data_offer(d->id, mime_type, write_data, object);
}
break;
}
}
return NULL;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
@@ -2073,6 +2212,18 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
return window->wl.surface;
}
GLFWAPI void glfwWaylandActivateWindow(GLFWwindow* handle, const char *activation_token) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT();
if (activation_token && activation_token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, activation_token, window->wl.surface);
}
GLFWAPI void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT();
get_activation_token(window, _glfw.wl.input_serial, cb, cb_data);
}
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}

10
glfw/x11_init.c vendored
View File

@@ -680,8 +680,14 @@ void _glfwPlatformTerminate(void)
glfw_xkb_release(&_glfw.x11.xkb);
glfw_dbus_terminate(&_glfw.x11.dbus);
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString);
if (_glfw.x11.mime_atoms.array) {
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
free((void*)_glfw.x11.mime_atoms.array[i].mime);
}
free(_glfw.x11.mime_atoms.array);
}
if (_glfw.x11.clipboard_atoms.array) { free(_glfw.x11.clipboard_atoms.array); }
if (_glfw.x11.primary_atoms.array) { free(_glfw.x11.primary_atoms.array); }
if (_glfw.x11.display)
{

16
glfw/x11_platform.h vendored
View File

@@ -207,6 +207,16 @@ typedef struct _GLFWwindowX11
} _GLFWwindowX11;
typedef struct MimeAtom {
Atom atom;
const char* mime;
} MimeAtom;
typedef struct AtomArray {
MimeAtom *array;
size_t sz, capacity;
} AtomArray;
// X11-specific global data
//
typedef struct _GLFWlibraryX11
@@ -225,10 +235,6 @@ typedef struct _GLFWlibraryX11
XContext context;
// Most recent error code received by X error handler
int errorCode;
// Primary selection string (while the primary selection is owned)
char* primarySelectionString;
// Clipboard string (while the selection is owned)
char* clipboardString;
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active
@@ -291,6 +297,8 @@ typedef struct _GLFWlibraryX11
// XRM database atom
Atom RESOURCE_MANAGER;
// Atoms for MIME types
AtomArray mime_atoms, clipboard_atoms, primary_atoms;
struct {
bool available;

345
glfw/x11_window.c vendored
View File

@@ -716,19 +716,60 @@ static bool createNativeWindow(_GLFWwindow* window,
return true;
}
static size_t
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data) {
*data = NULL;
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
char *buf = NULL;
size_t sz = 0, cap = 0;
void *iter = chunk.iter;
if (!iter) return 0;
while (true) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
if (cap < sz + chunk.sz) {
cap = MAX(cap * 2, sz + 4 * chunk.sz);
buf = realloc(buf, cap);
}
memcpy(buf + sz, chunk.data, chunk.sz);
sz += chunk.sz;
if (chunk.free) chunk.free((void*)chunk.free_data);
}
*data = buf;
cd->get_data(NULL, iter, cd->ctype);
return sz;
}
static void
get_atom_names(const Atom *atoms, int count, char **atom_names) {
_glfwGrabErrorHandlerX11();
XGetAtomNames(_glfw.x11.display, (Atom*)atoms, count, atom_names);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
for (int i = 0; i < count; i++) {
_glfwGrabErrorHandlerX11();
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
}
}
}
// Set the specified property to the selection converted to the requested target
//
static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
{
int i;
char* selectionString = NULL;
const Atom formats[] = { _glfw.x11.UTF8_STRING, XA_STRING };
const int formatCount = sizeof(formats) / sizeof(formats[0]);
const AtomArray *aa;
const _GLFWClipboardData *cd;
if (request->selection == _glfw.x11.PRIMARY)
selectionString = _glfw.x11.primarySelectionString;
else
selectionString = _glfw.x11.clipboardString;
if (request->selection == _glfw.x11.PRIMARY) {
aa = &_glfw.x11.primary_atoms;
cd = &_glfw.primary;
} else {
aa = &_glfw.x11.clipboard_atoms;
cd = &_glfw.clipboard;
}
if (request->property == None)
{
@@ -741,11 +782,10 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
{
// The list of supported targets was requested
const Atom targets[] = { _glfw.x11.TARGETS,
_glfw.x11.MULTIPLE,
_glfw.x11.UTF8_STRING,
XA_STRING };
Atom *targets = calloc(aa->sz + 2, sizeof(Atom));
targets[0] = _glfw.x11.TARGETS;
targets[1] = _glfw.x11.MULTIPLE;
for (size_t i = 0; i < aa->sz; i++) targets[i+2] = aa->array[i].atom;
XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
@@ -753,8 +793,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
32,
PropModeReplace,
(unsigned char*) targets,
sizeof(targets) / sizeof(targets[0]));
sizeof(targets[0]) * (aa->sz + 2));
free(targets);
return request->property;
}
@@ -763,7 +803,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
// Multiple conversions were requested
Atom* targets;
unsigned long i, count;
size_t i, j, count;
count = _glfwGetWindowPropertyX11(request->requestor,
request->property,
@@ -772,24 +812,25 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
for (i = 0; i < count; i += 2)
{
int j;
for (j = 0; j < formatCount; j++)
for (j = 0; j < aa->sz; j++)
{
if (targets[i] == formats[j])
if (targets[i] == aa->array[j].atom)
break;
}
if (j < formatCount)
if (j < aa->sz)
{
if (selectionString) XChangeProperty(_glfw.x11.display,
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[j].mime, &data);
if (data) XChangeProperty(_glfw.x11.display,
request->requestor,
targets[i + 1],
targets[i],
8,
PropModeReplace,
(unsigned char *) selectionString,
strlen(selectionString));
(unsigned char *) data,
sz);
free(data);
}
else
targets[i + 1] = None;
@@ -828,20 +869,22 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
// Conversion to a data target was requested
for (i = 0; i < formatCount; i++)
for (size_t i = 0; i < aa->sz; i++)
{
if (request->target == formats[i])
if (request->target == aa->array[i].atom)
{
// The requested target is one we support
if (selectionString) XChangeProperty(_glfw.x11.display,
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[i].mime, &data);
if (data) XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
request->target,
8,
PropModeReplace,
(unsigned char *) selectionString,
strlen(selectionString));
(unsigned char *) data,
sz);
free(data);
return request->property;
}
@@ -856,13 +899,11 @@ static void handleSelectionClear(XEvent* event)
{
if (event->xselectionclear.selection == _glfw.x11.PRIMARY)
{
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = NULL;
_glfw_free_clipboard_data(&_glfw.primary);
}
else
{
free(_glfw.x11.clipboardString);
_glfw.x11.clipboardString = NULL;
_glfw_free_clipboard_data(&_glfw.clipboard);
}
}
@@ -881,34 +922,21 @@ 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, Atom *targets, size_t num_targets, GLFWclipboardwritedatafun write_data, void *object, bool report_not_found)
{
char** selectionString = NULL;
const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING };
const size_t targetCount = sizeof(targets) / sizeof(targets[0]);
if (selection == _glfw.x11.PRIMARY)
selectionString = &_glfw.x11.primarySelectionString;
else
selectionString = &_glfw.x11.clipboardString;
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
return *selectionString;
#define XFREE(x) { if (x) XFree(x); x = NULL; }
if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) {
write_data(object, NULL, 1);
return;
}
free(*selectionString);
*selectionString = NULL;
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;
@@ -925,8 +953,7 @@ static const char* getSelectionString(Atom selection)
&notification))
{
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));
}
@@ -953,9 +980,6 @@ static const char* getSelectionString(Atom selection)
if (actualType == _glfw.x11.INCR)
{
size_t size = 1;
char* string = NULL;
for (;;)
{
start = glfwGetTime();
@@ -966,13 +990,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,
@@ -988,47 +1011,41 @@ 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;
}
else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) {
found = true;
write_data(object, data, itemCount);
}
XFree(data);
XFREE(data);
if (*selectionString)
break;
}
if (!*selectionString)
if (!found && report_not_found)
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"X11: Failed to convert selection to string");
"X11: Failed to convert selection to data from clipboard");
}
return *selectionString;
#undef XFREE
}
// Make the specified window and its video mode active on its monitor
@@ -1098,21 +1115,6 @@ static void onConfigChange(void)
}
}
static void
get_atom_names(Atom *atoms, int count, char **atom_names) {
_glfwGrabErrorHandlerX11();
XGetAtomNames(_glfw.x11.display, atoms, count, atom_names);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
for (int i = 0; i < count; i++) {
_glfwGrabErrorHandlerX11();
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
}
}
}
// Process the specified X event
//
static void processEvent(XEvent *event)
@@ -2849,51 +2851,106 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
}
}
void _glfwPlatformSetClipboardString(const char* string)
{
char* copy = _glfw_strdup(string);
free(_glfw.x11.clipboardString);
_glfw.x11.clipboardString = copy;
static MimeAtom atom_for_mime(const char *mime) {
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
MimeAtom ma = _glfw.x11.mime_atoms.array[i];
if (strcmp(ma.mime, mime) == 0) {
return ma;
}
}
MimeAtom ma = {.mime=_glfw_strdup(mime), .atom=XInternAtom(_glfw.x11.display, mime, 0)};
if (_glfw.x11.mime_atoms.capacity < _glfw.x11.mime_atoms.sz + 1) {
_glfw.x11.mime_atoms.capacity += 32;
_glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity);
}
_glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma;
return ma;
}
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.CLIPBOARD,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of clipboard selection");
void _glfwPlatformSetClipboard(GLFWClipboardType t) {
Atom which = None;
_GLFWClipboardData *cd = NULL;
AtomArray *aa = NULL;
switch (t) {
case GLFW_CLIPBOARD: which = _glfw.x11.CLIPBOARD; cd = &_glfw.clipboard; aa = &_glfw.x11.clipboard_atoms; break;
case GLFW_PRIMARY_SELECTION: which = _glfw.x11.PRIMARY; cd = &_glfw.primary; aa = &_glfw.x11.primary_atoms; break;
}
XSetSelectionOwner(_glfw.x11.display, which, _glfw.x11.helperWindowHandle, CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, which) != _glfw.x11.helperWindowHandle) {
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of clipboard selection");
}
if (aa->capacity < cd->num_mime_types + 32) {
aa->capacity = cd->num_mime_types + 32;
aa->array = malloc(sizeof(aa->array[0]) * aa->capacity);
}
aa->sz = 0;
for (size_t i = 0; i < cd->num_mime_types; i++) {
MimeAtom *a = aa->array + aa->sz++;
*a = atom_for_mime(cd->mime_types[i]);
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
a = aa->array + aa->sz++;
a->atom = _glfw.x11.UTF8_STRING;
a->mime = "text/plain";
}
}
}
const char* _glfwPlatformGetClipboardString(void)
{
return getSelectionString(_glfw.x11.CLIPBOARD);
typedef struct chunked_writer {
char *buf; size_t sz, cap;
} chunked_writer;
static bool
write_chunk(void *object, const char *data, size_t sz) {
chunked_writer *cw = object;
if (cw->cap < cw->sz + sz) {
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
cw->buf = realloc(cw->buf, cw->cap);
}
memcpy(cw->buf + cw->sz, data, sz);
cw->sz += sz;
return true;
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of primary selection");
static void
get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) {
chunked_writer cw = {0};
getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false);
size_t count = 0;
bool ok = true;
if (cw.buf) {
Atom *atoms = (Atom*)cw.buf;
count = cw.sz / sizeof(Atom);
char **names = calloc(count, sizeof(char*));
get_atom_names(atoms, count, names);
for (size_t i = 0; i < count; i++) {
if (strchr(names[i], '/')) {
if (ok) ok = write_data(object, names[i], strlen(names[i]));
} else {
if (atoms[i] == _glfw.x11.UTF8_STRING || atoms[i] == XA_STRING) {
if (ok) ok = write_data(object, "text/plain", strlen("text/plain"));
}
}
XFree(names[i]);
}
free(cw.buf);
free(names);
}
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
return getSelectionString(_glfw.x11.PRIMARY);
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
Atom atoms[4], which = clipboard_type == GLFW_PRIMARY_SELECTION ? _glfw.x11.PRIMARY : _glfw.x11.CLIPBOARD;
if (mime_type == NULL) {
get_available_mime_types(which, write_data, object);
return;
}
size_t count = 0;
atoms[count++] = atom_for_mime(mime_type).atom;
if (strcmp(mime_type, "text/plain") == 0) {
atoms[count++] = _glfw.x11.UTF8_STRING;
atoms[count++] = XA_STRING;
}
getSelectionString(which, atoms, count, write_data, object, true);
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)

View File

@@ -2,7 +2,6 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from contextlib import suppress
from fnmatch import fnmatch
from functools import lru_cache
@@ -12,6 +11,7 @@ from typing import (
)
from kitty.guess_mime_type import guess_type
from kitty.utils import control_codes_pat
if TYPE_CHECKING:
from .highlight import DiffHighlight # noqa
@@ -161,12 +161,9 @@ def collect_files(collection: Collection, left: str, right: str) -> None:
collection.add_add(right_path_map[name])
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
def sanitize(text: str) -> str:
ntext = text.replace('\r\n', '\n')
return sanitize_pat.sub('', ntext)
return control_codes_pat().sub('', ntext)
@lru_cache(maxsize=1024)

View File

@@ -15,13 +15,14 @@ from typing import (
from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions
from kitty.clipboard import set_clipboard_string, set_primary_selection
from kitty.constants import website_url
from kitty.fast_data_types import get_options, set_clipboard_string, wcswidth
from kitty.fast_data_types import get_options, wcswidth
from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import (
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
screen_size_function, set_primary_selection
screen_size_function
)
from ..tui.handler import Handler, result_handler

View File

@@ -172,7 +172,8 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
if ssh_opts.remote_kitty != 'no':
env['KITTY_REMOTE'] = ssh_opts.remote_kitty
if os.environ.get('KITTY_PUBLIC_KEY'):
env['KITTY_PUBLIC_KEY'] = os.environ['KITTY_PUBLIC_KEY']
env.pop('KITTY_PUBLIC_KEY', None)
literal_env['KITTY_PUBLIC_KEY'] = os.environ['KITTY_PUBLIC_KEY']
env_script = serialize_env(literal_env, env, base_env, for_python=compression != 'gz')
buf = io.BytesIO()
with tarfile.open(mode=f'w:{compression}', fileobj=buf, encoding='utf-8') as tf:

View File

@@ -20,6 +20,10 @@ from weakref import WeakValueDictionary
from .child import cached_process_data, default_env, set_default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .clipboard import (
Clipboard, get_clipboard_string, get_primary_selection,
set_clipboard_string, set_primary_selection
)
from .conf.utils import BadLine, KeyAction, to_cmdline
from .config import common_opts_as_dict, prepare_config_file_for_editing
from .constants import (
@@ -29,19 +33,19 @@ from .constants import (
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, GLFW_PRIMARY_SELECTION,
IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color,
EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update,
background_opacity_of, change_background_opacity, change_os_window_state,
cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_os_window, destroy_global_data,
focus_os_window, get_boss, get_clipboard_string, get_options,
get_os_window_size, global_font_size, mark_os_window_for_close,
os_window_font_size, patch_global_colors, redirect_mouse_handling,
ring_bell, safe_pipe, send_data_to_peer, set_application_quit_request,
set_background_image, set_boss, set_clipboard_string, set_in_sequence_mode,
set_options, set_os_window_size, set_os_window_title, thread_write,
toggle_fullscreen, toggle_maximized, toggle_secure_input
focus_os_window, get_boss, get_options, get_os_window_size,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, redirect_mouse_handling, ring_bell,
run_with_activation_token, safe_pipe, send_data_to_peer,
set_application_quit_request, set_background_image, set_boss,
set_in_sequence_mode, set_options, set_os_window_size, set_os_window_title,
thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
@@ -60,10 +64,9 @@ from .types import _T, AsyncResponse, WindowSystemMouseEvent, ac
from .typing import PopenType, TypedDict
from .utils import (
cleanup_ssh_control_masters, func_name, get_editor, get_new_os_window_size,
get_primary_selection, is_path_in_temp_dir, less_version, log_error,
macos_version, open_url, parse_address_spec, parse_uri_list,
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
single_instance, startup_notification_handler, which
is_path_in_temp_dir, less_version, log_error, macos_version, open_url,
parse_address_spec, parse_uri_list, platform_window_id, remove_socket_file,
safe_print, single_instance, startup_notification_handler, which
)
from .window import CommandOutput, CwdRequest, Window
@@ -239,6 +242,8 @@ class Boss:
prewarm: PrewarmProcess,
):
set_layout_options(opts)
self.clipboard = Clipboard()
self.primary_selection = Clipboard(GLFW_PRIMARY_SELECTION)
self.update_check_started = False
self.encryption_key = EllipticCurveKey()
self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}'
@@ -294,14 +299,22 @@ class Boss:
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
focused_os_window = wid = 0
token = os.environ.pop('XDG_ACTIVATION_TOKEN', '')
for startup_session in si:
self.add_os_window(startup_session, os_window_id=os_window_id)
wid = self.add_os_window(startup_session, os_window_id=os_window_id)
if startup_session.focus_os_window:
focused_os_window = wid
os_window_id = None
if self.args.start_as != 'normal':
if self.args.start_as == 'fullscreen':
self.toggle_fullscreen()
else:
change_os_window_state(self.args.start_as)
if focused_os_window > 0:
focus_os_window(focused_os_window, True, token)
elif token and is_wayland() and wid:
focus_os_window(wid, True, token)
def add_os_window(
self,
@@ -623,6 +636,7 @@ class Boss:
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
from .cli_stub import CLIOptions
startup_id = data.get('startup_id')
activation_token = data.get('activation_token', '')
args, rest = parse_args(data['args'][1:], result_class=CLIOptions)
cmdline_args_for_open = data.get('cmdline_args_for_open')
if cmdline_args_for_open:
@@ -635,14 +649,21 @@ class Boss:
args.session = PreReadSession(data['stdin'])
if not os.path.isabs(args.directory):
args.directory = os.path.join(data['cwd'], args.directory)
focused_os_window = os_window_id = 0
for session in create_sessions(opts, args, respect_cwd=True):
os_window_id = self.add_os_window(
session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id,
override_title=args.title or None)
if session.focus_os_window:
focused_os_window = os_window_id
if opts.background_opacity != get_options().background_opacity:
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
if data.get('notify_on_os_window_death'):
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
if focused_os_window > 0:
focus_os_window(focused_os_window, True, activation_token or '')
elif activation_token and is_wayland() and os_window_id:
focus_os_window(os_window_id, True, activation_token or '')
else:
log_error('Unknown message received from peer, ignoring')
return None
@@ -766,11 +787,19 @@ class Boss:
confirm_on_cancel: bool = False, # on closing window
confirm_on_accept: bool = True, # on pressing enter
) -> None:
result: bool = False
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') == 'y', *args)
nonlocal result
result = res.get('response') == 'y'
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result, *args)
self.run_kitten_with_metadata(
'ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
window=window, custom_callback=callback_, default_data={'response': 'y' if confirm_on_cancel else 'n'})
window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
default_data={'response': 'y' if confirm_on_cancel else 'n'})
def choose(
self, msg: str, # can contain newlines and ANSI formatting
@@ -782,8 +811,12 @@ class Boss:
hidden_text_placeholder: str = 'HIDDEN_TEXT_PLACEHOLDER', # placeholder text to insert in to message
unhide_key: str = 'u', # key to press to unhide hidden text
) -> Optional[Window]:
result: str = ''
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') or '')
nonlocal result
result = res.get('response') or ''
if hidden_text:
msg = msg.replace(hidden_text, hidden_text_placeholder)
cmd = ['--type=choices', '--message', msg]
@@ -796,7 +829,14 @@ class Boss:
input_data = hidden_text
else:
input_data = None
ans = self.run_kitten_with_metadata('ask', cmd, window=window, custom_callback=callback_, input_data=input_data, default_data={'response': ''})
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result)
ans = self.run_kitten_with_metadata(
'ask', cmd, window=window, custom_callback=callback_, input_data=input_data, default_data={'response': ''},
action_on_removal=on_popup_overlay_removal
)
if isinstance(ans, Window):
return ans
return None
@@ -808,10 +848,19 @@ class Boss:
prompt: str = '> ',
is_password: bool = False
) -> None:
result: str = ''
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') or '')
nonlocal result
result = res.get('response') or ''
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result)
cmd = ['--type', 'password' if is_password else 'line', '--message', msg, '--prompt', prompt]
self.run_kitten_with_metadata('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''})
self.run_kitten_with_metadata(
'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal
)
def confirm_tab_close(self, tab: Tab) -> None:
x = get_options().confirm_os_window_close
@@ -1473,9 +1522,7 @@ class Boss:
@ac('misc', 'Edit the kitty.conf config file in your favorite text editor')
def edit_config_file(self, *a: Any) -> None:
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
cmd = [kitty_exe(), '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor(get_options()) + [confpath]
cmd = [kitty_exe(), '+edit'] + get_editor(get_options()) + [confpath]
self.new_os_window(*cmd)
def run_kitten_with_metadata(
@@ -1705,7 +1752,16 @@ class Boss:
extra_env = {}
if self.listening_on:
extra_env['KITTY_LISTEN_ON'] = self.listening_on
open_url(url, program or get_options().open_url_with, cwd=cwd, extra_env=extra_env)
def doit(activation_token: str = '') -> None:
if activation_token:
extra_env['XDG_ACTIVATION_TOKEN'] = activation_token
open_url(url, program or get_options().open_url_with, cwd=cwd, extra_env=extra_env)
if is_wayland():
run_with_activation_token(doit)
else:
doit()
@ac('misc', 'Click a URL using the keyboard')
def open_url_with_hints(self) -> None:
@@ -1899,18 +1955,28 @@ class Boss:
with suppress(Exception):
cwd = cwd_from.cwd_of_child
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals)
except Exception:
os.close(w)
def doit(activation_token: str = '') -> None:
nonlocal env
if activation_token:
if env is None:
env = default_env().copy()
env['XDG_ACTIVATION_TOKEN'] = activation_token
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals)
except Exception:
os.close(w)
else:
thread_write(w, stdin)
finally:
os.close(r)
else:
thread_write(w, stdin)
finally:
os.close(r)
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals)
if is_wayland():
run_with_activation_token(doit)
else:
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals)
doit()
def pipe(self, source: str, dest: str, exe: str, *args: str) -> Optional[Window]:
cmd = [exe] + list(args)

View File

@@ -768,7 +768,7 @@ render(monotonic_t now, bool input_read) {
if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, ms_to_monotonic_t(250ll))) request_frame_render(w);
// dont respect render frames soon after a resize on Wayland as they cause flicker because
// we want to fill the newly resized buffer ASAP, not at compositors convenience
if (global_state.is_wayland && (monotonic() - w->viewport_resized_at) > s_double_to_monotonic_t(1)) continue;
if (!global_state.is_wayland || (monotonic() - w->viewport_resized_at) > s_double_to_monotonic_t(1)) continue;
}
w->render_calls++;
make_os_window_context_current(w);

View File

@@ -100,11 +100,14 @@ spawn(PyObject *self UNUSED, PyObject *args) {
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Child();
#endif
// See _Py_RestoreSignals in signalmodule.c for a list of signals python nukes
const struct sigaction act = {.sa_handler=SIG_DFL};
#define SA(which) if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed");
for (int si = 0; si < num_handled_signals; si++) { SA(handled_signals[si]); }
// See _Py_RestoreSignals in signalmodule.c for a list of signals python nukes
#ifdef SIGPIPE
SA(SIGPIPE)
#endif
#ifdef SIGXFSZ
SA(SIGXFSZ);
#endif

View File

@@ -148,6 +148,7 @@ def process_env() -> Dict[str, str]:
ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None)
if ssl_env_var is not None:
ans.pop(ssl_env_var, None)
ans.pop('XDG_ACTIVATION_TOKEN', None)
return ans

121
kitty/clipboard.py Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import io
from typing import IO, Callable, Dict, List, Tuple, Union
from .conf.utils import uniq
from .constants import supports_primary_selection
from .fast_data_types import (
GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION, get_boss, get_clipboard_mime,
set_clipboard_data_types
)
DataType = Union[bytes, 'IO[bytes]']
class Clipboard:
def __init__(self, clipboard_type: int = GLFW_CLIPBOARD) -> None:
self.data: Dict[str, DataType] = {}
self.clipboard_type = clipboard_type
self.enabled = self.clipboard_type == GLFW_CLIPBOARD or supports_primary_selection
def set_text(self, x: Union[str, bytes]) -> None:
if isinstance(x, str):
x = x.encode('utf-8')
self.set_mime({'text/plain': x})
def set_mime(self, data: Dict[str, DataType]) -> None:
if self.enabled and isinstance(data, dict):
self.data = data
set_clipboard_data_types(self.clipboard_type, tuple(self.data))
def get_text(self) -> str:
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:
if self.enabled:
try:
get_clipboard_mime(self.clipboard_type, mime, output)
except RuntimeError as err:
if str(err) != 'is_self_offer':
raise
data = self.data.get(mime, b'')
if isinstance(data, bytes):
output(data)
else:
data.seek(0, 0)
q = b' '
while q:
q = data.read(io.DEFAULT_BUFFER_SIZE)
output(q)
def get_mime_data(self, mime: str) -> bytes:
ans: List[bytes] = []
self.get_mime(mime, ans.append)
return b''.join(ans)
def get_available_mime_types_for_paste(self) -> Tuple[str, ...]:
if self.enabled:
parts: List[bytes] = []
try:
get_clipboard_mime(self.clipboard_type, None, parts.append)
except RuntimeError as err:
if str(err) != 'is_self_offer':
raise
return tuple(self.data)
return tuple(x.decode('utf-8', 'replace') for x in uniq(parts))
return ()
def __call__(self, mime: str) -> Callable[[], bytes]:
data = self.data.get(mime, b'')
if isinstance(data, str): # type: ignore
data = data.encode('utf-8') # type: ignore
if isinstance(data, bytes):
def chunker() -> bytes:
nonlocal data
assert isinstance(data, bytes)
ans = data
data = b''
return ans
return chunker
data.seek(0, 0)
def io_chunker() -> bytes:
assert not isinstance(data, bytes)
return data.read(io.DEFAULT_BUFFER_SIZE)
return io_chunker
def set_clipboard_string(x: Union[str, bytes]) -> None:
get_boss().clipboard.set_text(x)
def get_clipboard_string() -> str:
return get_boss().clipboard.get_text()
def set_primary_selection(x: Union[str, bytes]) -> None:
get_boss().primary_selection.set_text(x)
def get_primary_selection() -> str:
return get_boss().primary_selection.get_text()
def develop() -> Tuple[Clipboard, Clipboard]:
from .constants import is_macos, detect_if_wayland_ok
from .fast_data_types import set_boss
from .main import init_glfw_module
glfw_module = 'cocoa' if is_macos else ('wayland' if detect_if_wayland_ok() else 'x11')
class Boss:
clipboard = Clipboard()
primary_selection = Clipboard(GLFW_PRIMARY_SELECTION)
init_glfw_module(glfw_module)
set_boss(Boss()) # type: ignore
return Boss.clipboard, Boss.primary_selection

View File

@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 26, 2)
version: Version = Version(0, 26, 3)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -81,6 +81,25 @@ def launch(args: List[str]) -> None:
runpy.run_path(exe, run_name='__main__')
def edit(args: List[str]) -> None:
import shutil
from .constants import is_macos
if is_macos:
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a small delay.
import time
time.sleep(0.05)
exe = args[1]
if not os.path.isabs(exe):
exe = shutil.which(exe) or ''
if not exe or not os.access(exe, os.X_OK):
print('Cannot find an editor on your system. Set the \x1b[33meditor\x1b[39m value in kitty.conf'
' to the absolute path of your editor of choice.', file=sys.stderr)
from kitty.utils import hold_till_enter
hold_till_enter()
raise SystemExit(1)
os.execv(exe, args[1:])
def shebang(args: List[str]) -> None:
script_path = args[1]
cmd = args[2:]
@@ -152,6 +171,7 @@ namespaced_entry_points['open'] = open_urls
namespaced_entry_points['kitten'] = run_kitten
namespaced_entry_points['edit-config'] = edit_config_file
namespaced_entry_points['shebang'] = shebang
namespaced_entry_points['edit'] = edit
def setup_openssl_environment(ext_dir: str) -> None:

View File

@@ -1,7 +1,7 @@
import termios
from ctypes import Array, c_ubyte
from typing import (
Any, AnyStr, Callable, Dict, List, NewType, Optional, Tuple, TypedDict,
Any, Callable, Dict, List, NewType, Optional, Tuple, TypedDict,
Union, Iterator
)
@@ -13,6 +13,8 @@ from kitty.options.types import Options
from kitty.types import SignalInfo
# Constants {{{
GLFW_PRIMARY_SELECTION: int
GLFW_CLIPBOARD: int
CLD_KILLED: int
CLD_STOPPED: int
CLD_CONTINUED: int
@@ -304,14 +306,6 @@ def log_error_string(s: str) -> None:
pass
def set_primary_selection(x: Union[bytes, str]) -> None:
pass
def get_primary_selection() -> Optional[bytes]:
pass
def redirect_std_streams(devnull: str) -> None:
pass
@@ -597,10 +591,6 @@ def set_in_sequence_mode(yes: bool) -> None:
pass
def set_clipboard_string(data: AnyStr) -> None:
pass
def set_background_image(
path: Optional[str],
os_window_ids: Tuple[int, ...],
@@ -764,11 +754,7 @@ def global_font_size(val: float = -1.) -> float:
pass
def get_clipboard_string() -> str:
pass
def focus_os_window(os_window_id: int, also_raise: bool = True) -> bool:
def focus_os_window(os_window_id: int, also_raise: bool = True, activation_token: Optional[str] = None) -> bool:
pass
@@ -1486,3 +1472,6 @@ class SingleKey:
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: ...
def run_with_activation_token(func: Callable[[str], None]) -> None: ...

14
kitty/glfw-wrapper.c generated
View File

@@ -365,11 +365,11 @@ load_glfw(const char* path) {
*(void **) (&glfwGetGamepadState_impl) = dlsym(handle, "glfwGetGamepadState");
if (glfwGetGamepadState_impl == NULL) fail("Failed to load glfw function glfwGetGamepadState with error: %s", dlerror());
*(void **) (&glfwSetClipboardString_impl) = dlsym(handle, "glfwSetClipboardString");
if (glfwSetClipboardString_impl == NULL) fail("Failed to load glfw function glfwSetClipboardString with error: %s", dlerror());
*(void **) (&glfwSetClipboardDataTypes_impl) = dlsym(handle, "glfwSetClipboardDataTypes");
if (glfwSetClipboardDataTypes_impl == NULL) fail("Failed to load glfw function glfwSetClipboardDataTypes with error: %s", dlerror());
*(void **) (&glfwGetClipboardString_impl) = dlsym(handle, "glfwGetClipboardString");
if (glfwGetClipboardString_impl == NULL) fail("Failed to load glfw function glfwGetClipboardString 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());
@@ -449,6 +449,12 @@ load_glfw(const char* path) {
*(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent");
if (glfwRequestWaylandFrameEvent_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwWaylandActivateWindow_impl) = dlsym(handle, "glfwWaylandActivateWindow");
if (glfwWaylandActivateWindow_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwWaylandRunWithActivationToken_impl) = dlsym(handle, "glfwWaylandRunWithActivationToken");
if (glfwWaylandRunWithActivationToken_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwWaylandSetTitlebarColor_impl) = dlsym(handle, "glfwWaylandSetTitlebarColor");
if (glfwWaylandSetTitlebarColor_impl == NULL) dlerror(); // clear error indicator

33
kitty/glfw-wrapper.h generated
View File

@@ -1454,8 +1454,21 @@ typedef void (* GLFWjoystickfun)(int,int);
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
typedef void (* GLFWtickcallback)(void*);
typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data);
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
typedef char* (* GLFWcurrentselectionfun)(void);
typedef void (* GLFWclipboarddatafreefun)(void* data);
typedef struct GLFWDataChunk {
const char *data;
size_t sz;
GLFWclipboarddatafreefun free;
void *iter, *free_data;
} GLFWDataChunk;
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.
*
@@ -2081,13 +2094,13 @@ typedef int (*glfwGetGamepadState_func)(int, GLFWgamepadstate*);
GFW_EXTERN glfwGetGamepadState_func glfwGetGamepadState_impl;
#define glfwGetGamepadState glfwGetGamepadState_impl
typedef void (*glfwSetClipboardString_func)(GLFWwindow*, const char*);
GFW_EXTERN glfwSetClipboardString_func glfwSetClipboardString_impl;
#define glfwSetClipboardString glfwSetClipboardString_impl
typedef void (*glfwSetClipboardDataTypes_func)(GLFWClipboardType, const char* const*, size_t, GLFWclipboarditerfun);
GFW_EXTERN glfwSetClipboardDataTypes_func glfwSetClipboardDataTypes_impl;
#define glfwSetClipboardDataTypes glfwSetClipboardDataTypes_impl
typedef const char* (*glfwGetClipboardString_func)(GLFWwindow*);
GFW_EXTERN glfwGetClipboardString_func glfwGetClipboardString_impl;
#define glfwGetClipboardString glfwGetClipboardString_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;
@@ -2193,6 +2206,14 @@ typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long lon
GFW_EXTERN glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;
#define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl
typedef void (*glfwWaylandActivateWindow_func)(GLFWwindow*, const char*);
GFW_EXTERN glfwWaylandActivateWindow_func glfwWaylandActivateWindow_impl;
#define glfwWaylandActivateWindow glfwWaylandActivateWindow_impl
typedef void (*glfwWaylandRunWithActivationToken_func)(GLFWwindow*, GLFWactivationcallback, void*);
GFW_EXTERN glfwWaylandRunWithActivationToken_func glfwWaylandRunWithActivationToken_impl;
#define glfwWaylandRunWithActivationToken glfwWaylandRunWithActivationToken_impl
typedef bool (*glfwWaylandSetTitlebarColor_func)(GLFWwindow*, uint32_t, bool);
GFW_EXTERN glfwWaylandSetTitlebarColor_func glfwWaylandSetTitlebarColor_impl;
#define glfwWaylandSetTitlebarColor glfwWaylandSetTitlebarColor_impl

View File

@@ -312,6 +312,14 @@ dpi_change_callback(GLFWwindow *w, float x_scale UNUSED, float y_scale UNUSED) {
window->live_resize.last_resize_event_at = monotonic();
global_state.callback_os_window = NULL;
request_tick_callback();
if (global_state.is_wayland) {
// because of the stupidity of Wayland design, the GLFW wayland backend
// will need to swap buffers immediately after a scale change to ensure
// the buffer size is a multiple of the scale. So blank the new buffer to
// ensure we dont leak any unintialized pixels to the screen. The OpenGL viewport
// will already have been resized to its new size in framebuffer_size_callback
blank_os_window(window);
}
}
static void
@@ -633,6 +641,7 @@ set_os_window_size(OSWindow *os_window, int x, int y) {
static void
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
// if you change this function also change createSurface() in wl_window.c
*xscale = 1; *yscale = 1;
if (w) glfwGetWindowContentScale(w, xscale, yscale);
else {
@@ -833,6 +842,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
if (!global_state.is_wayland) {
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
// which won't happen until the event loop ticks, so using a temp window is useless.
// The glfw backend assigns new windows a scale matching the primary monitor or 1 if
// no monitor has yet been reported. This is mimiced by get_window_content_scale() when
// using a null window, so we will match. Lets hope neither of these two change their algorithms
// in the future.
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
}
@@ -856,7 +869,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
// changing, in case the background color is not black
blank_canvas(is_semi_transparent ? OPT(background_opacity) : 1.0f, OPT(background));
#ifndef __APPLE__
if (is_first_window) glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
#endif
// On Wayland the initial swap is allowed only after the first XDG configure event
if (glfwAreSwapsAllowed(glfw_window)) glfwSwapBuffers(glfw_window);
@@ -1024,13 +1037,17 @@ destroy_os_window(OSWindow *w) {
}
void
focus_os_window(OSWindow *w, bool also_raise) {
focus_os_window(OSWindow *w, bool also_raise, const char *activation_token) {
if (w->handle) {
#ifdef __APPLE__
if (!also_raise) cocoa_focus_window(glfwGetCocoaWindow(w->handle));
else glfwFocusWindow(w->handle);
(void)activation_token;
#else
(void)also_raise;
if (global_state.is_wayland && activation_token && activation_token[0] && also_raise) {
glfwWaylandActivateWindow(w->handle, activation_token);
return;
}
glfwFocusWindow(w->handle);
#endif
}
@@ -1263,13 +1280,6 @@ toggle_secure_input(PYNOARG) {
Py_RETURN_NONE;
}
static PyObject*
get_clipboard_string(PYNOARG) {
OSWindow *w = current_os_window();
if (w) return Py_BuildValue("s", glfwGetClipboardString(w->handle));
return Py_BuildValue("s", "");
}
static void
ring_audio_bell(void) {
static monotonic_t last_bell_at = -1;
@@ -1298,16 +1308,6 @@ get_content_scale_for_window(PYNOARG) {
return Py_BuildValue("ff", xscale, yscale);
}
static PyObject*
set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
char *title;
Py_ssize_t sz;
if(!PyArg_ParseTuple(args, "s#", &title, &sz)) return NULL;
OSWindow *w = current_os_window();
if (w) glfwSetClipboardString(w->handle, title);
Py_RETURN_NONE;
}
static OSWindow*
find_os_window(id_type os_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
@@ -1317,6 +1317,26 @@ find_os_window(id_type os_window_id) {
return NULL;
}
static void
activation_token_callback(GLFWwindow *window UNUSED, const char *token, void *data) {
if (!token || !token[0]) {
token = "";
log_error("Wayland: Did not get activation token from compositor. Use a better compositor.");
}
PyObject *ret = PyObject_CallFunction(data, "s", token);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
Py_CLEAR(data);
}
void
run_with_activation_token_in_os_window(OSWindow *w, PyObject *callback) {
if (global_state.is_wayland) {
Py_INCREF(callback);
glfwWaylandRunWithActivationToken(w->handle, activation_token_callback, callback);
}
}
static PyObject*
toggle_fullscreen(PyObject UNUSED *self, PyObject *args) {
id_type os_window_id = 0;
@@ -1390,10 +1410,10 @@ wakeup_main_loop() {
bool
should_os_window_be_rendered(OSWindow* w) {
return (
glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED) ||
!glfwGetWindowAttrib(w->handle, GLFW_VISIBLE) ||
glfwGetWindowAttrib(w->handle, GLFW_OCCLUDED) ||
!glfwAreSwapsAllowed(w->handle)
glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED)
|| !glfwGetWindowAttrib(w->handle, GLFW_VISIBLE)
|| glfwGetWindowAttrib(w->handle, GLFW_OCCLUDED)
|| !glfwAreSwapsAllowed(w->handle)
) ? false : true;
}
@@ -1441,28 +1461,6 @@ cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) {
#endif
}
static PyObject*
get_primary_selection(PYNOARG) {
if (glfwGetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle));
} else log_error("Failed to load glfwGetPrimarySelectionString");
Py_RETURN_NONE;
}
static PyObject*
set_primary_selection(PyObject UNUSED *self, PyObject *args) {
char *text;
Py_ssize_t sz;
if (!PyArg_ParseTuple(args, "s#", &text, &sz)) return NULL;
if (glfwSetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) glfwSetPrimarySelectionString(w->handle, text);
}
else log_error("Failed to load glfwSetPrimarySelectionString");
Py_RETURN_NONE;
}
static PyObject*
set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
int shape;
@@ -1618,26 +1616,94 @@ set_ignore_os_keyboard_processing(bool enabled) {
glfwSetIgnoreOSKeyboardProcessing(enabled);
}
static void
decref_pyobj(void *x) {
Py_XDECREF(x);
}
static GLFWDataChunk
get_clipboard_data(const char *mime_type, void *iter, GLFWClipboardType ct) {
GLFWDataChunk ans = {.iter=iter, .free=decref_pyobj};
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) { return ans; }
PyObject *i = PyObject_CallFunction(c, "s", mime_type);
Py_DECREF(c);
if (!i) { return ans; }
ans.iter = i;
return ans;
}
if (mime_type == NULL) {
Py_XDECREF(iter);
return ans;
}
PyObject *ret = PyObject_CallFunctionObjArgs(iter, NULL);
if (ret == NULL) return ans;
ans.data = PyBytes_AS_STRING(ret);
ans.sz = PyBytes_GET_SIZE(ret);
ans.free_data = ret;
return ans;
}
static PyObject*
set_clipboard_data_types(PyObject *self UNUSED, PyObject *args) {
PyObject *mta;
int ctype;
if (!PyArg_ParseTuple(args, "iO!", &ctype, &PyTuple_Type, &mta)) return NULL;
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;
if (data == NULL) {
PyErr_SetString(PyExc_RuntimeError, "is_self_offer");
return false;
}
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[] = {
METHODB(set_custom_cursor, METH_VARARGS),
{"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL},
METHODB(set_default_window_icon, METH_VARARGS),
METHODB(get_clipboard_string, METH_NOARGS),
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),
METHODB(set_clipboard_string, METH_VARARGS),
METHODB(toggle_fullscreen, METH_VARARGS),
METHODB(toggle_maximized, METH_VARARGS),
METHODB(change_os_window_state, METH_VARARGS),
METHODB(glfw_window_hint, METH_VARARGS),
METHODB(get_primary_selection, METH_NOARGS),
METHODB(x11_display, METH_NOARGS),
METHODB(get_click_interval, METH_NOARGS),
METHODB(x11_window_id, METH_O),
METHODB(set_primary_selection, METH_VARARGS),
METHODB(strip_csi, METH_O),
#ifndef __APPLE__
METHODB(dbus_send_notification, METH_VARARGS),
@@ -1672,6 +1738,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_REPEAT);
ADDC(true); ADDC(false);
ADDC(GLFW_IBEAM_CURSOR); ADDC(GLFW_HAND_CURSOR); ADDC(GLFW_ARROW_CURSOR);
ADDC(GLFW_PRIMARY_SELECTION); ADDC(GLFW_CLIPBOARD);
/* start glfw functional keys (auto generated by gen-key-constants.py do not edit) */
ADDC(GLFW_FKEY_ESCAPE);

View File

@@ -314,7 +314,7 @@ SingleKey_set_vals(SingleKey *self, long long key, unsigned short mods, int is_n
keybitfield k = (keybitfield)(unsigned long long)key;
self->key.key = k & BIT_MASK(keybitfield, KEY_BITS);
}
if (!(mods & 1 << (MOD_BITS + 1))) self->key.mods = mods & BIT_MASK(u_int32_t, MOD_BITS);
if (!(mods & 1 << (MOD_BITS + 1))) self->key.mods = mods & BIT_MASK(uint32_t, MOD_BITS);
if (is_native > -1) self->key.is_native = is_native ? 1 : 0;
}

View File

@@ -14,17 +14,15 @@ from .boss import Boss
from .child import Child
from .cli import parse_args
from .cli_stub import LaunchCLIOptions
from .clipboard import set_clipboard_string, set_primary_selection
from .constants import kitty_exe, shell_path
from .fast_data_types import (
add_timer, get_boss, get_options, get_os_window_title,
patch_color_profiles, set_clipboard_string
add_timer, get_boss, get_options, get_os_window_title, patch_color_profiles
)
from .options.utils import env as parse_env
from .tabs import Tab, TabManager
from .types import OverlayType, run_once
from .utils import (
get_editor, log_error, resolve_custom_file, set_primary_selection, which
)
from .utils import get_editor, log_error, resolve_custom_file, which
from .window import CwdRequest, CwdRequestType, Watchers, Window
try:

View File

@@ -15,6 +15,7 @@
#endif
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <Python.h>
#include <fcntl.h>

View File

@@ -61,7 +61,7 @@ def talk_to_instance(args: CLIOptions) -> None:
if args.session == '-':
stdin = sys.stdin.read()
data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'cmdline_args_for_open': getattr(sys, 'cmdline_args_for_open', []),
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'),
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'), 'activation_token': os.environ.get('XDG_ACTIVATION_TOKEN'),
'cwd': os.getcwd(), 'stdin': stdin}
notify_socket = None
if args.wait_for_single_instance_window_close:

View File

@@ -141,6 +141,19 @@ encode_mouse_scroll(Window *w, int button, int mods) {
// }}}
static Window*
window_for_id(id_type window_id) {
if (global_state.callback_os_window && global_state.callback_os_window->num_tabs) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
if (w->id == window_id) return w;
}
}
return window_for_window_id(window_id);
}
static bool
dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabbed) {
bool handled = false;
@@ -492,8 +505,10 @@ send_pending_click_to_window(Window *w, void *data) {
) {
MousePosition current_pos = w->mouse_pos;
w->mouse_pos = pc->mouse_pos;
id_type wid = w->id;
dispatch_mouse_event(w, pc->button, pc->count, pc->modifiers, pc->grabbed);
w->mouse_pos = current_pos;
w = window_for_id(wid);
if (w) w->mouse_pos = current_pos;
}
#undef press
}
@@ -581,16 +596,6 @@ mouse_in_region(Region *r) {
return true;
}
static Window*
window_for_id(id_type window_id) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) {
Window *w = t->windows + i;
if (w->id == window_id) return w;
}
return NULL;
}
static Window*
window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
Region central, tab_bar;
@@ -653,7 +658,7 @@ enter_event() {
// On cocoa there is no way to configure the window manager to
// focus windows on mouse enter, so we do it ourselves
if (OPT(focus_follows_mouse) && !global_state.callback_os_window->is_focused) {
focus_os_window(global_state.callback_os_window, false);
focus_os_window(global_state.callback_os_window, false, NULL);
}
#endif
}

View File

@@ -60,6 +60,7 @@ class Session:
self.default_title = default_title
self.os_window_size: Optional[WindowSizes] = None
self.os_window_class: Optional[str] = None
self.focus_os_window: bool = False
def add_tab(self, opts: Options, name: str = '') -> None:
if self.tabs and not self.tabs[-1].windows:
@@ -146,6 +147,8 @@ def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
ans.add_window(rest)
elif cmd == 'focus':
ans.focus()
elif cmd == 'focus_os_window':
ans.focus_os_window = True
elif cmd == 'enabled_layouts':
ans.set_enabled_layouts(rest)
elif cmd == 'cd':

View File

@@ -822,14 +822,26 @@ PYWRAP0(current_application_quit_request) {
PYWRAP1(focus_os_window) {
id_type os_window_id;
int also_raise = 1;
PA("K|p", &os_window_id, &also_raise);
const char *activation_token = NULL;
PA("K|pz", &os_window_id, &also_raise, &activation_token);
WITH_OS_WINDOW(os_window_id)
if (!os_window->is_focused) focus_os_window(os_window, also_raise);
if (!os_window->is_focused || (activation_token && activation_token[0])) focus_os_window(os_window, also_raise, activation_token);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(run_with_activation_token) {
for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *os_window = global_state.os_windows + o;
if (os_window->is_focused) {
run_with_activation_token_in_os_window(os_window, args);
break;
}
}
Py_RETURN_NONE;
}
PYWRAP1(set_titlebar_color) {
id_type os_window_id;
unsigned int color;
@@ -1291,6 +1303,7 @@ static PyMethodDef module_methods[] = {
MW(set_titlebar_color, METH_VARARGS),
MW(focus_os_window, METH_VARARGS),
MW(mark_tab_bar_dirty, METH_O),
MW(run_with_activation_token, METH_O),
MW(change_background_opacity, METH_VARARGS),
MW(background_opacity_of, METH_O),
MW(update_window_visibility, METH_VARARGS),

View File

@@ -262,7 +262,7 @@ void get_os_window_size(OSWindow *os_window, int *w, int *h, int *fw, int *fh);
void get_os_window_content_scale(OSWindow *os_window, double *xdpi, double *ydpi, float *xscale, float *yscale);
void update_os_window_references(void);
void mark_os_window_for_close(OSWindow* w, CloseRequest cr);
void update_os_window_viewport(OSWindow *window, bool);
void update_os_window_viewport(OSWindow *window, bool notify_boss);
bool should_os_window_be_rendered(OSWindow* w);
void wakeup_main_loop(void);
void swap_window_buffers(OSWindow *w);
@@ -270,7 +270,8 @@ bool make_window_context_current(id_type);
void hide_mouse(OSWindow *w);
bool is_mouse_hidden(OSWindow *w);
void destroy_os_window(OSWindow *w);
void focus_os_window(OSWindow *w, bool also_raise);
void focus_os_window(OSWindow *w, bool also_raise, const char *activation_token);
void run_with_activation_token_in_os_window(OSWindow *w, PyObject *callback);
void set_os_window_title(OSWindow *w, const char *title);
OSWindow* os_window_for_kitty_window(id_type);
OSWindow* add_os_window(void);

View File

@@ -19,8 +19,7 @@ from typing import (
from .constants import (
appname, clear_handled_signals, config_dir, is_macos, is_wayland,
read_kitty_resource, runtime_dir, shell_path, ssh_control_master_template,
supports_primary_selection
read_kitty_resource, runtime_dir, shell_path, ssh_control_master_template
)
from .fast_data_types import Color, open_tty
from .rgb import to_color
@@ -230,20 +229,6 @@ def fit_image(width: int, height: int, pwidth: int, pheight: int) -> Tuple[int,
return int(width), int(height)
def set_primary_selection(text: Union[str, bytes]) -> None:
if not supports_primary_selection:
return # There is no primary selection
from kitty.fast_data_types import set_primary_selection as s
s(text)
def get_primary_selection() -> str:
if not supports_primary_selection:
return '' # There is no primary selection
from kitty.fast_data_types import get_primary_selection as g
return (g() or b'').decode('utf-8', 'replace')
def base64_encode(
integer: int,
chars: str = string.ascii_uppercase + string.ascii_lowercase + string.digits +

View File

@@ -20,6 +20,10 @@ from typing import (
from .child import ProcessDesc
from .cli_stub import CLIOptions
from .clipboard import (
get_clipboard_string, get_primary_selection, set_clipboard_string,
set_primary_selection
)
from .config import build_ansi_color_table
from .constants import (
appname, clear_handled_signals, config_dir, is_macos, wakeup_io_loop
@@ -34,23 +38,25 @@ from .fast_data_types import (
KeyEvent, Screen, add_timer, add_window, cell_size_for_window,
click_mouse_cmd_output, click_mouse_url, compile_program,
current_os_window, encode_key_for_tty, get_boss, get_click_interval,
get_clipboard_string, get_options, init_cell_program, mark_os_window_dirty,
mouse_selection, move_cursor_to_mouse_if_in_prompt, pt_to_px,
set_clipboard_string, set_titlebar_color, set_window_logo,
set_window_padding, set_window_render_data, update_ime_position_for_window,
update_window_title, update_window_visibility, wakeup_main_loop
get_options, init_cell_program, mark_os_window_dirty, mouse_selection,
move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color,
set_window_logo, set_window_padding, set_window_render_data,
update_ime_position_for_window, update_window_title,
update_window_visibility, wakeup_main_loop
)
from .keys import keyboard_mode_name, mod_mask
from .notify import NotificationCommand, handle_notification_cmd, sanitize_identifier_pat
from .notify import (
NotificationCommand, handle_notification_cmd, sanitize_identifier_pat
)
from .options.types import Options
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import (
docs_url, get_primary_selection, kitty_ansi_sanitizer_pat, load_shaders,
log_error, open_cmd, open_url, parse_color_set, path_from_osc7_url,
resolve_custom_file, resolved_shell, sanitize_title, set_primary_selection
docs_url, kitty_ansi_sanitizer_pat, load_shaders, log_error, open_cmd,
open_url, parse_color_set, path_from_osc7_url, resolve_custom_file,
resolved_shell, sanitize_title
)
MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]]

View File

@@ -50,13 +50,11 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge
'pid': os.getpid(),
'font_family': get_options().font_family,
'stdin': sys.stdin.read(),
'done': 'hello',
}, indent=2))"""], cwd=cwd, env=env, stdin_data=stdin_data)
}, indent=2), "ALL_OUTPUT_PRESENT", sep="")"""], cwd=cwd, env=env, stdin_data=stdin_data)
self.assertFalse(pty.screen_contents().strip())
p.mark_child_as_ready(child.child_id)
pty.wait_till(lambda: 'hello' in pty.screen_contents())
data = json.loads(pty.screen_contents())
pty.wait_till(lambda: 'ALL_OUTPUT_PRESENT' in pty.screen_contents())
data = json.JSONDecoder().raw_decode(pty.screen_contents())[0]
self.ae(data['cols'], cols)
self.assertTrue(data['cterm'])
self.ae(data['ttyname'], ttyname)

View File

@@ -25,10 +25,11 @@ def bash_ok():
v = shutil.which('bash')
if not v:
return False
o = subprocess.check_output([v, '-c', 'echo "${BASH_VERSION}"']).decode('utf-8').strip()
if not o or int(o[0]) < 5:
o = subprocess.check_output([v, '-c', 'echo "${BASH_VERSINFO[0]}\n${BASH_VERSINFO[4]}"']).decode('utf-8').splitlines()
if not o:
return False
return True
major_ver, relstatus = o[0], o[-1]
return int(major_ver) >= 5 and relstatus == 'release'
def basic_shell_env(home_dir):
@@ -240,7 +241,7 @@ function _set_status_prompt; function fish_prompt; echo -n "$pipestatus $status
pty.send_cmd_to_child('exit')
@unittest.skipUnless(bash_ok(), 'bash not installed or too old')
@unittest.skipUnless(bash_ok(), 'bash not installed, too old, or debug build')
def test_bash_integration(self):
ps1 = 'prompt> '
with self.run_shell(

View File

@@ -149,7 +149,7 @@ copy --exclude */w.* d1
self.ae(len(glob.glob(f'{remote_home}/{tname}/*/xterm-kitty')), 2)
def test_ssh_env_vars(self):
tset = '$A-$(echo no)-`echo no2` "something\nelse"'
tset = '$A-$(echo no)-`echo no2` !Q5 "something\nelse"'
for sh in self.all_possible_sh:
with self.subTest(sh=sh), tempfile.TemporaryDirectory() as tdir:
os.mkdir(os.path.join(tdir, 'cwd'))

View File

@@ -1,15 +1,10 @@
[flake8]
max-line-length = 160
exclude==template.py,linux-package
[yapf]
based_on_style = pep8
split_penalty_import_names = 100
dedent_closing_brackets = True
coalesce_brackets = True
blank_line_before_nested_class_or_def = True
extend_ignore=W503,E203
[isort]
profile = black
combine_as_imports = True
multi_line_output = 5

View File

@@ -92,6 +92,7 @@ read_base64_from_tty() {
untar_and_read_env() {
# extract the tar file atomically, in the sense that any file from the
# tarfile is only put into place after it has been fully written to disk
command -v tar > /dev/null 2> /dev/null || die "tar is not availiable on this server. The ssh kitten requires tar."
tdir=$(command mktemp -d "$HOME/.kitty-ssh-kitten-untar-XXXXXXXXXXXX")
[ $? = 0 ] || die "Creating temp directory failed"
# suppress STDERR for tar as tar prints various warnings if for instance, timestamps are in the future