Merge upstream/master into feat-draggable-window-title-bars
Resolves conflict in kitty/tabs.py by keeping both force_show_title_bars and renaming_in_window fields.
This commit is contained in:
@@ -311,6 +311,24 @@ static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
(void)notification;
|
||||
// When the application becomes active after switching spaces (e.g., swiping
|
||||
// back from a fullscreen app on another space), macOS may not send
|
||||
// windowDidBecomeKey: for the already-key window. This leaves GLFW thinking
|
||||
// no window has focus (since windowDidResignKey: was sent when leaving).
|
||||
// Ensure GLFW's focus state is updated to match the actual key window.
|
||||
NSWindow *keyWindow = [NSApp keyWindow];
|
||||
if (keyWindow && !_glfw.focusedWindowId) {
|
||||
for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) {
|
||||
if (window->ns.object == keyWindow) {
|
||||
if (_glfw.focusedWindowId != window->id) _glfwInputWindowFocus(window, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||
{
|
||||
(void)sender;
|
||||
@@ -839,6 +857,24 @@ is_apple_jis_layout_function_key(NSEvent *event) {
|
||||
return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_apple_fn_global_shortcut(void) {
|
||||
NSDictionary *hitoolbox_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.HIToolbox"];
|
||||
id obj = [hitoolbox_settings objectForKey:@"AppleFnUsageType"];
|
||||
if (![obj isKindOfClass:[NSNumber class]]) return false;
|
||||
// Non-zero AppleFnUsageType means macOS has reserved Fn/Globe for a
|
||||
// system action such as input source switching, emoji picker, or dictation.
|
||||
return [obj integerValue] != 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_apple_fn_global_shortcut(NSEvent *event) {
|
||||
if ([event keyCode] != 0x3f /* kVK_Function */) return false;
|
||||
NSEventModifierFlags mods = USEFUL_MODS([event modifierFlags]);
|
||||
if (mods != 0 && mods != NSEventModifierFlagFunction) return false;
|
||||
return has_apple_fn_global_shortcut();
|
||||
}
|
||||
|
||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||
handle_reopen_callback = callback;
|
||||
@@ -949,6 +985,10 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
|
||||
debug_key("-------------- flags changed -----------------\n");
|
||||
debug_key("%s\n", [[event description] UTF8String]);
|
||||
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||
if (!_glfw.ignoreOSKeyboardProcessing && !_glfw.keyboard_grabbed && is_apple_fn_global_shortcut(event)) {
|
||||
debug_key("flagsChanged triggered global fn shortcut ignoring\n");
|
||||
return event;
|
||||
}
|
||||
// switching to the next input source is only confirmed when all modifier keys are released
|
||||
if (last_keydown_shortcut_event.input_source_switch_modifiers) {
|
||||
if (!([event modifierFlags] & last_keydown_shortcut_event.input_source_switch_modifiers))
|
||||
|
||||
8
glfw/cocoa_platform.h
vendored
8
glfw/cocoa_platform.h
vendored
@@ -117,8 +117,11 @@ typedef UInt8 (*PFN_LMGetKbdType)(void);
|
||||
#define LMGetKbdType _glfw.ns.tis.GetKbdType
|
||||
|
||||
typedef struct _GLFWDropData {
|
||||
const char **mimes;
|
||||
const char **mimes; // Original MIME list; strings are owned here, never reordered
|
||||
size_t mimes_count;
|
||||
const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
|
||||
size_t copy_mimes_count; // Accepted count after last callback
|
||||
bool drag_accepted;
|
||||
id pasteboard;
|
||||
id data_mapping;
|
||||
id file_promise_mapping;
|
||||
@@ -138,6 +141,9 @@ typedef struct _GLFWwindowNS
|
||||
bool in_traditional_fullscreen;
|
||||
bool in_fullscreen_transition;
|
||||
bool suppress_frame_constraints;
|
||||
id notch_cover_window;
|
||||
unsigned int notch_cover_color;
|
||||
float notch_cover_opacity;
|
||||
bool titlebar_hidden;
|
||||
unsigned long pre_full_screen_style_mask;
|
||||
CGRect pre_traditional_fullscreen_frame;
|
||||
|
||||
@@ -99,6 +99,22 @@ polymorphic_string_as_utf8(id string) {
|
||||
return [characters UTF8String];
|
||||
}
|
||||
|
||||
static bool
|
||||
forward_dictation_selector_to_app(SEL selector, id sender) {
|
||||
static SEL start_dictation_selector = NULL, stop_dictation_selector = NULL;
|
||||
if (start_dictation_selector == NULL) {
|
||||
start_dictation_selector = NSSelectorFromString(@"startDictation:");
|
||||
stop_dictation_selector = NSSelectorFromString(@"stopDictation:");
|
||||
}
|
||||
if (selector != start_dictation_selector && selector != stop_dictation_selector) return false;
|
||||
if ([NSApp respondsToSelector:selector]) {
|
||||
debug_key("Forwarding %s to NSApp\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
[NSApp performSelector:selector withObject:sender];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
vk_code_to_functional_key_code(uint8_t key_code) { // {{{
|
||||
switch(key_code) {
|
||||
@@ -579,6 +595,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
@end
|
||||
|
||||
static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWwindow*, bool, bool);
|
||||
static void _glfwUpdateNotchCover(_GLFWwindow*);
|
||||
|
||||
@implementation GLFWWindowDelegate
|
||||
|
||||
@@ -798,6 +815,7 @@ static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWw
|
||||
NSWindowStyleMask savedMask = window->ns.pre_full_screen_style_mask;
|
||||
CGRect savedFrame = window->ns.pre_traditional_fullscreen_frame;
|
||||
window->ns.in_traditional_fullscreen = false;
|
||||
_glfwUpdateNotchCover(window);
|
||||
window->ns.suppress_frame_constraints = true;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_GLFWwindow *w = NULL;
|
||||
@@ -842,6 +860,7 @@ static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWw
|
||||
// With the default macOS keybindings, pressing certain key combinations
|
||||
// (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound.
|
||||
debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
if (forward_dictation_selector_to_app(selector, nil)) return;
|
||||
}
|
||||
@end // }}}
|
||||
|
||||
@@ -1459,8 +1478,10 @@ is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_m
|
||||
static void
|
||||
free_drop_data(_GLFWwindow *window) {
|
||||
if (window->ns.drop_data.mimes) {
|
||||
for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free(window->ns.drop_data.mimes + i);
|
||||
for (size_t i = 0; i < window->ns.drop_data.mimes_count; i++) free((void*)window->ns.drop_data.mimes[i]);
|
||||
free(window->ns.drop_data.mimes);
|
||||
}
|
||||
free(window->ns.drop_data.copy_mimes); // pointer array only; strings owned by mimes[]
|
||||
if (window->ns.drop_data.pasteboard) [window->ns.drop_data.pasteboard release];
|
||||
if (window->ns.drop_data.data_mapping) [window->ns.drop_data.data_mapping release];
|
||||
if (window->ns.drop_data.file_promise_mapping) {
|
||||
@@ -1477,12 +1498,24 @@ free_drop_data(_GLFWwindow *window) {
|
||||
}
|
||||
|
||||
static void
|
||||
update_drop_state(_GLFWwindow *window, size_t mime_count) {
|
||||
update_drop_state(_GLFWwindow *window, size_t accepted_count) {
|
||||
_GLFWDropData *d = &window->ns.drop_data;
|
||||
for (size_t i = mime_count; i < d->mimes_count; i++) {
|
||||
if (d->mimes[i]) { free((void*)d->mimes[i]); d->mimes[i] = NULL; }
|
||||
d->copy_mimes_count = accepted_count;
|
||||
d->drag_accepted = accepted_count > 0;
|
||||
}
|
||||
|
||||
// Reset the working copy of mimes so the next callback sees the full original
|
||||
// list. Returns false on allocation failure.
|
||||
static bool
|
||||
reset_drop_copy_mimes(_GLFWDropData *d) {
|
||||
if (d->mimes_count == 0) { d->copy_mimes_count = 0; return true; }
|
||||
if (!d->copy_mimes) {
|
||||
d->copy_mimes = malloc(d->mimes_count * sizeof(const char*));
|
||||
if (!d->copy_mimes) return false;
|
||||
}
|
||||
d->mimes_count = mime_count;
|
||||
memcpy(d->copy_mimes, d->mimes, d->mimes_count * sizeof(const char*));
|
||||
d->copy_mimes_count = d->mimes_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
@@ -1547,14 +1580,17 @@ update_drop_state(_GLFWwindow *window, size_t mime_count) {
|
||||
window->ns.drop_data.mimes = mime_array;
|
||||
window->ns.drop_data.mimes_count = mime_count;
|
||||
bool from_self = ([sender draggingSource] != nil);
|
||||
mime_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, mime_array, mime_count, from_self);
|
||||
update_drop_state(window, mime_count);
|
||||
return mime_count ? NSDragOperationGeneric :NSDragOperationNone;
|
||||
_GLFWDropData *d = &window->ns.drop_data;
|
||||
if (reset_drop_copy_mimes(d)) {
|
||||
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_ENTER, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
|
||||
update_drop_state(window, accepted_count);
|
||||
}
|
||||
return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone;
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if (!window->ns.drop_data.mimes_count) return NSDragOperationNone;
|
||||
if (!window->ns.drop_data.drag_accepted) return NSDragOperationNone;
|
||||
const NSRect contentRect = [window->ns.view frame];
|
||||
const NSPoint pos = [sender draggingLocation];
|
||||
double xpos = pos.x;
|
||||
@@ -1562,35 +1598,40 @@ update_drop_state(_GLFWwindow *window, size_t mime_count) {
|
||||
|
||||
bool from_self = ([sender draggingSource] != nil);
|
||||
_GLFWDropData *d = &window->ns.drop_data;
|
||||
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->mimes, d->mimes_count, from_self);
|
||||
update_drop_state(window, mime_count);
|
||||
return mime_count ? NSDragOperationGeneric :NSDragOperationNone;
|
||||
if (reset_drop_copy_mimes(d)) {
|
||||
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
|
||||
update_drop_state(window, accepted_count);
|
||||
}
|
||||
return window->ns.drop_data.drag_accepted ? NSDragOperationGeneric : NSDragOperationNone;
|
||||
}
|
||||
|
||||
- (void)draggingExited:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
bool from_self = ([sender draggingSource] != nil);
|
||||
_GLFWDropData *d = &window->ns.drop_data;
|
||||
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->mimes, d->mimes_count, from_self);
|
||||
update_drop_state(window, mime_count);
|
||||
if (reset_drop_copy_mimes(d)) {
|
||||
size_t accepted_count = _glfwInputDropEvent(window, GLFW_DROP_LEAVE, 0, 0, d->copy_mimes, d->copy_mimes_count, from_self);
|
||||
update_drop_state(window, accepted_count);
|
||||
}
|
||||
free_drop_data(window);
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if (!window->ns.drop_data.mimes_count) return NO;
|
||||
if (!window->ns.drop_data.drag_accepted) return NO;
|
||||
const NSRect contentRect = [window->ns.view frame];
|
||||
const NSPoint pos = [sender draggingLocation];
|
||||
double xpos = pos.x;
|
||||
double ypos = contentRect.size.height - pos.y;
|
||||
bool from_self = ([sender draggingSource] != nil);
|
||||
_GLFWDropData *d = &window->ns.drop_data;
|
||||
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->mimes, d->mimes_count, from_self);
|
||||
if (d->mimes) {
|
||||
update_drop_state(window, mime_count);
|
||||
if (!reset_drop_copy_mimes(d)) return NO;
|
||||
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, xpos, ypos, d->copy_mimes, d->copy_mimes_count, from_self);
|
||||
if (d->copy_mimes) {
|
||||
update_drop_state(window, num_accepted);
|
||||
window->ns.drop_data.pasteboard = [[sender draggingPasteboard] retain];
|
||||
for (size_t i = 0; i < d->mimes_count; i++)
|
||||
_glfwPlatformRequestDropData(window, d->mimes[i]);
|
||||
for (size_t i = 0; i < num_accepted; i++)
|
||||
_glfwPlatformRequestDropData(window, d->copy_mimes[i]);
|
||||
}
|
||||
// Restore first-responder status after native DnD; the drag operation can
|
||||
// displace the content view from first responder, silently breaking keyboard
|
||||
@@ -1913,6 +1954,17 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
- (void)doCommandBySelector:(SEL)selector
|
||||
{
|
||||
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||
if (forward_dictation_selector_to_app(selector, self)) return;
|
||||
}
|
||||
|
||||
- (void)startDictation:(id)sender
|
||||
{
|
||||
forward_dictation_selector_to_app(_cmd, sender);
|
||||
}
|
||||
|
||||
- (void)stopDictation:(id)sender
|
||||
{
|
||||
forward_dictation_selector_to_app(_cmd, sender);
|
||||
}
|
||||
|
||||
- (BOOL)isAccessibilityElement
|
||||
@@ -2387,6 +2439,12 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
if (_glfw.ns.disabledCursorWindow == window)
|
||||
_glfw.ns.disabledCursorWindow = NULL;
|
||||
free_drop_data(window);
|
||||
if (window->ns.notch_cover_window) {
|
||||
[w removeChildWindow:window->ns.notch_cover_window];
|
||||
[window->ns.notch_cover_window close];
|
||||
[window->ns.notch_cover_window release];
|
||||
window->ns.notch_cover_window = nil;
|
||||
}
|
||||
|
||||
[w orderOut:nil];
|
||||
|
||||
@@ -3313,6 +3371,44 @@ make_window_fullscreen_after_show(unsigned long long timer_id, void* data) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_glfwUpdateNotchCover(_GLFWwindow* w) {
|
||||
NSWindow *window = w->ns.object;
|
||||
if (w->ns.notch_cover_window) {
|
||||
[window removeChildWindow:w->ns.notch_cover_window];
|
||||
[w->ns.notch_cover_window close];
|
||||
[w->ns.notch_cover_window release];
|
||||
w->ns.notch_cover_window = nil;
|
||||
}
|
||||
if (!w->ns.in_traditional_fullscreen) return;
|
||||
if (@available(macOS 12.0, *)) {
|
||||
CGFloat insetTop = window.screen.safeAreaInsets.top;
|
||||
if (insetTop <= 0) return;
|
||||
NSRect sf = [window.screen frame];
|
||||
NSWindow *bg_window = [[NSWindow alloc] initWithContentRect:sf styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
|
||||
[bg_window setBackgroundColor:[NSColor clearColor]];
|
||||
[bg_window setHasShadow:NO];
|
||||
[bg_window setOpaque:NO];
|
||||
[bg_window setIgnoresMouseEvents:YES];
|
||||
[bg_window setReleasedWhenClosed:NO];
|
||||
[bg_window setColorSpace:[window colorSpace]];
|
||||
// Add a colored subview only in the notch strip area
|
||||
NSView *notchView = [[NSView alloc] initWithFrame:NSMakeRect(0, sf.size.height - insetTop, sf.size.width, insetTop)];
|
||||
notchView.wantsLayer = YES;
|
||||
unsigned int c = w->ns.notch_cover_color;
|
||||
float a = w->ns.notch_cover_opacity;
|
||||
notchView.layer.backgroundColor = [NSColor colorWithSRGBRed:((c >> 16) & 0xFF) / 255.0
|
||||
green:((c >> 8) & 0xFF) / 255.0
|
||||
blue:(c & 0xFF) / 255.0
|
||||
alpha:a].CGColor;
|
||||
[bg_window.contentView addSubview:notchView];
|
||||
// must be above otherwise shadow of main window is rendered over bg_window
|
||||
[window addChildWindow:bg_window ordered:NSWindowAbove];
|
||||
w->ns.notch_cover_window = bg_window;
|
||||
[notchView release];
|
||||
}
|
||||
}
|
||||
|
||||
bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
NSWindow *window = w->ns.object;
|
||||
bool made_fullscreen = true;
|
||||
@@ -3335,8 +3431,13 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
w->ns.pre_traditional_fullscreen_frame = [window frame];
|
||||
[window setStyleMask: NSWindowStyleMaskBorderless];
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||
[window setFrame:[window.screen frame] display:YES];
|
||||
NSRect screenFrame = [window.screen frame];
|
||||
if (@available(macOS 12.0, *)) {
|
||||
screenFrame.size.height -= window.screen.safeAreaInsets.top;
|
||||
}
|
||||
[window setFrame:screenFrame display:YES];
|
||||
w->ns.in_traditional_fullscreen = true;
|
||||
_glfwUpdateNotchCover(w);
|
||||
} else {
|
||||
made_fullscreen = false;
|
||||
if (sm & NSWindowStyleMaskFullScreen) {
|
||||
@@ -3354,6 +3455,7 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
[window setStyleMask: w->ns.pre_full_screen_style_mask];
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||
w->ns.in_traditional_fullscreen = false;
|
||||
_glfwUpdateNotchCover(w);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -3925,6 +4027,9 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
|
||||
|
||||
// HACK: Changing the style mask can cause the first responder to be cleared
|
||||
[nsw makeFirstResponder:window->ns.view];
|
||||
window->ns.notch_cover_color = color;
|
||||
window->ns.notch_cover_opacity = background_opacity;
|
||||
if (window->ns.notch_cover_window) _glfwUpdateNotchCover(window);
|
||||
}}
|
||||
|
||||
GLFWAPI uint32_t
|
||||
|
||||
8
glfw/input.c
vendored
8
glfw/input.c
vendored
@@ -402,7 +402,13 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered)
|
||||
window->callbacks.cursorEnter((GLFWwindow*) window, entered);
|
||||
}
|
||||
|
||||
// Notifies shared code of a drop event
|
||||
// Notifies shared code of a drop event.
|
||||
// The caller is responsible for passing a mutable working-copy of the mimes
|
||||
// array (reset to the full original list before each call) so that the
|
||||
// callback can sort/filter in-place without touching the backend's canonical
|
||||
// storage. The return value is ev.num_mimes after the callback returns,
|
||||
// i.e. the number of accepted (possibly reordered) mimes starting at
|
||||
// mimes[0].
|
||||
size_t _glfwInputDropEvent(_GLFWwindow *window, GLFWDropEventType type, double xpos, double ypos, const char** mimes, size_t num_mimes, bool from_self) {
|
||||
if (!window->callbacks.drop_event) return 0;
|
||||
GLFWDropEvent ev = {
|
||||
|
||||
10
glfw/wl_init.c
vendored
10
glfw/wl_init.c
vendored
@@ -206,27 +206,35 @@ pointer_handle_frame(void *data UNUSED, struct wl_pointer *pointer UNUSED) {
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
if (!window) return;
|
||||
GLFWScrollEvent ev = {.keyboard_modifiers=_glfw.wl.xkb.states.modifiers};
|
||||
bool found = false;
|
||||
|
||||
if (info.discrete.y_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.unscaled.y = info.discrete.y;
|
||||
if (info.discrete.y_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
found = true;
|
||||
} else if (info.continuous.y_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
ev.unscaled.y = info.continuous.y;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (info.discrete.x_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.unscaled.x = info.discrete.x;
|
||||
if (info.discrete.x_axis_type == AXIS_EVENT_VALUE120) ev.offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
found = true;
|
||||
} else if (info.continuous.x_axis_type != AXIS_EVENT_UNKNOWN) {
|
||||
ev.offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
ev.unscaled.x = info.continuous.x;
|
||||
found = true;
|
||||
}
|
||||
bool stopped = info.y_stop_received || info.x_stop_received;
|
||||
if (!found && stopped) ev.offset_type = window->wl.prev_frame_offset_type;
|
||||
ev.unscaled.x *= -1;
|
||||
const double scale = ev.offset_type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfwWaylandWindowScale(window) : 1;
|
||||
ev.x_offset = scale * ev.unscaled.x; ev.y_offset = scale * ev.unscaled.y;
|
||||
glfw_handle_scroll_event_for_momentum(
|
||||
window, &ev, info.y_stop_received || info.x_stop_received, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER);
|
||||
window, &ev, stopped, info.source_type == WL_POINTER_AXIS_SOURCE_FINGER);
|
||||
window->wl.prev_frame_offset_type = ev.offset_type;
|
||||
/* clear pointer_curr_axis_info for next frame */
|
||||
memset(&info, 0, sizeof(info));
|
||||
}
|
||||
|
||||
3
glfw/wl_platform.h
vendored
3
glfw/wl_platform.h
vendored
@@ -212,6 +212,7 @@ typedef struct _GLFWwindowWayland
|
||||
uint32_t source_type;
|
||||
monotonic_t x_start_time, x_stop_time, y_stop_time, y_start_time;
|
||||
} pointer_curr_axis_info;
|
||||
GLFWOffsetType prev_frame_offset_type;
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY;
|
||||
@@ -310,6 +311,8 @@ typedef struct _GLFWWaylandDataOffer
|
||||
struct wl_surface *surface;
|
||||
const char **mimes;
|
||||
size_t mimes_capacity, mimes_count;
|
||||
const char **copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
|
||||
size_t copy_mimes_count; // Count of entries in copy_mimes (accepted count after callback)
|
||||
bool drag_accepted, dropped;
|
||||
uint32_t serial;
|
||||
struct {
|
||||
|
||||
120
glfw/wl_window.c
vendored
120
glfw/wl_window.c
vendored
@@ -1588,6 +1588,14 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
|
||||
void
|
||||
_glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) {
|
||||
if (is_layer_shell(window)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot set window icon on layer shell surfaces");
|
||||
return;
|
||||
}
|
||||
if (!window->wl.xdg.toplevel) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Ignoring attempt to set window icon on window without a toplevel");
|
||||
return;
|
||||
}
|
||||
if (!_glfw.wl.xdg_toplevel_icon_manager_v1) {
|
||||
static bool warned_once = false;
|
||||
if (!warned_once) {
|
||||
@@ -2364,10 +2372,25 @@ destroy_data_offer(_GLFWWaylandDataOffer *offer) {
|
||||
for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]);
|
||||
free(offer->mimes);
|
||||
}
|
||||
free(offer->copy_mimes); // pointer array only; strings are owned by mimes[]
|
||||
if (offer->requested_drop_data) destroy_drop_data(offer);
|
||||
memset(offer, 0, sizeof(offer[0]));
|
||||
}
|
||||
|
||||
// Reset the working copy of mimes so the next callback sees the full original
|
||||
// list. Returns false on allocation failure.
|
||||
static bool
|
||||
reset_copy_mimes(_GLFWWaylandDataOffer *offer) {
|
||||
if (offer->mimes_count == 0) { offer->copy_mimes_count = 0; return true; }
|
||||
if (!offer->copy_mimes) {
|
||||
offer->copy_mimes = malloc(offer->mimes_count * sizeof(const char*));
|
||||
if (!offer->copy_mimes) return false;
|
||||
}
|
||||
memcpy(offer->copy_mimes, offer->mimes, offer->mimes_count * sizeof(const char*));
|
||||
offer->copy_mimes_count = offer->mimes_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
mark_data_offer(_GLFWWaylandDataOffer *ans, void *id) {
|
||||
if (ans->id) destroy_data_offer(ans);
|
||||
@@ -2480,15 +2503,12 @@ static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary
|
||||
|
||||
// Helper function to update drop state from callback results
|
||||
static void
|
||||
update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t mime_count) {
|
||||
for (size_t i = mime_count; i < d->mimes_count; i++) {
|
||||
if (d->mimes[i]) { free((void*)d->mimes[i]); d->mimes[i] = NULL; }
|
||||
}
|
||||
d->mimes_count = mime_count;
|
||||
bool accepted = mime_count > 0;
|
||||
update_drop_state(_GLFWWaylandDataOffer *d, _GLFWwindow* window UNUSED, size_t accepted_count) {
|
||||
d->copy_mimes_count = accepted_count;
|
||||
bool accepted = accepted_count > 0;
|
||||
bool acceptance_changed = (accepted != d->drag_accepted);
|
||||
// The first MIME in the sorted list is the preferred one for drop
|
||||
const char* new_preferred_mime = (accepted && mime_count > 0) ? d->mimes[0] : NULL;
|
||||
// The first entry in the accepted (sorted) copy is the preferred MIME.
|
||||
const char* new_preferred_mime = (accepted && d->copy_mimes) ? d->copy_mimes[0] : NULL;
|
||||
bool mime_changed = false;
|
||||
|
||||
// Check if the preferred MIME changed
|
||||
@@ -2522,10 +2542,12 @@ drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint
|
||||
if (window->wl.surface == surface) {
|
||||
double xpos = wl_fixed_to_double(x);
|
||||
double ypos = wl_fixed_to_double(y);
|
||||
size_t mime_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_ENTER, xpos, ypos,
|
||||
offer->mimes, offer->mimes_count, offer->is_self_offer);
|
||||
update_drop_state(offer, window, mime_count);
|
||||
if (reset_copy_mimes(offer)) {
|
||||
size_t mime_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_ENTER, xpos, ypos,
|
||||
offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
|
||||
update_drop_state(offer, window, mime_count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
@@ -2563,7 +2585,7 @@ _glfwPlatformReadAvailableDropData(GLFWwindow *w, GLFWDropEvent *ev, char *buffe
|
||||
for (size_t o = 0; o < offer->dd_count; o++) {
|
||||
if (offer->requested_drop_data[o].fd == fd) {
|
||||
ssize_t ret;
|
||||
do { ret = read(fd, buffer, sz); } while (ret < 0 && errno == EINTR);
|
||||
do { ret = read(fd, buffer, sz); } while (ret < 0 && (errno == EINTR || errno == EAGAIN));
|
||||
if (ret <= 0) removeWatch(&_glfw.wl.eventLoopData, offer->requested_drop_data[o].watch_id);
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
@@ -2643,9 +2665,11 @@ drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window) {
|
||||
if (window->wl.surface == offer->surface) {
|
||||
size_t num_mimes = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, offer->mimes, offer->mimes_count, offer->is_self_offer);
|
||||
if (!offer->mimes) { destroy_data_offer(offer); return; }
|
||||
for (size_t i = 0; i < num_mimes; i++) request_drop_data(offer, offer->mimes[i]);
|
||||
if (reset_copy_mimes(offer)) {
|
||||
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
|
||||
if (!offer->copy_mimes) { destroy_data_offer(offer); return; }
|
||||
for (size_t i = 0; i < num_accepted; i++) request_drop_data(offer, offer->copy_mimes[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
@@ -2661,9 +2685,11 @@ motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t
|
||||
if (window->wl.surface == offer->surface) {
|
||||
double xpos = wl_fixed_to_double(x);
|
||||
double ypos = wl_fixed_to_double(y);
|
||||
size_t mime_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_MOVE, xpos, ypos, offer->mimes, offer->mimes_count, offer->is_self_offer);
|
||||
update_drop_state(offer, window, mime_count);
|
||||
if (reset_copy_mimes(offer)) {
|
||||
size_t mime_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_MOVE, xpos, ypos, offer->copy_mimes, offer->copy_mimes_count, offer->is_self_offer);
|
||||
update_drop_state(offer, window, mime_count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
@@ -2673,8 +2699,8 @@ motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t
|
||||
void
|
||||
_glfwPlatformRequestDropUpdate(_GLFWwindow* window) {
|
||||
_GLFWWaylandDataOffer *d = &_glfw.wl.drop_data_offer;
|
||||
if (d->id) {
|
||||
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, d->mimes, d->mimes_count, d->is_self_offer);
|
||||
if (d->id && reset_copy_mimes(d)) {
|
||||
size_t mime_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, d->copy_mimes, d->copy_mimes_count, d->is_self_offer);
|
||||
update_drop_state(d, window, mime_count);
|
||||
}
|
||||
}
|
||||
@@ -3151,6 +3177,7 @@ finish_drag_write(size_t i) {
|
||||
dr.watch_id = 0;
|
||||
if (dr.fd > -1) safe_close(dr.fd);
|
||||
dr.fd = -1;
|
||||
free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0;
|
||||
free((void*)dr.mime_type); dr.mime_type = NULL;
|
||||
}
|
||||
|
||||
@@ -3172,31 +3199,49 @@ write_as_much_as_possible(int fd, const char *data, size_t sz) {
|
||||
static void
|
||||
send_drag_data(_GLFWwindow *window, size_t i) {
|
||||
ssize_t ret;
|
||||
bool has_preset_data = _glfw.drag.items[i].data_size > 0;
|
||||
// Find the item matching the mime type for this data request.
|
||||
// We cannot use i directly to index _glfw.drag.items because the compositor
|
||||
// may call drag_source_send multiple times (once per target entered), making
|
||||
// data_requests grow independently of the items array.
|
||||
size_t item_idx = _glfw.drag.item_count; // sentinel: not found
|
||||
for (size_t j = 0; j < _glfw.drag.item_count; j++) {
|
||||
if (dr.mime_type && _glfw.drag.items[j].mime_type &&
|
||||
strcmp(_glfw.drag.items[j].mime_type, dr.mime_type) == 0) {
|
||||
item_idx = j; break;
|
||||
}
|
||||
}
|
||||
if (item_idx == _glfw.drag.item_count) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: compositor requested data for unrecognised MIME type: %s",
|
||||
dr.mime_type ? dr.mime_type : "(null)");
|
||||
}
|
||||
bool has_preset_data = item_idx < _glfw.drag.item_count && _glfw.drag.items[item_idx].data_size > 0;
|
||||
// On write error, only close this pipe; do NOT destroy the wl_data_source
|
||||
// since the compositor must send cancelled/dnd_finished before that is safe.
|
||||
#define on_fail _glfwInputError(\
|
||||
GLFW_PLATFORM_ERROR, "Wayland: failed to write drag source data to pipe with error: %s", strerror(errno)); \
|
||||
cancel_drag(GLFW_DRAG_CANCELLED);
|
||||
finish_drag_write(i); return;
|
||||
|
||||
if (dr.sz > dr.offset) {
|
||||
ret = write_as_much_as_possible(dr.fd, dr.pending_data + dr.offset, dr.sz - dr.offset);
|
||||
if (ret < 0) { on_fail; } else {
|
||||
dr.offset += ret;
|
||||
if (dr.offset >= dr.sz) {
|
||||
free(dr.pending_data); dr.sz = 0; dr.offset = 0;
|
||||
if (has_preset_data) finish_drag_write(i);
|
||||
free(dr.pending_data); dr.pending_data = NULL; dr.sz = 0; dr.offset = 0;
|
||||
finish_drag_write(i);
|
||||
}
|
||||
}
|
||||
} else if (has_preset_data) {
|
||||
do { ret = write(dr.fd, _glfw.drag.items[i].optional_data, _glfw.drag.items[i].data_size); } while (ret < 0 && errno == EINTR);
|
||||
do { ret = write(dr.fd, _glfw.drag.items[item_idx].optional_data, _glfw.drag.items[item_idx].data_size); } while (ret < 0 && errno == EINTR);
|
||||
if (ret < 0) {
|
||||
on_fail;
|
||||
} else {
|
||||
if ((size_t)ret >= _glfw.drag.items[i].data_size) {
|
||||
if ((size_t)ret >= _glfw.drag.items[item_idx].data_size) {
|
||||
finish_drag_write(i);
|
||||
} else {
|
||||
void *pending = malloc(_glfw.drag.items[i].data_size - ret);
|
||||
if (!pending) { on_fail; } else {
|
||||
dr.pending_data = pending; dr.sz = _glfw.drag.items[i].data_size - ret; dr.offset = 0;
|
||||
void *pending = malloc(_glfw.drag.items[item_idx].data_size - ret);
|
||||
if (!pending) { errno = ENOMEM; on_fail; } else {
|
||||
dr.pending_data = pending; dr.sz = _glfw.drag.items[item_idx].data_size - ret; dr.offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3205,21 +3250,20 @@ send_drag_data(_GLFWwindow *window, size_t i) {
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
if (ev.err_num) {
|
||||
if (ev.err_num == EAGAIN) { removeWatch(&_glfw.wl.eventLoopData, dr.watch_id); dr.watch_id = 0; }
|
||||
else cancel_drag(GLFW_DRAG_CANCELLED);
|
||||
else { finish_drag_write(i); return; }
|
||||
} else {
|
||||
if (ev.data_sz) {
|
||||
ret = write_as_much_as_possible(dr.fd, ev.data, ev.data_sz);
|
||||
if (ret >= 0) {
|
||||
if ((size_t)ret < ev.data_sz) {
|
||||
void *pending = malloc(ev.data_sz - ret);
|
||||
if (!pending) { on_fail; } else {
|
||||
dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0;
|
||||
memcpy(pending, ev.data + ret, dr.sz);
|
||||
}
|
||||
if (ret >= 0 && (size_t)ret < ev.data_sz) {
|
||||
void *pending = malloc(ev.data_sz - ret);
|
||||
if (!pending) { ret = -1; } else {
|
||||
dr.pending_data = pending; dr.sz = ev.data_sz - ret; dr.offset = 0;
|
||||
memcpy(pending, ev.data + ret, dr.sz);
|
||||
}
|
||||
}
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
if (ret < 0) { on_fail; }
|
||||
else if ((size_t)ret >= ev.data_sz) { finish_drag_write(i); }
|
||||
} else finish_drag_write(i);
|
||||
}
|
||||
}
|
||||
|
||||
6
glfw/x11_init.c
vendored
6
glfw/x11_init.c
vendored
@@ -216,8 +216,12 @@ read_xi_scroll_devices(void) {
|
||||
if (_glfw.x11.xi.num_scroll_devices >= arraysz(_glfw.x11.xi.scroll_devices)) continue;
|
||||
d = &_glfw.x11.xi.scroll_devices[_glfw.x11.xi.num_scroll_devices++];
|
||||
*d = (XIScrollDevice){
|
||||
.is_highres=is_highres, .is_finger_based=is_finger_based, .deviceid=device->deviceid, .sourceid=scroll->sourceid,
|
||||
.is_finger_based=is_finger_based, .deviceid=device->deviceid, .sourceid=scroll->sourceid,
|
||||
};
|
||||
if (is_highres) {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
}
|
||||
memcpy(d->name, device->name, MIN(sizeof(d->name)-1, strlen(device->name)));
|
||||
}
|
||||
if (d->num_valuators >= arraysz(d->valuators)) continue;
|
||||
|
||||
11
glfw/x11_platform.h
vendored
11
glfw/x11_platform.h
vendored
@@ -239,12 +239,14 @@ typedef struct XIScrollValuator {
|
||||
} XIScrollValuator;
|
||||
|
||||
typedef struct XIScrollDevice {
|
||||
bool is_highres;
|
||||
bool is_finger_based;
|
||||
bool type_detected;
|
||||
int deviceid, sourceid;
|
||||
XIScrollValuator valuators[8];
|
||||
unsigned num_valuators;
|
||||
char name[32];
|
||||
unsigned num_events;
|
||||
GLFWOffsetType offset_type;
|
||||
} XIScrollDevice;
|
||||
|
||||
typedef struct XdndSelectionRequest {
|
||||
@@ -393,8 +395,11 @@ typedef struct _GLFWlibraryX11
|
||||
char format[256];
|
||||
int format_priority;
|
||||
Window target_window; // For drag events: the window being dragged over
|
||||
const char** mimes; // Cached MIME types from drag enter
|
||||
size_t mimes_count; // Current count of MIME types (may be reduced by callback)
|
||||
const char** mimes; // Cached MIME types from drag enter (original, never reordered)
|
||||
size_t mimes_count; // Count of MIME types (full original list, never reduced)
|
||||
const char** copy_mimes; // Working copy passed to callbacks; pointers into mimes[]
|
||||
size_t copy_mimes_count; // Accepted count after last callback
|
||||
bool drag_accepted; // Whether the callback accepted at least one MIME type
|
||||
bool from_self, dropped;
|
||||
Time drop_time;
|
||||
XdndSelectionRequest *selection_requests;
|
||||
|
||||
128
glfw/x11_window.c
vendored
128
glfw/x11_window.c
vendored
@@ -29,6 +29,7 @@
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "internal.h"
|
||||
#include "math.h"
|
||||
#include "backend_utils.h"
|
||||
#include "linux_notify.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
@@ -1414,6 +1415,11 @@ handle_mouse_move_event(_GLFWwindow *window, const int x, const int y) {
|
||||
window->x11.lastCursorPosY = y;
|
||||
}
|
||||
|
||||
static bool
|
||||
number_has_fractional_part(double x) {
|
||||
return fabs(x - round(x)) >= 1e-6;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
XIScrollDevice *d = NULL;
|
||||
@@ -1441,17 +1447,35 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
scroll_valuator_found = true;
|
||||
double delta = value - v->value;
|
||||
v->value = value;
|
||||
if (v->is_vertical) delta *= -1;
|
||||
delta *= -1;
|
||||
double *off = v->is_vertical ? &yOffset : &xOffset;
|
||||
*off = delta;
|
||||
if (!d->is_highres) {
|
||||
if (v->increment == 120.) type = GLFW_SCROLL_OFFEST_V120;
|
||||
else {
|
||||
type = GLFW_SCROLL_OFFSET_LINES;
|
||||
if (v->increment != 0) *off /= v->increment;
|
||||
d->num_events++;
|
||||
if (!d->type_detected) {
|
||||
if (v->increment == 120.) {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
} else {
|
||||
bool delta_is_fractional = number_has_fractional_part(delta);
|
||||
if (delta_is_fractional) {
|
||||
if (fabs(delta * 120 - round(delta * 120)) < 0.01) {
|
||||
d->type_detected = d->num_events > 2;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_V120;
|
||||
} else {
|
||||
d->type_detected = true;
|
||||
d->offset_type = GLFW_SCROLL_OFFEST_HIGHRES;
|
||||
}
|
||||
} else {
|
||||
d->type_detected = d->num_events > 2;
|
||||
d->offset_type = GLFW_SCROLL_OFFSET_LINES;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d->offset_type == GLFW_SCROLL_OFFSET_LINES) {
|
||||
if (v->increment != 0) *off /= v->increment;
|
||||
}
|
||||
}
|
||||
type = d->offset_type;
|
||||
if (xOffset != 0 || yOffset != 0) {
|
||||
// Get keyboard modifiers
|
||||
int mods = translateState(de->mods.effective);
|
||||
@@ -1465,7 +1489,7 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
};
|
||||
|
||||
// For high-resolution, finger-based scrolling, use timer-based momentum scrolling
|
||||
if (d->is_highres && d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) {
|
||||
if (d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) {
|
||||
// Reset the timer on each scroll event
|
||||
x11_cancel_momentum_scroll_timer();
|
||||
|
||||
@@ -1498,7 +1522,7 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
|
||||
|
||||
static void
|
||||
end_drop(_GLFWwindow *window, GLFWDragOperationType op) {
|
||||
bool accepted = dnd.mimes_count > 0 || dnd.dropped;
|
||||
bool accepted = dnd.drag_accepted || dnd.dropped;
|
||||
XEvent reply = { ClientMessage };
|
||||
reply.xclient.window = dnd.source;
|
||||
reply.xclient.message_type = _glfw.x11.XdndFinished;
|
||||
@@ -1520,14 +1544,12 @@ end_drop(_GLFWwindow *window, GLFWDragOperationType op) {
|
||||
|
||||
|
||||
static void
|
||||
update_drop_state(_GLFWwindow* window, size_t mime_count) {
|
||||
for (size_t i = mime_count; i < dnd.mimes_count; i++) {
|
||||
if (dnd.mimes[i]) { XFree((void*)dnd.mimes[i]); dnd.mimes[i] = NULL; }
|
||||
}
|
||||
dnd.mimes_count = mime_count;
|
||||
bool accepted = mime_count > 0;
|
||||
// The first MIME in the sorted list is the preferred one for drop
|
||||
const char* new_preferred_mime = (accepted && mime_count > 0) ? dnd.mimes[0] : NULL;
|
||||
update_drop_state(_GLFWwindow* window, size_t accepted_count) {
|
||||
dnd.copy_mimes_count = accepted_count;
|
||||
bool accepted = accepted_count > 0;
|
||||
dnd.drag_accepted = accepted;
|
||||
// The first entry in the accepted (sorted) copy is the preferred MIME.
|
||||
const char* new_preferred_mime = (accepted && dnd.copy_mimes) ? dnd.copy_mimes[0] : NULL;
|
||||
// Check if the preferred MIME changed
|
||||
bool mime_changed = strncmp(new_preferred_mime ? new_preferred_mime : "", dnd.format, arraysz(dnd.format)) != 0;
|
||||
if (mime_changed) {
|
||||
@@ -1566,12 +1588,30 @@ free_dnd_mimes(void) {
|
||||
dnd.mimes = NULL;
|
||||
dnd.mimes_count = 0;
|
||||
}
|
||||
free(dnd.copy_mimes); // pointer array only; strings are owned by mimes[]
|
||||
dnd.copy_mimes = NULL;
|
||||
dnd.copy_mimes_count = 0;
|
||||
}
|
||||
|
||||
// Reset the working copy of mimes so the next callback sees the full original
|
||||
// list. Returns false on allocation failure.
|
||||
static bool
|
||||
reset_dnd_copy_mimes(void) {
|
||||
if (dnd.mimes_count == 0) { dnd.copy_mimes_count = 0; return true; }
|
||||
if (!dnd.copy_mimes) {
|
||||
dnd.copy_mimes = malloc(dnd.mimes_count * sizeof(const char*));
|
||||
if (!dnd.copy_mimes) return false;
|
||||
}
|
||||
memcpy(dnd.copy_mimes, dnd.mimes, dnd.mimes_count * sizeof(const char*));
|
||||
dnd.copy_mimes_count = dnd.mimes_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
free_dnd_data(void) {
|
||||
dnd.source = None;
|
||||
dnd.target_window = None;
|
||||
dnd.drag_accepted = false;
|
||||
free_dnd_mimes();
|
||||
if (dnd.selection_requests) {
|
||||
for (size_t i = 0; i < dnd.selection_requests_count; i++) {
|
||||
@@ -1640,14 +1680,13 @@ drop_start(_GLFWwindow *window, XEvent *event) {
|
||||
update_dnd_mimes(event);
|
||||
dnd.from_self = _glfw.x11.drag.source_window != None && dnd.source == _glfw.x11.drag.source_window;
|
||||
// Position is not known yet at enter time, will be updated with XdndPosition
|
||||
size_t mimes_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_ENTER, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
|
||||
update_drop_state(window, mimes_count);
|
||||
// Update cached mime count with callback result
|
||||
if (dnd.mimes_count > 0) {
|
||||
// The first MIME type in the reordered list is the preferred one
|
||||
strncpy(dnd.format, dnd.mimes[0], arraysz(dnd.format) - 1);
|
||||
dnd.format_priority = 1;
|
||||
if (reset_dnd_copy_mimes()) {
|
||||
size_t accepted_count = _glfwInputDropEvent(
|
||||
window, GLFW_DROP_ENTER, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
|
||||
update_drop_state(window, accepted_count);
|
||||
// Set format_priority when the callback accepted at least one MIME type.
|
||||
// dnd.format has already been updated by update_drop_state.
|
||||
if (accepted_count > 0) dnd.format_priority = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1674,8 +1713,10 @@ drop_move(_GLFWwindow *window, XEvent *event) {
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success) _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position");
|
||||
_glfwInputCursorPos(window, xpos, ypos);
|
||||
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, dnd.mimes, dnd.mimes_count, dnd.from_self);
|
||||
update_drop_state(window, mimes_count);
|
||||
if (reset_dnd_copy_mimes()) {
|
||||
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_MOVE, xpos, ypos, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
|
||||
update_drop_state(window, mimes_count);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1684,8 +1725,10 @@ _glfwPlatformRequestDropUpdate(_GLFWwindow* window) {
|
||||
if (dnd.source == None || dnd.target_window != window->x11.handle) return;
|
||||
// Call the drag callback with STATUS_UPDATE event to get updated state
|
||||
// Position values are not valid for this event type
|
||||
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
|
||||
update_drop_state(window, mimes_count);
|
||||
if (reset_dnd_copy_mimes()) {
|
||||
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_STATUS_UPDATE, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
|
||||
update_drop_state(window, mimes_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1695,9 +1738,10 @@ drop(_GLFWwindow *window, XEvent *event) {
|
||||
if (dnd.version > _GLFW_XDND_VERSION || dnd.version < 2) return;
|
||||
dnd.dropped = true;
|
||||
dnd.drop_time = (unsigned long)event->xclient.data.l[2];
|
||||
size_t mimes_count = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, dnd.mimes, dnd.mimes_count, dnd.from_self);
|
||||
if (!dnd.mimes) return;
|
||||
for (size_t i = 0; i < mimes_count; i++) _glfwPlatformRequestDropData(window, dnd.mimes[i]);
|
||||
if (!reset_dnd_copy_mimes()) return;
|
||||
size_t num_accepted = _glfwInputDropEvent(window, GLFW_DROP_DROP, 0, 0, dnd.copy_mimes, dnd.copy_mimes_count, dnd.from_self);
|
||||
if (!dnd.copy_mimes) return;
|
||||
for (size_t i = 0; i < num_accepted; i++) _glfwPlatformRequestDropData(window, dnd.copy_mimes[i]);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -4004,8 +4048,11 @@ send_xdnd_enter(Window target, int version) {
|
||||
}
|
||||
}
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None;
|
||||
}
|
||||
|
||||
// Send XdndPosition message to target window
|
||||
@@ -4024,9 +4071,12 @@ send_xdnd_position(Window target, int root_x, int root_y, Time timestamp) {
|
||||
event.xclient.data.l[3] = timestamp;
|
||||
event.xclient.data.l[4] = _glfw.x11.drag.action_atom;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfw.x11.drag.waiting_for_status = true;
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode == BadWindow) _glfw.x11.drag.current_target = None;
|
||||
else _glfw.x11.drag.waiting_for_status = true;
|
||||
}
|
||||
|
||||
// Send XdndLeave message to target window
|
||||
@@ -4041,8 +4091,11 @@ send_xdnd_leave(Window target) {
|
||||
event.xclient.format = 32;
|
||||
event.xclient.data.l[0] = _glfw.x11.drag.source_window;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
// BadWindow on leave is benign – the target window is already gone
|
||||
}
|
||||
|
||||
// Send XdndDrop message to target window
|
||||
@@ -4059,8 +4112,19 @@ send_xdnd_drop(Window target, Time timestamp) {
|
||||
event.xclient.data.l[1] = 0; // Reserved
|
||||
event.xclient.data.l[2] = timestamp;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XSendEvent(_glfw.x11.display, target, False, NoEventMask, &event);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode == BadWindow) {
|
||||
// Target window was destroyed; cancel the drag gracefully
|
||||
_GLFWwindow *window = _glfwWindowForId(_glfw.drag.window_id);
|
||||
if (window) {
|
||||
GLFWDragEvent ev = {.type = GLFW_DRAG_CANCELLED};
|
||||
_glfwInputDragSourceRequest(window, &ev);
|
||||
}
|
||||
_glfwFreeDragSourceData();
|
||||
}
|
||||
}
|
||||
|
||||
// Render thumbnail pixels into _glfw.x11.drag.thumbnail_pixmap / thumbnail_gc.
|
||||
|
||||
Reference in New Issue
Block a user