mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-14 00:47:02 +00:00
Compare commits
32 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a771b1d7e5 | ||
|
|
a28abf1573 | ||
|
|
5b8d8b9f43 | ||
|
|
56f276580f | ||
|
|
8b17088b58 | ||
|
|
8b85bc3440 | ||
|
|
7cd9ba03a3 | ||
|
|
572b14f994 | ||
|
|
c2a7225657 | ||
|
|
9c29c612a5 | ||
|
|
217a01cfdf | ||
|
|
c909809bb4 | ||
|
|
b28712bfae | ||
|
|
cb8fe24ab1 | ||
|
|
c0661024d8 | ||
|
|
824b46507c | ||
|
|
b9261c4e26 | ||
|
|
2bda489698 | ||
|
|
0a69b89a80 | ||
|
|
12bdf972e0 | ||
|
|
4e3a3ba6ab | ||
|
|
654cce687e | ||
|
|
f4e9824e18 | ||
|
|
02cfa89bae | ||
|
|
634f13e65f | ||
|
|
43b028bd6a | ||
|
|
114f1ff128 | ||
|
|
845aeeffd9 | ||
|
|
9993f82d64 | ||
|
|
15f71ebd37 | ||
|
|
07ec007388 | ||
|
|
b3e7c3e717 |
32 changed files with 707 additions and 328 deletions
1
.github/workflows/ci.py
vendored
1
.github/workflows/ci.py
vendored
|
|
@ -232,6 +232,7 @@ IGNORED_DEPENDENCY_CVES = [
|
|||
'CVE-2026-4224',
|
||||
'CVE-2026-4519',
|
||||
'CVE-2026-1502',
|
||||
'CVE-2026-7210', # DoS in unused XML parser
|
||||
# github.com/nwaples/rardecode/v2
|
||||
'CVE-2025-11579', # rardecode is version 2.2.1, not vulnerable
|
||||
'CVE-2026-2673', # openssl fix not released
|
||||
|
|
|
|||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -54,7 +54,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4.35.2
|
||||
uses: github/codeql-action/init@v4.35.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
trap-caching: false
|
||||
|
|
@ -64,7 +64,7 @@ jobs:
|
|||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4.35.2
|
||||
uses: github/codeql-action/analyze@v4.35.3
|
||||
|
||||
- name: Run govulncheck
|
||||
if: matrix.language == 'go'
|
||||
|
|
|
|||
|
|
@ -276,6 +276,8 @@ Detailed list of changes
|
|||
|
||||
- ssh kitten: Sanitize user controlled data in error messages that might leak to shell (:cve:`2026-42850`)
|
||||
|
||||
- Linux: Respect the fontconfig matrix setting commonly used for fake slant with fonts that do not have italic variants (:pull:`9990`)
|
||||
|
||||
|
||||
0.46.2 [2026-03-21]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ examine the :ref:`machine_id` sent with the enable drag offers
|
|||
from the URI list. To request data for a particular entry, terminals send an
|
||||
escape code of the form::
|
||||
|
||||
OSC _dnd_code ; t=k:x=idx ; base64 encoded file data ST
|
||||
OSC _dnd_code ; t=k:x=idx ST
|
||||
|
||||
Here ``idx`` is the one based index into the list of entries in the
|
||||
``text/uri-list`` MIME type. Then the client can respond with the data
|
||||
|
|
@ -419,11 +419,6 @@ adding ``Y=parent-handle:y=num`` to the escape codes above. Here
|
|||
is the one based index into the list of entries in the directory. Thus, the
|
||||
set of keys ``x, y, Y`` uniquely determine an entry.
|
||||
|
||||
Once all data for a dirctory is transmitted, the client informs the terminal emulator of
|
||||
completion with::
|
||||
|
||||
OSC _dnd_code ; t=k:Y=handle ; ST
|
||||
|
||||
If any error occurs in the client while reading the data, it can inform
|
||||
the terminal using::
|
||||
|
||||
|
|
|
|||
|
|
@ -878,6 +878,7 @@ static void _glfwUpdateNotchCover(_GLFWwindow*);
|
|||
|
||||
- (instancetype)initWithWindow:(_GLFWwindow*)initWindow mimeType:(const char*)mime instanceId:(GLFWid)iid;
|
||||
- (void)request_drag_data;
|
||||
- (void)promised_data_ready:(const char*)data sz:(size_t)sz type:(int)type;
|
||||
- (void)end_transfer:(int)errorCode;
|
||||
- (void)end_transfer_with_error:(NSError*)err;
|
||||
- (bool)is_mimetype:(const char*)mime_type;
|
||||
|
|
@ -4333,7 +4334,7 @@ add_uri_list_drag_items(_GLFWwindow *window, NSMutableArray<NSDraggingItem*>* dr
|
|||
NSString *extension = [url pathExtension];
|
||||
UTType *type = [UTType typeWithFilenameExtension:extension];
|
||||
if (!type) type = UTTypeItem;
|
||||
snprintf(buf, sizeof(buf), "text/uri-list:%d", count);
|
||||
snprintf(buf, sizeof(buf), "kitty-internal/uri-list-item-%d", count);
|
||||
GLFWFilePromiseProviderDelegate* delegate = [[[GLFWFilePromiseProviderDelegate alloc]
|
||||
initWithWindow:window mimeType:buf instanceId:_glfw.drag.instance_id] autorelease];
|
||||
NSFilePromiseProvider *provider = [[[NSFilePromiseProvider alloc]
|
||||
|
|
@ -4351,12 +4352,21 @@ add_uri_list_drag_items(_GLFWwindow *window, NSMutableArray<NSDraggingItem*>* dr
|
|||
|
||||
static int
|
||||
add_drag_items(_GLFWwindow *window, NSMutableArray<NSDraggingItem*>* dragItems, GLFWDragSourceItem *mime_item, const GLFWimage *thumbnail) {
|
||||
// URI list items get added directly to the clipboard for use by native apps since macOS does not support
|
||||
// the text/uri-list MIME type, however this type remains as a private
|
||||
// kitty type to enable kitty to kitty DnD for remote clients.
|
||||
bool is_uri_list = false;
|
||||
if (strcmp(mime_item->mime_type, "text/uri-list") == 0 && mime_item->optional_data && mime_item->data_size) {
|
||||
return add_uri_list_drag_items(window, dragItems, mime_item->optional_data, mime_item->data_size, mime_item->is_remote_client, thumbnail);
|
||||
is_uri_list = true;
|
||||
int err = add_uri_list_drag_items(window, dragItems, mime_item->optional_data, mime_item->data_size, mime_item->is_remote_client, thumbnail);
|
||||
if (err != 0) return err;
|
||||
}
|
||||
NSString* utiString = mime_to_uti(mime_item->mime_type);
|
||||
id w;
|
||||
if (mime_item->optional_data) {
|
||||
// remote client URI list must be set a file promise so that the kitty drag
|
||||
// source code receives a request for it and can then fetch the remote
|
||||
// files.
|
||||
if (mime_item->optional_data && !(is_uri_list && mime_item->is_remote_client)) {
|
||||
NSPasteboardItem *pbItem = [[[NSPasteboardItem alloc] init] autorelease];
|
||||
NSData *data = [NSData dataWithBytes:mime_item->optional_data length:mime_item->data_size];
|
||||
[pbItem setData:data forType:utiString];
|
||||
|
|
@ -4451,6 +4461,104 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autore
|
|||
|
||||
- (bool)is_mimetype:(const char*)q { return strcmp(q, mimeType) == 0; }
|
||||
|
||||
- (void)promised_data_ready:(const char*)path sz:(size_t)sz type:(int)type {
|
||||
if (drag_finish_timer) {
|
||||
[drag_finish_timer invalidate];
|
||||
drag_finish_timer = nil;
|
||||
}
|
||||
if (path == NULL) return; // progress update
|
||||
if (file_handle) [file_handle release];
|
||||
file_handle = nil;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
// Erase the empty placeholder file created by writePromiseToURL:
|
||||
[fileManager removeItemAtURL:file_url error:nil];
|
||||
NSError *error = nil;
|
||||
NSURL *srcURL = [NSURL fileURLWithPath:@(path)];
|
||||
switch (type) {
|
||||
case 0:
|
||||
// Create a hard link to path at file_url
|
||||
if (![fileManager linkItemAtURL:srcURL toURL:file_url error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
// Create a symlink to the same destination as the symlink at path
|
||||
NSString *linkDest = [fileManager destinationOfSymbolicLinkAtPath:@(path) error:&error];
|
||||
if (!linkDest) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
if (![fileManager createSymbolicLinkAtPath:file_url.path withDestinationPath:linkDest error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Copy the directory at path to file_url recursively using hard links for files
|
||||
if (![fileManager createDirectoryAtURL:file_url withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
NSString *srcPath = srcURL.path;
|
||||
NSString *dstPath = file_url.path;
|
||||
__block NSError *enumError = nil;
|
||||
NSDirectoryEnumerator<NSURL*> *enumerator = [fileManager
|
||||
enumeratorAtURL:srcURL
|
||||
includingPropertiesForKeys:@[NSURLIsDirectoryKey, NSURLIsSymbolicLinkKey]
|
||||
options:0
|
||||
errorHandler:^BOOL(NSURL *url, NSError *err) {
|
||||
(void)url;
|
||||
enumError = err;
|
||||
return NO;
|
||||
}];
|
||||
for (NSURL *itemURL in enumerator) {
|
||||
NSString *itemPath = itemURL.path;
|
||||
if (itemPath.length <= srcPath.length) continue;
|
||||
NSString *relativePath = [itemPath substringFromIndex:srcPath.length + 1];
|
||||
NSURL *destURL = [NSURL fileURLWithPath:[dstPath stringByAppendingPathComponent:relativePath]];
|
||||
NSNumber *isSymlink = nil, *isDirectory = nil;
|
||||
if (![itemURL getResourceValue:&isSymlink forKey:NSURLIsSymbolicLinkKey error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
if (![itemURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
if ([isSymlink boolValue]) {
|
||||
NSString *linkDest = [fileManager destinationOfSymbolicLinkAtPath:itemPath error:&error];
|
||||
if (!linkDest) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
if (![fileManager createSymbolicLinkAtPath:destURL.path withDestinationPath:linkDest error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
} else if ([isDirectory boolValue]) {
|
||||
if (![fileManager createDirectoryAtURL:destURL withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (![fileManager linkItemAtURL:itemURL toURL:destURL error:&error]) {
|
||||
[self end_transfer_with_error:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enumError) {
|
||||
[self end_transfer_with_error:enumError];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self end_transfer:0];
|
||||
}
|
||||
|
||||
- (void)request_drag_data {
|
||||
if (instanceId != _glfw.drag.instance_id) { [self end_transfer:EINVAL]; return; }
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
|
|
@ -4497,6 +4605,8 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {@autore
|
|||
|
||||
- (void)dealloc {
|
||||
free(mimeType); mimeType = NULL;
|
||||
if (file_handle) [file_handle release];
|
||||
file_handle = nil;
|
||||
if (file_url) [file_url release];
|
||||
file_url = nil;
|
||||
[self end_transfer:EINVAL];
|
||||
|
|
@ -4593,11 +4703,15 @@ _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) {@autoreleasepool{
|
|||
return 0;
|
||||
}}
|
||||
|
||||
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type) {
|
||||
_glfwPlatformDragDataReady(const char *mime_type, const char *data, size_t sz, int type) {
|
||||
if (!file_promise_providers) return 0;
|
||||
for (GLFWFilePromiseProviderDelegate *d in file_promise_providers) {
|
||||
if ([d is_mimetype:mime_type]) [d request_drag_data];
|
||||
if ([d is_mimetype:mime_type]) {
|
||||
if (type == -1) [d request_drag_data];
|
||||
else [d promised_data_ready:data sz:sz type:type];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
1
glfw/glfw3.h
vendored
1
glfw/glfw3.h
vendored
|
|
@ -1828,6 +1828,7 @@ typedef struct GLFWDragSourceItem {
|
|||
const char *optional_data;
|
||||
size_t data_size;
|
||||
bool is_remote_client;
|
||||
int type; // used for file promises type of entry 0 = regular, 1 = symlink, 2 = directory
|
||||
} GLFWDragSourceItem;
|
||||
|
||||
typedef struct GLFWDragEvent {
|
||||
|
|
|
|||
2
glfw/input.c
vendored
2
glfw/input.c
vendored
|
|
@ -1190,7 +1190,7 @@ glfwStartDrag(GLFWwindow* handle, const GLFWDragSourceItem *items, size_t item_c
|
|||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(EINVAL);
|
||||
if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type);
|
||||
if (operations == -1) return _glfwPlatformDragDataReady(items[0].mime_type, items[0].optional_data, items[0].data_size, items[0].type);
|
||||
if (operations == -2) return _glfwPlatformChangeDragImage(thumbnail);
|
||||
if (operations == -3) { _glfwPlatformCancelDrag(window); return 0; }
|
||||
_glfwFreeDragSourceData();
|
||||
|
|
|
|||
2
glfw/internal.h
vendored
2
glfw/internal.h
vendored
|
|
@ -840,7 +840,7 @@ void _glfwPlatformCancelDrag(_GLFWwindow* window);
|
|||
void _glfwFreeDragSourceData(void);
|
||||
void _glfwPlatformFreeDragSourceData(void);
|
||||
void _glfwInputDragSourceRequest(_GLFWwindow* window, GLFWDragEvent *ev);
|
||||
int _glfwPlatformDragDataReady(const char *mime_type);
|
||||
int _glfwPlatformDragDataReady(const char *mime_type, const char *data, size_t sz, int type);
|
||||
int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail);
|
||||
|
||||
|
||||
|
|
|
|||
3
glfw/null_window.c
vendored
3
glfw/null_window.c
vendored
|
|
@ -547,7 +547,8 @@ _glfwPlatformStartDrag(_GLFWwindow* window, const GLFWimage* thumbnail) {
|
|||
return ENOTSUP;
|
||||
}
|
||||
void _glfwPlatformFreeDragSourceData(void) {}
|
||||
int _glfwPlatformDragDataReady(const char *mime_type) { (void) mime_type; return 0; }
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type UNUSED, const char *data UNUSED, size_t sz UNUSED, int type UNUSED) { return 0; }
|
||||
int _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) { (void)thumbnail; return 0; }
|
||||
|
||||
const char** _glfwPlatformGetDropMimeTypes(GLFWDropData* drop UNUSED, int* count)
|
||||
|
|
|
|||
2
glfw/wl_window.c
vendored
2
glfw/wl_window.c
vendored
|
|
@ -3343,7 +3343,7 @@ _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) {
|
|||
}
|
||||
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type) {
|
||||
_glfwPlatformDragDataReady(const char *mime_type, const char *data UNUSED, size_t sz UNUSED, int type UNUSED) {
|
||||
for (size_t i = 0; i < _glfw.wl.drag.count; i++) {
|
||||
if (strcmp(dr.mime_type, mime_type) == 0) {
|
||||
if (!dr.watch_id) dr.watch_id = add_drag_watch(dr.fd);
|
||||
|
|
|
|||
2
glfw/x11_window.c
vendored
2
glfw/x11_window.c
vendored
|
|
@ -4614,7 +4614,7 @@ _glfwPlatformChangeDragImage(const GLFWimage *thumbnail) {
|
|||
}
|
||||
|
||||
int
|
||||
_glfwPlatformDragDataReady(const char *mime_type) {
|
||||
_glfwPlatformDragDataReady(const char *mime_type, const char *data UNUSED, size_t sz UNUSED, int type UNUSED) {
|
||||
// Find the pending request for this MIME type
|
||||
for (size_t i = 0; i < _glfw.x11.drag.pending_count; i++) {
|
||||
if (_glfw.x11.drag.pending_requests[i].inflight &&
|
||||
|
|
|
|||
8
go.mod
8
go.mod
|
|
@ -6,7 +6,7 @@ toolchain go1.26.3
|
|||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
github.com/alecthomas/chroma/v2 v2.24.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0
|
||||
github.com/dlclark/regexp2 v1.12.0
|
||||
github.com/ebitengine/purego v0.10.0
|
||||
|
|
@ -14,7 +14,7 @@ require (
|
|||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1
|
||||
github.com/kovidgoyal/go-parallel v1.1.1
|
||||
github.com/kovidgoyal/go-shm v1.0.0
|
||||
|
|
@ -22,12 +22,12 @@ require (
|
|||
github.com/nwaples/rardecode/v2 v2.2.2
|
||||
github.com/seancfoley/ipaddress-go v1.7.1
|
||||
github.com/sgtdi/fswatcher v1.2.0
|
||||
github.com/shirou/gopsutil/v4 v4.26.3
|
||||
github.com/shirou/gopsutil/v4 v4.26.4
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/zeebo/xxh3 v1.1.0
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.39.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/text v0.36.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
|
|
|||
16
go.sum
16
go.sum
|
|
@ -2,8 +2,8 @@ github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM
|
|||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
|
|
@ -28,8 +28,8 @@ github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1p
|
|||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 h1:rMY/hWfcVzBm6BLX6YLA+gLJEpuXBed/VP6YEkXt8R4=
|
||||
|
|
@ -56,8 +56,8 @@ github.com/seancfoley/ipaddress-go v1.7.1 h1:fDWryS+L8iaaH5RxIKbY0xB5Z+Zxk8xoXLN
|
|||
github.com/seancfoley/ipaddress-go v1.7.1/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
|
||||
github.com/sgtdi/fswatcher v1.2.0 h1:uSJuMc3/Eo/vaPnZWpJ42EFYb5j38cZENmkszOV0yhw=
|
||||
github.com/sgtdi/fswatcher v1.2.0/go.mod h1:smzXnaqu0SYJQNIwGLLkvRkpH4RdEACB7avMSsSaqjQ=
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
|
||||
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
|
|
@ -78,8 +78,8 @@ golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
|
|||
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -22,11 +22,10 @@ import (
|
|||
var _ = fmt.Print
|
||||
|
||||
type data_request struct {
|
||||
drag_source *drag_source
|
||||
send_remote_data bool
|
||||
index int
|
||||
write_id loop.IdType
|
||||
base64 streaming_base64.StreamingBase64Encoder
|
||||
drag_source *drag_source
|
||||
index int
|
||||
write_id loop.IdType
|
||||
base64 streaming_base64.StreamingBase64Encoder
|
||||
}
|
||||
|
||||
type remote_data_item struct {
|
||||
|
|
@ -51,7 +50,7 @@ type drag_status struct {
|
|||
current_remote_file *remote_data_item
|
||||
dir_handle_counter int
|
||||
remote_item_write_id loop.IdType
|
||||
remote_data_was_sent bool
|
||||
remote_data_requests []int
|
||||
}
|
||||
|
||||
func find_drag_image(drag_sources map[string]*drag_source) image.Image {
|
||||
|
|
@ -66,12 +65,10 @@ func find_drag_image(drag_sources map[string]*drag_source) image.Image {
|
|||
}
|
||||
}
|
||||
var uri_list []string
|
||||
if ds := drag_sources["text/uri-list"]; ds != nil && len(ds.data) > 0 {
|
||||
if q, err := parse_uri_list(string(ds.data)); err == nil {
|
||||
for _, path := range q {
|
||||
if path != "" {
|
||||
uri_list = append(uri_list, path)
|
||||
}
|
||||
if ds := drag_sources["text/uri-list"]; ds != nil {
|
||||
for _, e := range ds.uri_list {
|
||||
if e.path != "" {
|
||||
uri_list = append(uri_list, e.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,12 +99,10 @@ func (dnd *dnd) set_drag_image_text() (err error) {
|
|||
}
|
||||
}
|
||||
if icon == "" {
|
||||
if ds := dnd.drag_sources["text/uri-list"]; ds != nil && len(ds.data) > 0 {
|
||||
if q, err := parse_uri_list(string(ds.data)); err == nil {
|
||||
for _, path := range q {
|
||||
if path != "" && from_path(path) {
|
||||
break
|
||||
}
|
||||
if ds := dnd.drag_sources["text/uri-list"]; ds != nil {
|
||||
for _, e := range ds.uri_list {
|
||||
if e.path != "" && from_path(e.path) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -219,7 +214,7 @@ func (dnd *dnd) reset_drag() {
|
|||
dnd.drag_status = drag_status{}
|
||||
}
|
||||
|
||||
func (dnd *dnd) on_drag_event(x, y, operation, Y int) (err error) {
|
||||
func (dnd *dnd) on_drag_event(x, y, operation int) (err error) {
|
||||
switch x {
|
||||
case 1:
|
||||
dnd.drag_status.accepted_mime = y
|
||||
|
|
@ -230,21 +225,22 @@ func (dnd *dnd) on_drag_event(x, y, operation, Y int) (err error) {
|
|||
case 4:
|
||||
was_dropped := dnd.drag_status.dropped
|
||||
was_move := dnd.drag_status.accepted_operation == 2
|
||||
was_remote := dnd.drag_status.remote_data_was_sent
|
||||
dnd.reset_drag()
|
||||
if was_dropped && dnd.has_exit_on("drag-finish") {
|
||||
dnd.lp.Quit(0)
|
||||
}
|
||||
if was_dropped && was_move && was_remote {
|
||||
if was_dropped && was_move {
|
||||
if ds := dnd.drag_sources["text/uri-list"]; ds != nil {
|
||||
for _, item := range ds.uri_list {
|
||||
if item.metadata.IsDir() {
|
||||
err = os.RemoveAll(item.path)
|
||||
} else {
|
||||
err = os.Remove(item.path)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if item.was_sent {
|
||||
if item.metadata.IsDir() {
|
||||
err = os.RemoveAll(item.path)
|
||||
} else {
|
||||
err = os.Remove(item.path)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -256,7 +252,7 @@ func (dnd *dnd) on_drag_event(x, y, operation, Y int) (err error) {
|
|||
}
|
||||
}
|
||||
case 5:
|
||||
if err = dnd.handle_data_request(y, Y == 1); err != nil {
|
||||
if err = dnd.handle_data_request(y); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -272,28 +268,24 @@ func (dnd *dnd) finish_drag(errname string) {
|
|||
dnd.reset_drag()
|
||||
}
|
||||
|
||||
func (dnd *dnd) handle_data_request(idx int, send_remote_data bool) (err error) {
|
||||
func (dnd *dnd) handle_data_request(idx int) (err error) {
|
||||
if idx < 0 || idx >= len(dnd.drag_status.offered_mimes) {
|
||||
dnd.finish_drag("EINVAL")
|
||||
return fmt.Errorf("terminal asked for drag data from MIME list with out of bounds index: %d", idx)
|
||||
}
|
||||
mime := dnd.drag_status.offered_mimes[idx]
|
||||
ds := dnd.drag_sources[mime]
|
||||
send_remote_data = send_remote_data && mime == "text/uri-list" && len(ds.uri_list) > 0
|
||||
for _, dr := range dnd.drag_status.data_requests {
|
||||
if dr.index == idx {
|
||||
dnd.finish_drag("EINVAL")
|
||||
return fmt.Errorf("terminal sent a duplicate drag data request")
|
||||
}
|
||||
}
|
||||
dr := &data_request{drag_source: ds, send_remote_data: send_remote_data, index: idx}
|
||||
dr := &data_request{drag_source: ds, index: idx}
|
||||
if ds.path == "" {
|
||||
dnd.lp.QueueDnDData(DC{Type: 'e', Y: idx, Payload: utils.UnsafeStringToBytes(base64.RawStdEncoding.EncodeToString(ds.data))})
|
||||
dnd.lp.QueueDnDData(DC{Type: 'e', Y: idx}) // EOF
|
||||
if !dr.send_remote_data {
|
||||
return
|
||||
}
|
||||
return dnd.start_remote_data_send(ds)
|
||||
return
|
||||
} else {
|
||||
if ds.file != nil {
|
||||
ds.file.Close()
|
||||
|
|
@ -340,11 +332,8 @@ func (dnd *dnd) on_send_done(id loop.IdType) (err error) {
|
|||
}
|
||||
}
|
||||
if id == dnd.drag_status.remote_item_write_id {
|
||||
if dnd.drag_status.current_remote_file != nil {
|
||||
err = dnd.send_next_file_chunk()
|
||||
} else {
|
||||
err = dnd.next_remote_item()
|
||||
}
|
||||
dnd.drag_status.remote_item_write_id = 0
|
||||
err = dnd.send_next_file_chunk()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -356,9 +345,7 @@ func (dnd *dnd) on_data_request_finished(i int) (err error) {
|
|||
dr.drag_source.file = nil
|
||||
}
|
||||
dnd.drag_status.data_requests = slices.Delete(dnd.drag_status.data_requests, i, i+1)
|
||||
if dr.send_remote_data {
|
||||
err = dnd.start_remote_data_send(dr.drag_source)
|
||||
} else if len(dnd.drag_status.data_requests) > 0 {
|
||||
if len(dnd.drag_status.data_requests) > 0 {
|
||||
err = dnd.send_data_for_data_request(0)
|
||||
}
|
||||
return
|
||||
|
|
@ -386,6 +373,9 @@ func (dnd *dnd) send_remote_dir(path string, idx_in_uri_list, parent_dir_handle,
|
|||
dnd.finish_drag("EIO")
|
||||
return nil, err
|
||||
}
|
||||
for dnd.drag_status.dir_handle_counter < 2 {
|
||||
dnd.drag_status.dir_handle_counter++
|
||||
}
|
||||
handle := dnd.drag_status.dir_handle_counter
|
||||
dnd.drag_status.dir_handle_counter++
|
||||
names := make([]string, 0, len(entries))
|
||||
|
|
@ -402,7 +392,9 @@ func (dnd *dnd) send_remote_dir(path string, idx_in_uri_list, parent_dir_handle,
|
|||
names = append(names, entry.Name())
|
||||
}
|
||||
payload := utils.UnsafeStringToBytes(strings.Join(names, "\x00"))
|
||||
dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, handle, payload)
|
||||
if len(payload) > 0 {
|
||||
dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, handle, payload)
|
||||
}
|
||||
dnd.drag_status.remote_item_write_id = dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, handle, nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -413,7 +405,9 @@ func (dnd *dnd) send_remote_symlink(path string, idx_in_uri_list, parent_dir_han
|
|||
dnd.finish_drag("EIO")
|
||||
return err
|
||||
}
|
||||
dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, 1, utils.UnsafeStringToBytes(target))
|
||||
if len(target) > 0 {
|
||||
dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, 1, utils.UnsafeStringToBytes(target))
|
||||
}
|
||||
dnd.drag_status.remote_item_write_id = dnd.send_remote_item_payload(parent_dir_handle, idx, idx_in_uri_list, 1, nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -447,7 +441,11 @@ func (dnd *dnd) send_next_file_chunk() (err error) {
|
|||
|
||||
func (dnd *dnd) next_remote_item() (err error) {
|
||||
if len(dnd.drag_status.remote_items) < 1 {
|
||||
dnd.lp.QueueDnDData(DC{Type: 'k'}) // inform terminal remote data is finished
|
||||
// current remote data request finished
|
||||
dnd.drag_status.remote_data_requests = dnd.drag_status.remote_data_requests[1:]
|
||||
if len(dnd.drag_status.remote_data_requests) > 0 {
|
||||
return dnd.send_next_remote_data_request()
|
||||
}
|
||||
if len(dnd.drag_status.data_requests) > 0 {
|
||||
err = dnd.send_data_for_data_request(0)
|
||||
}
|
||||
|
|
@ -478,26 +476,44 @@ func (dnd *dnd) next_remote_item() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (dnd *dnd) start_remote_data_send(ds *drag_source) (err error) {
|
||||
dnd.drag_status.dir_handle_counter = 2
|
||||
dnd.drag_status.remote_item_write_id = 0
|
||||
dnd.drag_status.remote_data_was_sent = true
|
||||
func (dnd *dnd) on_drag_remote_data_request(idx int) (err error) {
|
||||
ds := dnd.drag_sources["text/uri-list"]
|
||||
if ds == nil || len(ds.uri_list) < 1 {
|
||||
dnd.finish_drag("EINVAL")
|
||||
return fmt.Errorf("terminal asked for drag data from URI list but no list present")
|
||||
}
|
||||
if idx < 0 || idx >= len(ds.uri_list) {
|
||||
dnd.finish_drag("EINVAL")
|
||||
return fmt.Errorf("terminal asked for drag data from URI list with out of bounds index: %d", idx)
|
||||
}
|
||||
ds.uri_list[idx].was_sent = true
|
||||
dnd.drag_status.remote_data_requests = append(dnd.drag_status.remote_data_requests, idx)
|
||||
if len(dnd.drag_status.remote_data_requests) == 1 {
|
||||
err = dnd.send_next_remote_data_request()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dnd *dnd) send_next_remote_data_request() (err error) {
|
||||
if len(dnd.drag_status.remote_data_requests) == 0 {
|
||||
return nil
|
||||
}
|
||||
i := dnd.drag_status.remote_data_requests[0]
|
||||
x := dnd.drag_sources["text/uri-list"].uri_list[i]
|
||||
items := []*remote_data_item{}
|
||||
for i, x := range ds.uri_list {
|
||||
if x.metadata.IsDir() {
|
||||
if children, err := dnd.send_remote_dir(x.path, i, 0, i); err != nil {
|
||||
return err
|
||||
} else {
|
||||
items = append(items, children...)
|
||||
}
|
||||
} else if x.metadata.Mode().Type()&os.ModeSymlink != 0 {
|
||||
if err = dnd.send_remote_symlink(x.path, i, 0, i); err != nil {
|
||||
return err
|
||||
}
|
||||
if x.metadata.IsDir() {
|
||||
if children, err := dnd.send_remote_dir(x.path, i, 0, i); err != nil {
|
||||
return err
|
||||
} else {
|
||||
f := remote_data_item{idx_in_parent: i, idx_in_uri_list: i, metadata: x.metadata, path: x.path}
|
||||
dnd.drag_status.remote_items = append(dnd.drag_status.remote_items, &f)
|
||||
items = append(items, children...)
|
||||
}
|
||||
} else if x.metadata.Mode().Type()&os.ModeSymlink != 0 {
|
||||
if err = dnd.send_remote_symlink(x.path, i, 0, i); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
f := remote_data_item{idx_in_parent: i, idx_in_uri_list: i, metadata: x.metadata, path: x.path}
|
||||
dnd.drag_status.remote_items = append(dnd.drag_status.remote_items, &f)
|
||||
}
|
||||
dnd.drag_status.remote_items = append(dnd.drag_status.remote_items, items...)
|
||||
if dnd.drag_status.remote_item_write_id == 0 {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type uri_list_item struct {
|
|||
path, uri, human_name string
|
||||
file *os.File
|
||||
metadata os.FileInfo
|
||||
was_sent bool
|
||||
}
|
||||
|
||||
type drag_source struct {
|
||||
|
|
@ -105,7 +106,7 @@ func (dnd *dnd) send_test_response(payload string) {
|
|||
}
|
||||
|
||||
func (dnd *dnd) has_exit_on(event string) bool {
|
||||
for _, e := range strings.Split(dnd.opts.ExitOn, ",") {
|
||||
for e := range strings.SplitSeq(dnd.opts.ExitOn, ",") {
|
||||
if strings.TrimSpace(e) == event {
|
||||
return true
|
||||
}
|
||||
|
|
@ -276,7 +277,9 @@ func (dnd *dnd) run_loop() (err error) {
|
|||
case 'E':
|
||||
return dnd.on_drag_error(cmd)
|
||||
case 'e':
|
||||
return dnd.on_drag_event(cmd.X, cmd.Y, cmd.Operation, cmd.Yp)
|
||||
return dnd.on_drag_event(cmd.X, cmd.Y, cmd.Operation)
|
||||
case 'k':
|
||||
return dnd.on_drag_remote_data_request(cmd.X - 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -378,6 +381,7 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error
|
|||
}
|
||||
var uri_list []uri_list_item
|
||||
for _, arg := range args {
|
||||
arg = utils.Expanduser(arg)
|
||||
st, err := os.Lstat(arg)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
|
|
|
|||
|
|
@ -200,10 +200,12 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||
if hint == "" {
|
||||
hint = " "
|
||||
}
|
||||
if len(mark_text) <= len(hint) {
|
||||
hint_runes := len(hint)
|
||||
runes := []rune(mark_text)
|
||||
if len(runes) <= hint_runes {
|
||||
mark_text = ""
|
||||
} else {
|
||||
replaced_text := mark_text[:len(hint)]
|
||||
replaced_text := string(runes[:hint_runes])
|
||||
replaced_text = strings.ReplaceAll(replaced_text, "\r", "\n")
|
||||
if strings.Contains(replaced_text, "\n") {
|
||||
buf := strings.Builder{}
|
||||
|
|
@ -224,7 +226,7 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
|||
}
|
||||
hint = buf.String()
|
||||
}
|
||||
mark_text = mark_text[len(hint):]
|
||||
mark_text = string(runes[hint_runes:])
|
||||
}
|
||||
ans := hint_style(hint) + text_style(mark_text)
|
||||
return fmt.Sprintf("\x1b]8;;mark:%d\a%s\x1b]8;;\a", m.Index, ans)
|
||||
|
|
|
|||
|
|
@ -977,8 +977,16 @@ class Boss:
|
|||
override_title=args.title or None, window_state=wstate, x=pos_x, y=pos_y)
|
||||
if session.focus_os_window:
|
||||
focused_os_window = os_window_id
|
||||
if opts.background_opacity != get_options().background_opacity:
|
||||
global_opts = get_options()
|
||||
if opts.background_opacity != global_opts.background_opacity:
|
||||
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
|
||||
if opts.background_image != global_opts.background_image:
|
||||
self.set_background_image(
|
||||
opts.background_image[0] if opts.background_image else None, (os_window_id,), False,
|
||||
layout=opts.background_image_layout,
|
||||
linear_interpolation=opts.background_image_linear,
|
||||
tint=opts.background_tint,
|
||||
tint_gaps=opts.background_tint_gaps)
|
||||
if n := data.get('notify_on_os_window_death'):
|
||||
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, n)
|
||||
if focused_os_window > 0:
|
||||
|
|
|
|||
466
kitty/dnd.c
466
kitty/dnd.c
|
|
@ -33,6 +33,8 @@ static size_t REMOTE_DRAG_LIMIT = DEFAULT_REMOTE_DRAG_LIMIT;
|
|||
static PyObject *g_dnd_test_write_func = NULL;
|
||||
static const unsigned file_permissions = 0644;
|
||||
static const unsigned dir_permissions = 0755;
|
||||
#define startwith_literal(x, literal) (x != NULL && strlen(x) >= sizeof(literal)-1 && memcmp(x, literal, sizeof(literal)-1) == 0)
|
||||
|
||||
|
||||
// Utils {{{
|
||||
// In test mode, this callable is invoked instead of schedule_write_to_child_if_possible.
|
||||
|
|
@ -1252,8 +1254,17 @@ drag_free_remote_item(DragRemoteItem *x) {
|
|||
zero_at_ptr(x);
|
||||
}
|
||||
|
||||
static void
|
||||
delete_basedir_for_remote_items(Window *w) {
|
||||
if (ds.base_dir_fd_plus_one) {
|
||||
rmtree_best_effort(".", ds.base_dir_fd_plus_one - 1);
|
||||
ds.base_dir_fd_plus_one = 0;
|
||||
}
|
||||
free(ds.base_dir_for_remote_items); ds.base_dir_for_remote_items = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
drag_free_offer(Window *w) {
|
||||
drag_free_offer(Window *w, bool remove_remote_items) {
|
||||
free(ds.mimes_buf); ds.mimes_buf = NULL; ds.bufsz = 0;
|
||||
if (ds.items) {
|
||||
for (size_t i=0; i < ds.num_mimes; i++) {
|
||||
|
|
@ -1268,11 +1279,6 @@ drag_free_offer(Window *w) {
|
|||
free(ds.items[i].remote_items); ds.items[i].remote_items = NULL;
|
||||
ds.items[i].num_remote_items = 0;
|
||||
}
|
||||
if (ds.items[i].base_dir_fd_plus_one) {
|
||||
rmtree_best_effort(".", ds.items[i].base_dir_fd_plus_one - 1);
|
||||
ds.items[i].base_dir_fd_plus_one = 0;
|
||||
}
|
||||
free(ds.items[i].base_dir_for_remote_items); ds.items[i].base_dir_for_remote_items = NULL;
|
||||
}
|
||||
free(ds.items);
|
||||
ds.items = NULL;
|
||||
|
|
@ -1288,6 +1294,15 @@ drag_free_offer(Window *w) {
|
|||
ds.state = DRAG_SOURCE_NONE;
|
||||
ds.pre_sent_total_sz = 0;
|
||||
ds.images_sent_total_sz = 0;
|
||||
if (ds.file_promises) {
|
||||
for (size_t i = 0; i < ds.file_promises_count; i++) {
|
||||
drag_free_remote_item(&ds.file_promises[i].ri);
|
||||
}
|
||||
free(ds.file_promises);
|
||||
ds.file_promises = NULL;
|
||||
}
|
||||
ds.file_promises_count = 0; ds.file_promises_capacity = 0;
|
||||
if (remove_remote_items) delete_basedir_for_remote_items(w);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1305,7 +1320,7 @@ static void
|
|||
cancel_drag(Window *w, int error_code, const char *details) {
|
||||
if (error_code) drag_send_error(w, error_code, details);
|
||||
if (global_state.drag_source.is_active && global_state.drag_source.from_window == w->id) cancel_current_drag_source();
|
||||
drag_free_offer(w);
|
||||
if (error_code) drag_free_offer(w, true);
|
||||
}
|
||||
|
||||
#define abrt(code, details) { cancel_drag(w, code, details); return; }
|
||||
|
|
@ -1318,7 +1333,7 @@ drag_start_offerring(Window *w, const char *client_machine_id, size_t sz) {
|
|||
|
||||
void
|
||||
drag_stop_offerring(Window *w) {
|
||||
drag_free_offer(w);
|
||||
drag_free_offer(w, true);
|
||||
ds.can_offer = false; ds.is_remote_client = false;
|
||||
}
|
||||
|
||||
|
|
@ -1452,9 +1467,60 @@ expand_png_data(Window *w, size_t idx) {
|
|||
|
||||
static size_t last_total_image_size = 0;
|
||||
|
||||
static char**
|
||||
parse_uri_list(Window *w, char *data, const ssize_t sz, size_t *num_uris_out) {
|
||||
*num_uris_out = 0;
|
||||
// First pass: count non-comment, non-empty lines
|
||||
size_t count = 0;
|
||||
char *p = data;
|
||||
while (p - data < sz) {
|
||||
char *eol = p + strcspn(p, "\r\n");
|
||||
char saved = *eol; *eol = '\0';
|
||||
char *end = eol;
|
||||
while (end > p && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
||||
char saved_end = *end; *end = '\0';
|
||||
if (*p && *p != '#') count++;
|
||||
*end = saved_end;
|
||||
*eol = saved;
|
||||
if (saved == '\0') break;
|
||||
p = eol + 1;
|
||||
while (*p == '\r' || *p == '\n') p++;
|
||||
}
|
||||
|
||||
char **result = calloc((count + 1), sizeof(const char*));
|
||||
if (!result) { cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL; }
|
||||
|
||||
// Second pass: fill in decoded URI strings
|
||||
size_t idx = 0;
|
||||
p = data;
|
||||
while (p - data < sz && idx < count) {
|
||||
char *eol = p + strcspn(p, "\r\n");
|
||||
char saved = *eol; *eol = '\0';
|
||||
char *end = eol;
|
||||
while (end > p && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
||||
*end = '\0';
|
||||
if (*p && *p != '#') {
|
||||
char *decoded = strdup(p);
|
||||
if (!decoded) {
|
||||
for (size_t k = 0; k < idx; k++) free((char*)result[k]);
|
||||
free(result); cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL;
|
||||
}
|
||||
result[idx++] = decoded;
|
||||
}
|
||||
*eol = saved;
|
||||
if (saved == '\0') break;
|
||||
p = eol + 1;
|
||||
while (*p == '\r' || *p == '\n') p++;
|
||||
}
|
||||
*num_uris_out = idx;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
drag_start(Window *w) {
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL, "cannot start drag as drag source is not being built");
|
||||
delete_basedir_for_remote_items(w);
|
||||
size_t total_size = 0;
|
||||
for (size_t idx = 0; idx < arraysz(ds.images); idx++) {
|
||||
if (img.sz) {
|
||||
|
|
@ -1530,6 +1596,13 @@ drag_start(Window *w) {
|
|||
// Free images and optional_data but keep the items array for later
|
||||
// data requests from the drop target
|
||||
for (size_t i = 0; i < ds.num_mimes; i++) {
|
||||
if (ds.is_remote_client && ds.items[i].is_uri_list) {
|
||||
if (ds.items[i].optional_data && ds.items[i].data_size) {
|
||||
ds.items[i].uri_list = parse_uri_list(
|
||||
w, (char*)ds.items[i].optional_data, ds.items[i].data_size, &ds.items[i].num_uris);
|
||||
if (!ds.items[i].uri_list) return;
|
||||
} else abrt(EINVAL, "remote client must pre-send text/uri-list data");
|
||||
}
|
||||
free(ds.items[i].optional_data);
|
||||
ds.items[i].optional_data = NULL;
|
||||
ds.items[i].data_size = 0;
|
||||
|
|
@ -1571,7 +1644,7 @@ drag_notify(Window *w, DragNotifyType type) {
|
|||
sz += snprintf(buf + sz, sizeof(buf) - sz, ":y=%d", global_state.drag_source.was_canceled ? 1 : 0); break;
|
||||
}
|
||||
queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, sz, NULL, 0, false);
|
||||
if (type == DRAG_NOTIFY_FINISHED) drag_free_offer(w);
|
||||
if (type == DRAG_NOTIFY_FINISHED) drag_free_offer(w, false);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -1581,10 +1654,96 @@ drag_free_data(Window *w, const char *mime_type, const char* data, size_t sz) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_file_url(const char *url) { return startwith_literal(url, "file://"); }
|
||||
|
||||
static void
|
||||
request_remote_file(Window *w, DragRemoteItem *ri, const char *url, size_t idx_in_uri_list) {
|
||||
char buf[128];
|
||||
int header_sz = snprintf(buf, sizeof(buf), "\x1b]%d;t=k:x=%zu", DND_CODE, idx_in_uri_list + 1);
|
||||
ri->dir_entry_name = sanitized_filename_from_url(url);
|
||||
if (ri->dir_entry_name) {
|
||||
queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false);
|
||||
ri->waiting_for_completion = true;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
notify_drag_data_received(Window *w, size_t uri_item_idx, const char *basename, int type) {
|
||||
char mime_type[128], path[4096];
|
||||
snprintf(mime_type, sizeof(mime_type), "kitty-internal/uri-list-item-%zu", uri_item_idx);
|
||||
if (!basename || !basename[0]) notify_drag_data_ready(global_state.drag_source.from_os_window, mime_type, NULL, 0, type);
|
||||
int sz = snprintf(path, sizeof(path), "%s/%zu/%s", ds.base_dir_for_remote_items, uri_item_idx, basename);
|
||||
return notify_drag_data_ready(global_state.drag_source.from_os_window, mime_type, path, sz, type);
|
||||
}
|
||||
|
||||
static const char*
|
||||
request_file_promise(Window *w, size_t idx_in_uri_list, const char *url, int *err_code) {
|
||||
for (size_t i = 0; i < w->drag_source.file_promises_count; i++) {
|
||||
if (w->drag_source.file_promises[i].uri_item_idx == idx_in_uri_list) {
|
||||
const DragRemoteItem *ri = &w->drag_source.file_promises[i].ri;
|
||||
if (ri->completed) {
|
||||
notify_drag_data_received(w, idx_in_uri_list, ri->dir_entry_name, ri->type);
|
||||
*err_code = 0;
|
||||
}
|
||||
*err_code = EAGAIN;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (w->drag_source.file_promises_count + 1 >= w->drag_source.file_promises_capacity) {
|
||||
size_t cap = MAX(w->drag_source.file_promises_count + 1, MAX(16u, 2u * w->drag_source.file_promises_capacity));
|
||||
void *p = realloc(w->drag_source.file_promises, sizeof(w->drag_source.file_promises[0]) * cap);
|
||||
if (!p) { *err_code = ENOMEM; return NULL; }
|
||||
w->drag_source.file_promises = p;
|
||||
w->drag_source.file_promises_capacity = cap;
|
||||
}
|
||||
char *fname = sanitized_filename_from_url(url);
|
||||
if (!fname) { *err_code = EINVAL; return NULL; }
|
||||
w->drag_source.file_promises[w->drag_source.file_promises_count].uri_item_idx = idx_in_uri_list;
|
||||
w->drag_source.file_promises[w->drag_source.file_promises_count++].ri = (DragRemoteItem){.dir_entry_name = fname};
|
||||
|
||||
*err_code = EAGAIN;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
request_remote_files(Window *w, size_t i) {
|
||||
#define mi ds.items[i]
|
||||
mi.remote_items = calloc(mi.num_uris, sizeof(mi.remote_items[0]));
|
||||
if (!mi.remote_items) return false;
|
||||
mi.num_remote_items = mi.num_uris;
|
||||
for (size_t k = 0; k < mi.num_remote_items; k++) {
|
||||
if (is_file_url(mi.uri_list[k])) {
|
||||
request_remote_file(w, mi.remote_items + k, mi.uri_list[k], k);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#undef mi
|
||||
}
|
||||
|
||||
const char*
|
||||
drag_get_file_promise_data(Window *w, size_t idx_in_uri_list, size_t *sz, int *err_code) {
|
||||
*err_code = ENOENT; *sz = 0;
|
||||
for (size_t i = 0; i < ds.num_mimes; i++) {
|
||||
if (strcmp(ds.items[i].mime_type, "text/uri-list") == 0) {
|
||||
if ((unsigned)idx_in_uri_list >= ds.items[i].num_uris) break;
|
||||
const char *url = ds.items[i].uri_list[idx_in_uri_list];
|
||||
if (!is_file_url(url)) break;
|
||||
return request_file_promise(w, idx_in_uri_list, url, err_code);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char*
|
||||
drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) {
|
||||
*err_code = ENOENT; *sz = 0;
|
||||
if (!ds.items || ds.state < DRAG_SOURCE_STARTED) return NULL;
|
||||
if (startwith_literal(mime_type, "kitty-internal/uri-list-item-")) {
|
||||
// request for a single file:// URL from text/uri-list used by macOS backend file promise providers
|
||||
int idx_in_uri_list = atoi(mime_type + sizeof("kitty-internal/uri-list-item-")-1);
|
||||
return drag_get_file_promise_data(w, idx_in_uri_list, sz, err_code);
|
||||
}
|
||||
for (size_t i = 0; i < ds.num_mimes; i++) {
|
||||
if (strcmp(ds.items[i].mime_type, mime_type) == 0) {
|
||||
if (ds.items[i].fd_plus_one < 0) {
|
||||
|
|
@ -1636,12 +1795,17 @@ drag_get_data(Window *w, const char *mime_type, size_t *sz, int *err_code) {
|
|||
}
|
||||
// No fd yet, request data from the client
|
||||
if (!ds.items[i].data_requested_from_client) {
|
||||
char buf[128];
|
||||
ds.items[i].requested_remote_files = ds.is_remote_client && ds.items[i].is_uri_list;
|
||||
int header_sz = snprintf(buf, sizeof(buf), "\x1b]%d;t=e:x=%d:y=%zu:Y=%d",
|
||||
DND_CODE, DRAG_NOTIFY_FINISHED + 2, i, ds.items[i].requested_remote_files);
|
||||
queue_payload_to_child(w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false);
|
||||
ds.items[i].data_requested_from_client = true;
|
||||
ds.items[i].requested_remote_files = ds.is_remote_client && ds.items[i].is_uri_list;
|
||||
if (ds.items[i].requested_remote_files) {
|
||||
if (!request_remote_files(w, i)) { *err_code = ENOMEM; return NULL; }
|
||||
} else {
|
||||
char buf[128];
|
||||
int header_sz = snprintf(buf, sizeof(buf),
|
||||
"\x1b]%d;t=e:x=%d:y=%zu", DND_CODE, DRAG_NOTIFY_FINISHED + 2, i);
|
||||
queue_payload_to_child(
|
||||
w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_sz, NULL, 0, false);
|
||||
}
|
||||
}
|
||||
*err_code = EAGAIN;
|
||||
return NULL;
|
||||
|
|
@ -1676,6 +1840,12 @@ open_item_tmpfile(void) {
|
|||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
notify_drag_data_ready_to_read(const char *mime_type) {
|
||||
return notify_drag_data_ready(global_state.drag_source.from_os_window, mime_type, NULL, 0, -1);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *payload, size_t payload_sz) {
|
||||
if ((ds.state < DRAG_SOURCE_STARTED) || idx >= ds.num_mimes || !ds.items) {
|
||||
|
|
@ -1692,7 +1862,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
ds.items[idx].fd_plus_one = -err;
|
||||
ds.items[idx].data_decode_initialized = false;
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
int ret = notify_drag_data_ready_to_read(ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
return;
|
||||
|
|
@ -1704,7 +1874,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
if (ds.items[idx].fd_plus_one > 0) {
|
||||
if (!ds.items[idx].requested_remote_files) {
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
int ret = notify_drag_data_ready_to_read(ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
}
|
||||
|
|
@ -1745,80 +1915,13 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
// Notify as soon as any data is available
|
||||
if (!ds.items[idx].requested_remote_files) {
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
int ret = notify_drag_data_ready_to_read(ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char**
|
||||
parse_uri_list(Window *w, int fd, size_t *num_uris_out) {
|
||||
*num_uris_out = 0;
|
||||
// Determine file size and read all data
|
||||
off_t file_size = lseek(fd, 0, SEEK_END);
|
||||
if (file_size < 0) { cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL; }
|
||||
if (lseek(fd, 0, SEEK_SET) < 0) { cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL; }
|
||||
RAII_ALLOC(char, buf, malloc((size_t)file_size + 1));
|
||||
if (!buf) { cancel_drag(w, ENOMEM, "out of memory processing uri list data"); return NULL; }
|
||||
size_t total = 0;
|
||||
while (total < (size_t)file_size) {
|
||||
ssize_t n = read(fd, buf + total, (size_t)file_size - total);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL;
|
||||
}
|
||||
if (n == 0) break;
|
||||
total += (size_t)n;
|
||||
}
|
||||
buf[total] = '\0';
|
||||
|
||||
// First pass: count non-comment, non-empty lines
|
||||
size_t count = 0;
|
||||
char *p = buf;
|
||||
while (*p) {
|
||||
char *eol = p + strcspn(p, "\r\n");
|
||||
char saved = *eol; *eol = '\0';
|
||||
char *end = eol;
|
||||
while (end > p && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
||||
char saved_end = *end; *end = '\0';
|
||||
if (*p && *p != '#') count++;
|
||||
*end = saved_end;
|
||||
*eol = saved;
|
||||
if (saved == '\0') break;
|
||||
p = eol + 1;
|
||||
while (*p == '\r' || *p == '\n') p++;
|
||||
}
|
||||
|
||||
char **result = calloc((count + 1), sizeof(const char*));
|
||||
if (!result) { cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL; }
|
||||
|
||||
// Second pass: fill in decoded URI strings
|
||||
size_t idx = 0;
|
||||
p = buf;
|
||||
while (*p && idx < count) {
|
||||
char *eol = p + strcspn(p, "\r\n");
|
||||
char saved = *eol; *eol = '\0';
|
||||
char *end = eol;
|
||||
while (end > p && (end[-1] == ' ' || end[-1] == '\t')) end--;
|
||||
*end = '\0';
|
||||
if (*p && *p != '#') {
|
||||
char *decoded = strdup(p);
|
||||
if (!decoded) {
|
||||
for (size_t k = 0; k < idx; k++) free((char*)result[k]);
|
||||
free(result); cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL;
|
||||
}
|
||||
result[idx++] = decoded;
|
||||
}
|
||||
*eol = saved;
|
||||
if (saved == '\0') break;
|
||||
p = eol + 1;
|
||||
while (*p == '\r' || *p == '\n') p++;
|
||||
}
|
||||
*num_uris_out = idx;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
write_all(int fd, const void *buf, size_t sz) {
|
||||
size_t pos = 0; const char *p = buf;
|
||||
|
|
@ -1832,6 +1935,11 @@ write_all(int fd, const void *buf, size_t sz) {
|
|||
|
||||
static void
|
||||
finish_remote_data(Window *w, size_t item_idx) {
|
||||
if (!ds.items[item_idx].fd_plus_one) {
|
||||
int fd = open_item_tmpfile();
|
||||
if (fd < 0) abrt(EIO, "failed to open temp file to store modified uri list");
|
||||
ds.items[item_idx].fd_plus_one = fd + 1;
|
||||
}
|
||||
const int fd = ds.items[item_idx].fd_plus_one - 1;
|
||||
ds.items[item_idx].requested_remote_files = false;
|
||||
if (safe_ftruncate(fd, 0) != 0) abrt(errno, "error updating uri list after all remote data received");
|
||||
|
|
@ -1850,7 +1958,7 @@ finish_remote_data(Window *w, size_t item_idx) {
|
|||
// fields so drag_get_data returns the full new content starting from the beginning.
|
||||
ds.items[item_idx].data_capacity = new_size;
|
||||
ds.items[item_idx].data_size = 0;
|
||||
int ret = dnd_is_test_mode() ? 0 : notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[item_idx].mime_type);
|
||||
int ret = dnd_is_test_mode() ? 0 : notify_drag_data_ready_to_read(ds.items[item_idx].mime_type);
|
||||
if (ret) abrt(ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
|
||||
|
|
@ -1919,10 +2027,12 @@ populate_dir_entries(Window *w, DragRemoteItem *ri) {
|
|||
while (ptr < end) {
|
||||
const char *p = memchr(ptr, 0, (size_t)(end - ptr));
|
||||
size_t len = p ? (size_t)(p - ptr) : (size_t)(end - ptr);
|
||||
DragRemoteItem *child = ri->children + ri->children_sz++;
|
||||
child->parent = ri;
|
||||
if (len > 0) {
|
||||
char *name = strndup(ptr, len);
|
||||
if (!name) abrt(ENOMEM, "out of memory processing drag source item directory entries");
|
||||
ri->children[ri->children_sz++].dir_entry_name = name;
|
||||
child->dir_entry_name = name;
|
||||
}
|
||||
ptr = p ? p + 1 : end;
|
||||
}
|
||||
|
|
@ -1991,25 +2101,25 @@ add_payload(Window *w, DragRemoteItem *ri, bool has_more, const uint8_t *payload
|
|||
|
||||
}
|
||||
|
||||
static int
|
||||
ensure_base_dir_for_drag(Window *w) {
|
||||
if (!ds.base_dir_for_remote_items) {
|
||||
int fd;
|
||||
ds.base_dir_for_remote_items = mktempdir_in_cache("dnd-drag-", &fd);
|
||||
if (!ds.base_dir_for_remote_items) return errno;
|
||||
ds.base_dir_fd_plus_one = fd + 1;
|
||||
detect_tempdir_case_sensitivity(ds.base_dir_for_remote_items);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
toplevel_data_for_drag(
|
||||
Window *w, unsigned mime_item_idx, unsigned uri_item_idx, unsigned item_type,
|
||||
Window *w, DragRemoteItem *ri, unsigned mime_item_idx, unsigned uri_item_idx, unsigned item_type,
|
||||
bool has_more, const uint8_t *payload, size_t payload_sz
|
||||
) {
|
||||
if (!mi.remote_items) {
|
||||
mi.remote_items = calloc(mi.num_uris, sizeof(mi.remote_items[0]));
|
||||
if (!mi.remote_items) abrt(ENOMEM, "out of memory processing drag source item");
|
||||
mi.num_remote_items = mi.num_uris;
|
||||
}
|
||||
if (!mi.base_dir_for_remote_items) {
|
||||
int fd;
|
||||
mi.base_dir_for_remote_items = mktempdir_in_cache("dnd-drag-", &fd);
|
||||
if (!mi.base_dir_for_remote_items) abrt(errno, "failed to create temporary directory for drag source items");
|
||||
mi.base_dir_fd_plus_one = fd + 1;
|
||||
detect_tempdir_case_sensitivity(mi.base_dir_for_remote_items);
|
||||
}
|
||||
if (uri_item_idx >= mi.num_remote_items) abrt(EINVAL, "out of bounds uri list item index for drag source");
|
||||
DragRemoteItem *ri = mi.remote_items + uri_item_idx;
|
||||
int errcode;
|
||||
if ((errcode = ensure_base_dir_for_drag(w)) != 0) abrt(errcode, "failed to create temporary directory for drag source items");
|
||||
if (!ri->started) {
|
||||
ri->started = true;
|
||||
ri->type = item_type;
|
||||
|
|
@ -2021,12 +2131,14 @@ toplevel_data_for_drag(
|
|||
ri->dir_entry_name = fname;
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "%u", uri_item_idx);
|
||||
if (mkdirat(mi.base_dir_fd_plus_one - 1, path, dir_permissions) != 0 && errno != EEXIST) abrt(errno, "failed to create directory for drag source item");
|
||||
int fd = safe_openat(mi.base_dir_fd_plus_one - 1, path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (mkdirat(ds.base_dir_fd_plus_one - 1, path, dir_permissions) != 0 && errno != EEXIST) abrt(errno, "failed to create directory for drag source item");
|
||||
int fd = safe_openat(ds.base_dir_fd_plus_one - 1, path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (fd < 0) abrt(errno, "failed to create directory for drag source item");
|
||||
ri->top_level_parent_dir_fd_plus_one = fd + 1;
|
||||
free(mi.uri_list[uri_item_idx]);
|
||||
mi.uri_list[uri_item_idx] = as_file_url(mi.base_dir_for_remote_items, path, ri->dir_entry_name);
|
||||
if (!ds.file_promises) {
|
||||
free(mi.uri_list[uri_item_idx]);
|
||||
mi.uri_list[uri_item_idx] = as_file_url(ds.base_dir_for_remote_items, path, ri->dir_entry_name);
|
||||
}
|
||||
}
|
||||
add_payload(w, ri, has_more, payload, payload_sz, ri->top_level_parent_dir_fd_plus_one - 1);
|
||||
}
|
||||
|
|
@ -2050,10 +2162,10 @@ find_by_handle(DragRemoteItem *parent, int handle, char *path_to_parent, size_t
|
|||
static void
|
||||
subdir_data_for_drag(
|
||||
Window *w, unsigned mime_item_idx, unsigned uri_item_idx, int handle, unsigned entry_num, unsigned item_type,
|
||||
bool has_more, const uint8_t *payload, size_t payload_sz
|
||||
bool has_more, const uint8_t *payload, size_t payload_sz, DragRemoteItem **ri
|
||||
) {
|
||||
if (!mi.remote_items || uri_item_idx >= mi.num_remote_items) abrt(EINVAL, "drag source sub directory item uri list index out of range");
|
||||
DragRemoteItem *parent = NULL;
|
||||
DragRemoteItem *parent = NULL; *ri = NULL;
|
||||
if (mi.currently_open_subdir) {
|
||||
if (mi.currently_open_subdir->type == handle) parent = mi.currently_open_subdir;
|
||||
else {
|
||||
|
|
@ -2069,7 +2181,7 @@ subdir_data_for_drag(
|
|||
DragRemoteItem *root = mi.remote_items + uri_item_idx;
|
||||
if (!root->dir_entry_name) abrt(EINVAL, "drag source sub directory parent dir does not exist");
|
||||
size_t pos = snprintf(path, PATH_MAX, "%s/%u/%s",
|
||||
mi.base_dir_for_remote_items, uri_item_idx, root->dir_entry_name);
|
||||
ds.base_dir_for_remote_items, uri_item_idx, root->dir_entry_name);
|
||||
parent = find_by_handle(root, handle, path, &pos);
|
||||
if (!parent) abrt(EINVAL, "drag source sub directory parent dir handle does not exist");
|
||||
mi.currently_open_subdir = parent;
|
||||
|
|
@ -2080,15 +2192,14 @@ subdir_data_for_drag(
|
|||
}
|
||||
}
|
||||
if (entry_num >= parent->children_sz) abrt(EINVAL, "drag source sub diretory index out of bounds");
|
||||
DragRemoteItem *ri = parent->children + entry_num;
|
||||
if (!ri->started) {
|
||||
ri->started = true;
|
||||
ri->type = item_type;
|
||||
base64_init_stream_decoder(&ri->base64_state);
|
||||
*ri = parent->children + entry_num;
|
||||
if (!(*ri)->started) {
|
||||
(*ri)->started = true;
|
||||
(*ri)->type = item_type;
|
||||
base64_init_stream_decoder(&(*ri)->base64_state);
|
||||
}
|
||||
add_payload(w, ri, has_more, payload, payload_sz, parent->fd_plus_one - 1);
|
||||
add_payload(w, *ri, has_more, payload, payload_sz, parent->fd_plus_one - 1);
|
||||
}
|
||||
#undef mi
|
||||
|
||||
void
|
||||
drag_offer_start_to_child(Window *w, int32_t cell_x, int32_t cell_y, int32_t pixel_x, int32_t pixel_y) {
|
||||
|
|
@ -2099,29 +2210,89 @@ drag_offer_start_to_child(Window *w, int32_t cell_x, int32_t cell_y, int32_t pix
|
|||
w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_size, NULL, 0, false);
|
||||
}
|
||||
|
||||
static void
|
||||
finish_remote_data_if_all_items_received(Window *w, unsigned mime_item_idx) {
|
||||
for (size_t i = 0; i < mi.num_remote_items; i++) {
|
||||
if (mi.remote_items[i].waiting_for_completion && !mi.remote_items[i].completed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
finish_remote_data(w, mime_item_idx);
|
||||
}
|
||||
|
||||
static bool
|
||||
all_children_complete(DragRemoteItem *parent) {
|
||||
for (size_t i = 0; i < parent->children_sz; i++) {
|
||||
if (!parent->children[i].completed) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
finish_file_promise(Window *w, size_t uri_item_idx, DragRemoteItem *ri) {
|
||||
notify_drag_data_received(w, uri_item_idx, ri->dir_entry_name, ri->type);
|
||||
}
|
||||
|
||||
void
|
||||
drag_remote_file_data(
|
||||
Window *w, int32_t x, int32_t y, int32_t X, int32_t Y, bool has_more, const uint8_t *payload, size_t payload_sz
|
||||
) {
|
||||
size_t item_idx = ds.num_mimes;
|
||||
if (x < 1) abrt(EINVAL, "drag source remote item x index cannot be less than 1");
|
||||
size_t mime_item_idx = ds.num_mimes + 1;
|
||||
for (size_t i = 0; i < ds.num_mimes; i++) {
|
||||
if (ds.items[i].requested_remote_files) {
|
||||
item_idx = i; break;
|
||||
if ((ds.file_promises && ds.items[i].is_uri_list) || (!ds.file_promises && ds.items[i].is_uri_list)) {
|
||||
mime_item_idx = i; break;
|
||||
}
|
||||
}
|
||||
if (item_idx == ds.num_mimes || ds.items[item_idx].fd_plus_one == 0) abrt(EINVAL, "drag source remote file item index out of bounds");
|
||||
if (ds.items[item_idx].uri_list == NULL) {
|
||||
ds.items[item_idx].uri_list = parse_uri_list(w, ds.items[item_idx].fd_plus_one-1, &ds.items[item_idx].num_uris);
|
||||
if (!ds.items[item_idx].uri_list) return;
|
||||
if (mime_item_idx == ds.num_mimes + 1) abrt(EINVAL, ds.file_promises ? "drag source file promise no uri list present" : "drag source no remote data was requested");
|
||||
const bool all_data_received = !payload_sz && !has_more;
|
||||
DragRemoteItem *ri;
|
||||
const unsigned uri_item_idx = x - 1;
|
||||
if (ds.file_promises) {
|
||||
size_t promise_item_idx = ds.file_promises_count + 1;
|
||||
for (size_t i = 0; i < ds.file_promises_count; i++) {
|
||||
if (ds.file_promises[i].uri_item_idx == uri_item_idx) {
|
||||
promise_item_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (promise_item_idx == ds.file_promises_count + 1) abrt(EINVAL, "drag source file promise uri list index out of bounds");
|
||||
ri = &ds.file_promises[promise_item_idx].ri;
|
||||
notify_drag_data_received(w, uri_item_idx, "", ri->type);
|
||||
} else {
|
||||
if (uri_item_idx >= mi.num_remote_items) abrt(EINVAL, "drag source uri list index out of bounds");
|
||||
ri = mi.remote_items + uri_item_idx;
|
||||
}
|
||||
if (!Y) {
|
||||
toplevel_data_for_drag(w, ri, mime_item_idx, uri_item_idx, X, has_more, payload, payload_sz);
|
||||
if (all_data_received && all_children_complete(ri)) {
|
||||
ri->completed = true;
|
||||
if (ds.file_promises) finish_file_promise(w, uri_item_idx, ri);
|
||||
else if (ri->waiting_for_completion) finish_remote_data_if_all_items_received(w, mime_item_idx);
|
||||
}
|
||||
} else {
|
||||
if (y < 1) abrt(EINVAL, "drag source remote item y index cannot be less than 1");
|
||||
subdir_data_for_drag(w, mime_item_idx, uri_item_idx, Y, y - 1, X, has_more, payload, payload_sz, &ri);
|
||||
if (all_data_received && ri && all_children_complete(ri)) {
|
||||
ri->completed = true;
|
||||
while (1) {
|
||||
if (ri->parent) {
|
||||
ri = ri->parent;
|
||||
if (all_children_complete(ri)) ri->completed = true;
|
||||
else break;
|
||||
} else {
|
||||
if (ds.file_promises) finish_file_promise(w, uri_item_idx, ri);
|
||||
else finish_remote_data_if_all_items_received(w, mime_item_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (X < 0) abrt(EINVAL, "drag source remote item X cannot be negative");
|
||||
if (!x && !y && !Y) { finish_remote_data(w, item_idx); return; }
|
||||
if (!Y) toplevel_data_for_drag(w, item_idx, x - 1, X, has_more, payload, payload_sz);
|
||||
else subdir_data_for_drag(w, item_idx, x - 1, Y, y - 1, X, has_more, payload, payload_sz);
|
||||
}
|
||||
#undef img
|
||||
#undef abrt
|
||||
#undef ds
|
||||
#undef mi
|
||||
// }}}
|
||||
|
||||
// DnD testing infrastructure {{{
|
||||
|
|
@ -2139,7 +2310,7 @@ static void
|
|||
destroy_fake_window_contents(Window *w) {
|
||||
// Free window resources without touching GPU objects (none allocated for fake windows).
|
||||
drop_free_data(w);
|
||||
drag_free_offer(w);
|
||||
drag_free_offer(w, false);
|
||||
free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks);
|
||||
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
||||
Py_CLEAR(w->render_data.screen);
|
||||
|
|
@ -2294,26 +2465,33 @@ dnd_test_force_drag_dropped(PyObject *self UNUSED, PyObject *args) {
|
|||
Window *w = window_for_window_id((id_type)window_id);
|
||||
if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; }
|
||||
// Simulate what drag_start does on success, without calling start_window_drag
|
||||
#define ds w->drag_source
|
||||
for (size_t i = 0; i < w->drag_source.num_mimes; i++) {
|
||||
free(w->drag_source.items[i].optional_data);
|
||||
w->drag_source.items[i].optional_data = NULL;
|
||||
w->drag_source.items[i].data_size = 0;
|
||||
w->drag_source.items[i].data_capacity = 0;
|
||||
w->drag_source.items[i].data_decode_initialized = false;
|
||||
if (ds.is_remote_client && ds.items[i].is_uri_list) {
|
||||
if (ds.items[i].optional_data && ds.items[i].data_size) {
|
||||
ds.items[i].uri_list = parse_uri_list(
|
||||
w, (char*)ds.items[i].optional_data, ds.items[i].data_size, &ds.items[i].num_uris);
|
||||
}
|
||||
}
|
||||
free(ds.items[i].optional_data);
|
||||
ds.items[i].optional_data = NULL;
|
||||
ds.items[i].data_size = 0;
|
||||
ds.items[i].data_capacity = 0;
|
||||
ds.items[i].data_decode_initialized = false;
|
||||
}
|
||||
for (size_t i = 0; i < arraysz(w->drag_source.images); i++) {
|
||||
if (w->drag_source.images[i].data) free(w->drag_source.images[i].data);
|
||||
zero_at_ptr(w->drag_source.images + i);
|
||||
if (ds.images[i].data) free(w->drag_source.images[i].data);
|
||||
zero_at_ptr(ds.images + i);
|
||||
}
|
||||
w->drag_source.state = DRAG_SOURCE_DROPPED;
|
||||
ds.state = DRAG_SOURCE_DROPPED;
|
||||
#undef ds
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
dnd_test_request_drag_data(PyObject *self UNUSED, PyObject *args) {
|
||||
// Simulate what drag_get_data does initially: find the MIME item at the
|
||||
// given index, set requested_remote_files if appropriate, and return the
|
||||
// escape code that would be sent to the client.
|
||||
// given index, set requested_remote_files if appropriate.
|
||||
unsigned long long window_id;
|
||||
unsigned idx;
|
||||
if (!PyArg_ParseTuple(args, "KI", &window_id, &idx)) return NULL;
|
||||
|
|
@ -2323,6 +2501,7 @@ dnd_test_request_drag_data(PyObject *self UNUSED, PyObject *args) {
|
|||
PyErr_SetString(PyExc_ValueError, "Invalid state or index"); return NULL;
|
||||
}
|
||||
w->drag_source.items[idx].requested_remote_files = w->drag_source.is_remote_client && w->drag_source.items[idx].is_uri_list;
|
||||
if (w->drag_source.items[idx].requested_remote_files) request_remote_files(w, idx);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
@ -2420,6 +2599,19 @@ dnd_test_probe_state(PyObject *self UNUSED, PyObject *args) {
|
|||
if (strcmp(q, "drag_thumbnail_size") == 0) {
|
||||
return PyLong_FromSize_t(last_total_image_size);
|
||||
}
|
||||
if (strcmp(q, "drag_remote_data_complete") == 0) {
|
||||
for (size_t idx = 0; idx < w->drag_source.num_mimes; idx++) {
|
||||
#define mi w->drag_source.items[idx]
|
||||
if (mi.is_uri_list && mi.requested_remote_files) {
|
||||
for (size_t i = 0; i < mi.num_remote_items; i++) {
|
||||
if (mi.remote_items[i].waiting_for_completion && !mi.remote_items[i].completed)
|
||||
return PyUnicode_FromString(mi.remote_items[i].dir_entry_name ? mi.remote_items[i].dir_entry_name : "");
|
||||
}
|
||||
}
|
||||
#undef mi
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ void drop_dispatch_data(Window *w, const char *mime_type, const char *data, ssiz
|
|||
void drop_finish(Window *w);
|
||||
|
||||
typedef enum { DRAG_NOTIFY_ACCEPTED, DRAG_NOTIFY_ACTION_CHANGED, DRAG_NOTIFY_DROPPED, DRAG_NOTIFY_FINISHED } DragNotifyType;
|
||||
void drag_free_offer(Window *w);
|
||||
void drag_free_offer(Window *w, bool remove_remote_items);
|
||||
void drag_add_mimes(Window *w, int allowed_operations, uint32_t client_id, const char *data, size_t sz, bool has_more);
|
||||
void drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t sz);
|
||||
void drag_add_image(Window *w, unsigned idx_, int fmt, int width, int height, int opacity, const uint8_t *payload, size_t sz);
|
||||
|
|
|
|||
|
|
@ -423,6 +423,7 @@ class FontConfigPattern(TypedDict):
|
|||
scalable: bool
|
||||
outline: bool
|
||||
color: bool
|
||||
matrix: tuple[float, float, float, float]
|
||||
variable: bool
|
||||
named_instance: bool
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ static struct {PyObject *face, *descriptor;} builtin_nerd_font = {0};
|
|||
#define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger
|
||||
#define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate
|
||||
#define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool
|
||||
#define FcPatternGetMatrix dynamically_loaded_fc_symbol.PatternGetMatrix
|
||||
#define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet
|
||||
#define FcConfigAppFontAddFile dynamically_loaded_fc_symbol.ConfigAppFontAddFile
|
||||
|
||||
|
|
@ -66,6 +67,7 @@ static struct {
|
|||
FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i);
|
||||
FcPattern * (*PatternCreate) (void);
|
||||
FcResult (*PatternGetBool) (const FcPattern *p, const char *object, int n, FcBool *b);
|
||||
FcResult (*PatternGetMatrix) (const FcPattern *p, const char *object, int n, FcMatrix **m);
|
||||
FcBool (*PatternAddCharSet) (FcPattern *p, const char *object, const FcCharSet *c);
|
||||
FcBool (*ConfigAppFontAddFile) (FcConfig *config, const FcChar8 *file);
|
||||
} dynamically_loaded_fc_symbol = {0};
|
||||
|
|
@ -117,6 +119,7 @@ load_fontconfig_lib(void) {
|
|||
LOAD_FUNC(PatternAddInteger);
|
||||
LOAD_FUNC(PatternCreate);
|
||||
LOAD_FUNC(PatternGetBool);
|
||||
LOAD_FUNC(PatternGetMatrix);
|
||||
LOAD_FUNC(PatternAddCharSet);
|
||||
LOAD_FUNC(ConfigAppFontAddFile);
|
||||
}
|
||||
|
|
@ -210,6 +213,13 @@ pattern_as_dict(FcPattern *pat) {
|
|||
B(FC_OUTLINE, outline);
|
||||
B(FC_COLOR, color);
|
||||
E(FC_SPACING, spacing, pyspacing);
|
||||
{
|
||||
FcMatrix *mtx = NULL;
|
||||
if (FcPatternGetMatrix(pat, FC_MATRIX, 0, &mtx) == FcResultMatch && mtx) {
|
||||
RAII_PyObject(t, Py_BuildValue("(dddd)", mtx->xx, mtx->xy, mtx->yx, mtx->yy));
|
||||
if (!t || PyDict_SetItemString(ans, "matrix", t) != 0) return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ typedef struct {
|
|||
PyObject *name_lookup_table;
|
||||
FontFeatures font_features;
|
||||
unsigned short dark_palette_index, light_palette_index, palettes_scanned;
|
||||
FT_Matrix matrix;
|
||||
bool has_matrix;
|
||||
} Face;
|
||||
PyTypeObject Face_Type;
|
||||
|
||||
|
|
@ -284,6 +286,29 @@ set_load_error(const char *path, int error) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
read_matrix_from_descriptor(PyObject *descriptor, FT_Matrix *out, bool *out_has) {
|
||||
*out_has = false;
|
||||
PyObject *mt = PyDict_GetItemString(descriptor, "matrix");
|
||||
if (!mt || !PyTuple_Check(mt) || PyTuple_GET_SIZE(mt) != 4) return true;
|
||||
double v[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
v[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(mt, i));
|
||||
if (PyErr_Occurred()) return false;
|
||||
if (!isfinite(v[i])) {
|
||||
PyErr_SetString(PyExc_ValueError, "matrix contains non-finite value");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (v[0] == 1.0 && v[1] == 0.0 && v[2] == 0.0 && v[3] == 1.0) return true;
|
||||
out->xx = (FT_Fixed)(v[0] * 0x10000);
|
||||
out->xy = (FT_Fixed)(v[1] * 0x10000);
|
||||
out->yx = (FT_Fixed)(v[2] * 0x10000);
|
||||
out->yy = (FT_Fixed)(v[3] * 0x10000);
|
||||
*out_has = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
||||
Face *face = (Face*)face_;
|
||||
|
|
@ -292,6 +317,13 @@ face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
|
|||
if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false;
|
||||
t = PyDict_GetItemString(descriptor, "index");
|
||||
if (t && PyLong_AsLong(t) != face->face->face_index) return false;
|
||||
FT_Matrix dmat = {0};
|
||||
bool d_has = false;
|
||||
if (!read_matrix_from_descriptor(descriptor, &dmat, &d_has)) { PyErr_Clear(); return false; }
|
||||
if (d_has != face->has_matrix) return false;
|
||||
if (d_has && (
|
||||
face->matrix.xx != dmat.xx || face->matrix.xy != dmat.xy ||
|
||||
face->matrix.yx != dmat.yx || face->matrix.yy != dmat.yy)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -339,6 +371,29 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
|||
if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error);
|
||||
}
|
||||
if (!create_features_for_face(postscript_name_for_face((PyObject*)self), PyDict_GetItemString(descriptor, "features"), &self->font_features)) return NULL;
|
||||
if (!read_matrix_from_descriptor(descriptor, &self->matrix, &self->has_matrix)) return NULL;
|
||||
if (self->has_matrix) {
|
||||
FT_Set_Transform(self->face, &self->matrix, NULL);
|
||||
if (self->harfbuzz_font) {
|
||||
#if HB_VERSION_ATLEAST(4,0,0)
|
||||
// Inform HarfBuzz so shaping (mark positioning, cluster
|
||||
// boundaries) matches the slanted rendering. The HB API
|
||||
// models a horizontal shear ratio, which equals xy/xx for
|
||||
// matrix [[xx,xy],[yx,yy]]. Both operands are FT_Fixed at
|
||||
// the same 16.16 scale so the factor cancels in the
|
||||
// division. Guard against xx==0 (degenerate transform).
|
||||
// Pure scales (s,0,0,s) yield slant=0, which is correct.
|
||||
// Rotations and shear+rotation composites produce a slant
|
||||
// value HB can't represent faithfully, but stock fontconfig
|
||||
// only ever emits horizontal shear here.
|
||||
if (self->matrix.xx != 0) {
|
||||
float slant = (float)self->matrix.xy / (float)self->matrix.xx;
|
||||
hb_font_set_synthetic_slant(self->harfbuzz_font, slant);
|
||||
}
|
||||
#endif
|
||||
hb_ft_font_changed(self->harfbuzz_font);
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_XINCREF(retval);
|
||||
return retval;
|
||||
|
|
|
|||
1
kitty/glfw-wrapper.h
generated
1
kitty/glfw-wrapper.h
generated
|
|
@ -1556,6 +1556,7 @@ typedef struct GLFWDragSourceItem {
|
|||
const char *optional_data;
|
||||
size_t data_size;
|
||||
bool is_remote_client;
|
||||
int type; // used for file promises type of entry 0 = regular, 1 = symlink, 2 = directory
|
||||
} GLFWDragSourceItem;
|
||||
|
||||
typedef struct GLFWDragEvent {
|
||||
|
|
|
|||
|
|
@ -998,10 +998,12 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) {
|
|||
#undef ds
|
||||
|
||||
int
|
||||
notify_drag_data_ready(id_type os_window_id, const char *mime_type) {
|
||||
notify_drag_data_ready(id_type os_window_id, const char *mime_type, const char *data, size_t data_sz, int type) {
|
||||
OSWindow *w = os_window_for_id(os_window_id);
|
||||
GLFWDragSourceItem item = {.mime_type = mime_type};
|
||||
if (w && w->handle) return glfwStartDrag(w->handle, &item, 1, NULL, -1, false);
|
||||
if (w && w->handle) {
|
||||
GLFWDragSourceItem item = {.mime_type = mime_type, .optional_data = data, .data_size = data_sz, .type = type};
|
||||
return glfwStartDrag(w->handle, &item, 1, NULL, -1, false);
|
||||
}
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1573,7 +1573,7 @@ screen_handle_dnd_command(Screen *self, const DnDCommand *cmd_, const uint8_t *p
|
|||
} break;
|
||||
case 'E': {
|
||||
if (cmd->cell_y == -1) {
|
||||
drag_free_offer(w);
|
||||
drag_free_offer(w, true);
|
||||
if (global_state.drag_source.is_active && global_state.drag_source.from_window == w->id) {
|
||||
cancel_current_drag_source();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1537,14 +1537,15 @@ static void
|
|||
draw_bg_image(OSWindow *os_window, Tab *tab) {
|
||||
BackgroundImage *bg = background_image_for_os_window(os_window);
|
||||
if (!bg) return;
|
||||
BackgroundImageLayout layout = os_window->background_image.has_layout ? os_window->background_image.layout : OPT(background_image_layout);
|
||||
BackgroundImageRenderSettings s = {
|
||||
.os_window.width = os_window->viewport_width, .os_window.height = os_window->viewport_height,
|
||||
.instance_id = bg->id, .layout=OPT(background_image_layout),
|
||||
.instance_id = bg->id, .layout=layout,
|
||||
.linear=OPT(background_image_linear), .bgcolor=OPT(background), .opacity=effective_os_window_alpha(os_window),
|
||||
};
|
||||
GLfloat iwidth = bg->width, iheight = bg->height;
|
||||
GLfloat vwidth = s.os_window.width, vheight = s.os_window.height;
|
||||
if (CENTER_SCALED == OPT(background_image_layout)) {
|
||||
if (CENTER_SCALED == layout) {
|
||||
GLfloat ifrac = iwidth / iheight;
|
||||
if (ifrac > (vwidth / vheight)) {
|
||||
iheight = vheight;
|
||||
|
|
@ -1556,7 +1557,7 @@ draw_bg_image(OSWindow *os_window, Tab *tab) {
|
|||
}
|
||||
GLfloat tiled = 0.f;;
|
||||
GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0;
|
||||
switch (OPT(background_image_layout)) {
|
||||
switch (layout) {
|
||||
case TILING: case MIRRORED: case CLAMPED:
|
||||
tiled = 1.f; break;
|
||||
case SCALED:
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ update_os_window_title(OSWindow *os_window) {
|
|||
static void
|
||||
destroy_window(Window *w) {
|
||||
drop_free_data(w);
|
||||
drag_free_offer(w);
|
||||
drag_free_offer(w, true);
|
||||
free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks);
|
||||
free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys);
|
||||
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
|
||||
|
|
@ -1440,6 +1440,8 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
|
|||
if (bgimage) {
|
||||
if (!configured) { // configured means we use the zero index global image
|
||||
os_window->background_image.override = bgimage;
|
||||
os_window->background_image.layout = layout;
|
||||
os_window->background_image.has_layout = true;
|
||||
bgimage->refcnt++;
|
||||
}
|
||||
} else if (is_increment) {
|
||||
|
|
|
|||
|
|
@ -253,7 +253,8 @@ typedef struct DragRemoteItem {
|
|||
size_t children_sz;
|
||||
char *dir_entry_name;
|
||||
base64_state base64_state;
|
||||
bool started;
|
||||
bool started, waiting_for_completion, completed;
|
||||
struct DragRemoteItem *parent;
|
||||
} DragRemoteItem;
|
||||
|
||||
typedef struct Window {
|
||||
|
|
@ -328,7 +329,6 @@ typedef struct Window {
|
|||
char** uri_list; size_t num_uris;
|
||||
DragRemoteItem *remote_items; size_t num_remote_items;
|
||||
DragRemoteItem *currently_open_subdir;
|
||||
char *base_dir_for_remote_items; int base_dir_fd_plus_one;
|
||||
} *items;
|
||||
struct {
|
||||
int width, height, fmt, opacity; uint8_t *data; size_t sz, capacity; bool started; base64_state base64_state;
|
||||
|
|
@ -339,6 +339,10 @@ typedef struct Window {
|
|||
DragSourceState state;
|
||||
PendingData pending;
|
||||
uint32_t client_id;
|
||||
char *base_dir_for_remote_items;
|
||||
int base_dir_fd_plus_one;
|
||||
struct { size_t uri_item_idx; DragRemoteItem ri; } *file_promises;
|
||||
size_t file_promises_count, file_promises_capacity;
|
||||
} drag_source;
|
||||
} Window;
|
||||
|
||||
|
|
@ -419,7 +423,9 @@ typedef struct OSWindow {
|
|||
struct {
|
||||
size_t global_bg_images_idx;
|
||||
BackgroundImage *override;
|
||||
BackgroundImageLayout layout;
|
||||
bool no_image;
|
||||
bool has_layout;
|
||||
} background_image;
|
||||
struct {
|
||||
uint32_t framebuffer_id, attached_texture_generation;
|
||||
|
|
@ -639,5 +645,5 @@ void request_drop_data(OSWindow *w, id_type wid, const char* mime);
|
|||
void cancel_current_drag_source(void);
|
||||
bool change_drag_image(int idx);
|
||||
int start_window_drag(Window *w, bool in_test_mode);
|
||||
int notify_drag_data_ready(id_type os_window_id, const char *mime_type);
|
||||
int notify_drag_data_ready(id_type os_window_id, const char *mime_type, const char *data, size_t data_sz, int type);
|
||||
BackgroundImage* background_image_for_os_window(OSWindow *w);
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ class PTY:
|
|||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
if self.log_data_flow:
|
||||
print('t -> c:', data)
|
||||
print('t -> c:', bytes(data))
|
||||
self.write_buf += data
|
||||
if flush:
|
||||
self.process_input_from_child(0)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from kitty.fast_data_types import (
|
|||
dnd_test_fake_drop_data,
|
||||
dnd_test_fake_drop_event,
|
||||
dnd_test_force_drag_dropped,
|
||||
dnd_test_probe_state,
|
||||
dnd_test_request_drag_data,
|
||||
dnd_test_set_mouse_pos,
|
||||
)
|
||||
|
|
@ -236,14 +237,6 @@ def client_remote_file(
|
|||
return _osc(meta)
|
||||
|
||||
|
||||
def client_remote_file_finish(client_id: int = 0) -> bytes:
|
||||
"""Escape code signaling completion of all remote file data (t=k with no keys)."""
|
||||
meta = f'{DND_CODE};t=k'
|
||||
if client_id:
|
||||
meta += f':i={client_id}'
|
||||
return _osc(meta)
|
||||
|
||||
|
||||
# ---- escape-code decoder used by assertions ---------------------------------
|
||||
|
||||
_OSC_RE = re.compile(
|
||||
|
|
@ -2340,18 +2333,19 @@ class TestDnDProtocol(BaseTest):
|
|||
# Register with a different machine_id to make is_remote_client=True
|
||||
parse_bytes(screen, _osc(f'{DND_CODE};t=o:x=1;different-machine-id'))
|
||||
parse_bytes(screen, client_drag_offer_mimes(operations, mimes, client_id=client_id))
|
||||
cap.consume()
|
||||
dnd_test_force_drag_dropped(cap.window_id)
|
||||
# Find the index of text/uri-list
|
||||
mime_list = mimes.split()
|
||||
uri_idx = mime_list.index('text/uri-list')
|
||||
dnd_test_request_drag_data(cap.window_id, uri_idx)
|
||||
# Send the uri-list data
|
||||
b64 = standard_b64encode(uri_list_data).decode()
|
||||
parse_bytes(screen, client_drag_send_data(uri_idx, b64, client_id=client_id))
|
||||
# End of data
|
||||
parse_bytes(screen, client_drag_send_data(uri_idx, '', client_id=client_id))
|
||||
parse_bytes(screen, client_drag_pre_send(uri_idx, b64, client_id=client_id))
|
||||
cap.consume()
|
||||
dnd_test_force_drag_dropped(cap.window_id)
|
||||
dnd_test_request_drag_data(cap.window_id, uri_idx)
|
||||
events = self._get_events(cap)
|
||||
expected = 0
|
||||
for line in uri_list_data.decode().splitlines():
|
||||
if line.startswith('file://'):
|
||||
expected += 1
|
||||
self.assertEqual(expected, len(events))
|
||||
|
||||
def test_remote_drag_single_file(self) -> None:
|
||||
"""Transfer a single regular file via t=k."""
|
||||
|
|
@ -2366,9 +2360,7 @@ class TestDnDProtocol(BaseTest):
|
|||
# End of data for this file
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=0))
|
||||
self._assert_no_output(cap)
|
||||
# Completion signal
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_single_symlink(self) -> None:
|
||||
"""Transfer a symlink via t=k with X=1."""
|
||||
|
|
@ -2383,9 +2375,7 @@ class TestDnDProtocol(BaseTest):
|
|||
# End of data
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=1))
|
||||
self._assert_no_output(cap)
|
||||
# Completion signal
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_single_directory(self) -> None:
|
||||
"""Transfer a directory with entries via t=k with X=handle (>1)."""
|
||||
|
|
@ -2422,10 +2412,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(
|
||||
1, '', item_type=0, parent_handle=2, entry_num=2))
|
||||
self._assert_no_output(cap)
|
||||
|
||||
# Completion signal
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_multiple_uris(self) -> None:
|
||||
"""Transfer multiple files from a URI list."""
|
||||
|
|
@ -2442,9 +2429,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(2, b64, item_type=0))
|
||||
parse_bytes(screen, client_remote_file(2, '', item_type=0))
|
||||
self._assert_no_output(cap)
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_chunked_file(self) -> None:
|
||||
"""File data can be sent in multiple chunks with m=1."""
|
||||
|
|
@ -2468,9 +2453,7 @@ class TestDnDProtocol(BaseTest):
|
|||
# End of data
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=0))
|
||||
self._assert_no_output(cap)
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_directory_with_symlink(self) -> None:
|
||||
"""Directory can contain symlinks (X=1 type for children)."""
|
||||
|
|
@ -2499,10 +2482,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(
|
||||
1, '', item_type=1, parent_handle=2, entry_num=2))
|
||||
self._assert_no_output(cap)
|
||||
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_deep_directory_tree_breadth_first(self) -> None:
|
||||
"""Transfer a 3-level deep directory tree in breadth-first order.
|
||||
|
|
@ -2572,10 +2552,7 @@ class TestDnDProtocol(BaseTest):
|
|||
1, b64, item_type=1, parent_handle=4, entry_num=2))
|
||||
parse_bytes(screen, client_remote_file(
|
||||
1, '', item_type=1, parent_handle=4, entry_num=2))
|
||||
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_deep_directory_tree_depth_first(self) -> None:
|
||||
"""Transfer a 3-level deep directory tree in depth-first order.
|
||||
|
|
@ -2645,8 +2622,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(
|
||||
1, '', item_type=1, parent_handle=4, entry_num=2))
|
||||
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self.assert_drag_data_complete(cap)
|
||||
self._assert_no_output(cap)
|
||||
|
||||
def test_remote_drag_completion_signal(self) -> None:
|
||||
|
|
@ -2657,8 +2633,7 @@ class TestDnDProtocol(BaseTest):
|
|||
b64 = standard_b64encode(b'data').decode()
|
||||
parse_bytes(screen, client_remote_file(1, b64, item_type=0))
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=0))
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self.assert_drag_data_complete(cap)
|
||||
self._assert_no_output(cap)
|
||||
|
||||
def test_remote_drag_invalid_uri_index(self) -> None:
|
||||
|
|
@ -2726,15 +2701,6 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(1, big_b64, item_type=0))
|
||||
self.assert_error(cap)
|
||||
|
||||
def test_remote_drag_negative_X_rejected(self) -> None:
|
||||
"""Sending t=k with X < 0 is rejected."""
|
||||
uri_list = b'file:///home/user/f.txt\r\n'
|
||||
with dnd_test_window() as (screen, cap):
|
||||
self._setup_remote_drag(screen, cap, uri_list)
|
||||
# Directly construct escape code with negative X
|
||||
parse_bytes(screen, _osc(f'{DND_CODE};t=k:x=1:X=-1'))
|
||||
self.assert_error(cap)
|
||||
|
||||
def test_remote_drag_without_remote_flag_fails(self) -> None:
|
||||
"""t=k fails if the drag is not from a remote client."""
|
||||
with dnd_test_window() as (screen, cap):
|
||||
|
|
@ -2780,7 +2746,7 @@ class TestDnDProtocol(BaseTest):
|
|||
def test_remote_drag_dos_present_data_cap_on_directory(self) -> None:
|
||||
"""Directory listing data exceeding PRESENT_DATA_CAP triggers EMFILE error."""
|
||||
uri_list = b'file:///home/user/dir\r\n'
|
||||
with dnd_test_window(present_data_cap=20) as (screen, cap):
|
||||
with dnd_test_window(present_data_cap=100) as (screen, cap):
|
||||
self._setup_remote_drag(screen, cap, uri_list)
|
||||
# Send a directory listing that will exceed the cap
|
||||
big_listing = b'\x00'.join([f'file{i}.txt'.encode() for i in range(100)])
|
||||
|
|
@ -2801,6 +2767,10 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(1, b64, item_type=0))
|
||||
self.assert_error(cap)
|
||||
|
||||
def assert_drag_data_complete(self, cap) -> None:
|
||||
first_incomplete_entry = dnd_test_probe_state(cap.window_id, 'drag_remote_data_complete')
|
||||
self.assertIsNone(first_incomplete_entry)
|
||||
|
||||
def test_remote_drag_three_level_tree_with_verification(self) -> None:
|
||||
"""Transfer a 3-level directory tree and verify no errors occur.
|
||||
|
||||
|
|
@ -2875,9 +2845,7 @@ class TestDnDProtocol(BaseTest):
|
|||
1, '', item_type=1, parent_handle=30, entry_num=2))
|
||||
|
||||
self._assert_no_output(cap)
|
||||
# Completion
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_process_item_data_basic(self) -> None:
|
||||
"""Basic drag_process_item_data: send data for a MIME type after DROPPED state."""
|
||||
|
|
@ -2954,8 +2922,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(3, '', item_type=1))
|
||||
|
||||
self._assert_no_output(cap)
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_empty_file(self) -> None:
|
||||
"""Transfer an empty file (end-of-data immediately after start)."""
|
||||
|
|
@ -2965,8 +2932,7 @@ class TestDnDProtocol(BaseTest):
|
|||
# Start file transfer, then immediately end (no data chunks)
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=0))
|
||||
self._assert_no_output(cap)
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_empty_directory(self) -> None:
|
||||
"""Transfer a directory with no entries."""
|
||||
|
|
@ -2977,8 +2943,7 @@ class TestDnDProtocol(BaseTest):
|
|||
b64 = standard_b64encode(b'').decode()
|
||||
parse_bytes(screen, client_remote_file(1, b64, item_type=2))
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=2))
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
def test_remote_drag_uri_list_with_comments(self) -> None:
|
||||
"""URI list with comment lines (starting with #) should filter them out."""
|
||||
|
|
@ -3027,9 +2992,7 @@ class TestDnDProtocol(BaseTest):
|
|||
parse_bytes(screen, client_remote_file(
|
||||
1, '', item_type=0, parent_handle=2, entry_num=2))
|
||||
self._assert_no_output(cap)
|
||||
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
self.assert_drag_data_complete(cap)
|
||||
|
||||
# ---- DoS limits tests ---------------------------------------------------
|
||||
|
||||
|
|
@ -3137,9 +3100,6 @@ class TestDnDProtocol(BaseTest):
|
|||
# End of data for this file
|
||||
parse_bytes(screen, client_remote_file(1, '', item_type=0))
|
||||
self._assert_no_output(cap)
|
||||
# Completion signal
|
||||
parse_bytes(screen, client_remote_file_finish())
|
||||
self._assert_no_output(cap)
|
||||
# No crash or leak - cleanup happens in context manager exit
|
||||
|
||||
# ---- query tests --------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import random
|
|||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
from base64 import standard_b64encode
|
||||
from functools import partial
|
||||
|
|
@ -60,9 +61,9 @@ def create_fs(base, include_toplevel_working_symlink=True):
|
|||
w(4096 * 3 + 113, 'some-image.png')
|
||||
w(0, 'd1', 'f1')
|
||||
w(0, 'd1', 'f2')
|
||||
w(0, 'd1', 'sd', 'f1')
|
||||
w(0, 'd1', 'sd', 'ssd', 'f1')
|
||||
os.symlink('../moose', join('d1', 'sd', 'ssd', 's1'))
|
||||
w(0, 'd1', 'sd', 'f11')
|
||||
w(0, 'd1', 'sd', 'ssd', 'f111')
|
||||
os.symlink('../moose', join('d1', 'sd', 'ssd', 's11'))
|
||||
|
||||
|
||||
class TestDnDKitten(BaseTest):
|
||||
|
|
@ -384,22 +385,24 @@ class TestDnDKitten(BaseTest):
|
|||
self.exit_kitten()
|
||||
self.img_drag_data = None
|
||||
|
||||
def read_drag_data(self, mime):
|
||||
def read_drag_data(self, mime, timeout=10):
|
||||
# self.pty.log_data_flow = True
|
||||
ans = b''
|
||||
while True:
|
||||
end_time = time.monotonic() + timeout
|
||||
while time.monotonic() <= end_time:
|
||||
try:
|
||||
chunk = dnd_test_drag_get_data(self.capture.window_id, mime)
|
||||
if not chunk:
|
||||
break
|
||||
return ans
|
||||
ans += chunk
|
||||
except OSError as err:
|
||||
if err.errno == errno.EAGAIN:
|
||||
self.pty.process_input_from_child()
|
||||
self.pty.process_input_from_child(timeout=end_time - time.monotonic())
|
||||
continue
|
||||
chunk = ans = b''
|
||||
raise
|
||||
return ans
|
||||
chunk = ans = b''
|
||||
raise TimeoutError(f'timed out waiting for data from drag_get_data for {mime}')
|
||||
|
||||
def dnd_kitten_drag(self, remote_client, img_drop_path):
|
||||
# self.pty.log_data_flow = True
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ func ShowHelpInPager(text string) {
|
|||
pager.Stdin = strings.NewReader(text)
|
||||
pager.Stdout = os.Stdout
|
||||
pager.Stderr = os.Stderr
|
||||
_ = pager.Run()
|
||||
if err := pager.Run(); err != nil {
|
||||
os.Stdout.WriteString(text)
|
||||
}
|
||||
}
|
||||
|
||||
func getDeterministicTimestamp() time.Time {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue