diff --git a/patches/cocotron.patch b/patches/cocotron.patch index dad1f5c..7199576 100644 --- a/patches/cocotron.patch +++ b/patches/cocotron.patch @@ -2307,7 +2307,7 @@ index 18189404..c683f96a 100644 +- (NSRect) frameForRootRect: (NSRect) rect; @end diff --git a/AppKit/X11.backend/X11Display.m b/AppKit/X11.backend/X11Display.m -index 5ae29bf4..b98eff64 100644 +index 5ae29bf4..dc3fa95e 100644 --- a/AppKit/X11.backend/X11Display.m +++ b/AppKit/X11.backend/X11Display.m @@ -48,12 +48,271 @@ @@ -3076,10 +3076,10 @@ index 5ae29bf4..b98eff64 100644 + result = nil; + NSTimeInterval remaining; + while (result == nil && (remaining = [untilDate timeIntervalSinceNow]) > 0) { -+ /* Display-link and input sources have no CF timer deadline. A 20ms -+ * fallback can sleep past every 60Hz source wakeup; bound its latency -+ * below a refresh interval while the socket wakeup path is polled. */ -+ NSTimeInterval slice = remaining < 0.004 ? remaining : 0.004; ++ /* Display-link and input sources have no CF timer deadline. Bound the ++ * idle-poll latency below half a 144Hz frame (~3.5ms) so input events ++ * are dispatched promptly at high refresh rates. */ ++ NSTimeInterval slice = remaining < 0.002 ? remaining : 0.002; + /* A quiet X connection must not prevent main-queue callbacks or + * CF timers from running while AppKit waits for its next event. */ + CFStringRef cfMode = (mode == nil || [mode isEqual:NSDefaultRunLoopMode]) @@ -3593,7 +3593,7 @@ index 5ae29bf4..b98eff64 100644 NSLog(@"FocusIn"); if ([delegate attachedSheet]) { [[delegate attachedSheet] makeKeyAndOrderFront: delegate]; -@@ -1243,28 +2033,61 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1243,28 +2033,74 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { checkForAppDeactivation: NO]; lastFocusedWindow = nil; } @@ -3621,8 +3621,21 @@ index 5ae29bf4..b98eff64 100644 + rclolFocusDetailName(ev->xfocus.detail), _relativeRequested, _relativeMouse.active, + (unsigned long) _confinedWindow, + [NSApp isActive]); -+ if (ev->xfocus.detail == NotifyPointer || ev->xfocus.detail == NotifyInferior || ev->xfocus.mode == NotifyGrab || ev->xfocus.mode == NotifyUngrab) { -+ /* Pointer boundary crossing or grab transition in XWayland: do NOT release confinement or deactivate window */ ++ if (ev->xfocus.detail == NotifyPointer || ev->xfocus.detail == NotifyInferior) { ++ /* Pointer boundary crossing within the same top-level: ignore completely. */ ++ break; ++ } ++ if (ev->xfocus.mode == NotifyGrab || ev->xfocus.mode == NotifyUngrab) { ++ /* A window manager grab (e.g. Alt-Tab switcher): release the pointer ++ * so the cursor can move to other windows, but don't deactivate the ++ * app — FocusIn will re-establish confinement if focus returns. */ ++ [self suspendRelativeMouse]; ++ if (_confinedWindow) { ++ XUngrabPointer(_display, CurrentTime); ++ _confinedWindow = None; ++ _confinedWidth = 0; ++ _confinedHeight = 0; ++ } + break; + } NSLog(@"FocusOut"); @@ -3657,7 +3670,7 @@ index 5ae29bf4..b98eff64 100644 O2Rect rect = NSMakeRect(ev->xexpose.x, ev->xexpose.y, ev->xexpose.width, ev->xexpose.height); -@@ -1301,11 +2124,23 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1301,11 +2137,23 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { break; case UnmapNotify: @@ -3685,7 +3698,7 @@ index 5ae29bf4..b98eff64 100644 break; case MapRequest: -@@ -1317,10 +2152,15 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1317,10 +2165,15 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { break; case ConfigureNotify: @@ -3701,7 +3714,7 @@ index 5ae29bf4..b98eff64 100644 break; case ConfigureRequest: -@@ -1344,6 +2184,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1344,6 +2197,8 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { break; case PropertyNotify: @@ -3710,7 +3723,7 @@ index 5ae29bf4..b98eff64 100644 if ([window respondsToSelector: @selector(propertyNotify:)]) { [window propertyNotify: &ev->xproperty]; } -@@ -1399,7 +2241,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1399,7 +2254,9 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { break; default: @@ -3721,7 +3734,7 @@ index 5ae29bf4..b98eff64 100644 [self _invalidateRRCache]; break; } -@@ -1433,6 +2277,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { +@@ -1433,6 +2290,10 @@ static NSDictionary *modeInfoToDictionary(const XRRModeInfo *mi, int depth) { NSLog(@"Request code: %d:%d", errorEvent->request_code, errorEvent->minor_code); NSLog(@"Error code: %d", errorEvent->error_code); @@ -3732,7 +3745,7 @@ index 5ae29bf4..b98eff64 100644 return 0; } -@@ -1476,54 +2324,286 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top, +@@ -1476,54 +2337,286 @@ void CGNativeBorderFrameWidthsForStyle(NSUInteger styleMask, CGFloat *top, return frame; } @@ -3778,8 +3791,8 @@ index 5ae29bf4..b98eff64 100644 - position.x, position.y); - XSync(_display, False); + [self warpPointerToRootX: x y: y]; -+} -+ + } + +/* Confinement preserves absolute motion and edge panning. Relative mode holds + * its own grab and is left alone while requested. */ +- (void) updateMouseConfinementForWindow:(X11Window *)window { @@ -3929,8 +3942,8 @@ index 5ae29bf4..b98eff64 100644 + _confinedWidth = attrs.width; + _confinedHeight = attrs.height; + } - } - ++} ++ +/* Takes the relative-mode pointer grab on the key (else main) window, starting with the virtual cursor at the + * current pointer position. Returns the XGrabPointer result, or -1 without a window. */ +- (int) beginRelativeMouseWithWindow: (NSWindow **) outWindow pick: (const char **) outPick { diff --git a/patches/darling.patch b/patches/darling.patch index 312f184..4994591 100644 --- a/patches/darling.patch +++ b/patches/darling.patch @@ -3557,7 +3557,7 @@ diff --git a/src/frameworks/CoreVideo/src/CVDisplayLink.mm b/src/frameworks/Core index 3e9cba226..99e0a5494 100644 --- a/src/frameworks/CoreVideo/src/CVDisplayLink.mm +++ b/src/frameworks/CoreVideo/src/CVDisplayLink.mm -@@ -1,38 +1,878 @@ +@@ -1,38 +1,884 @@ #include +#include +#include @@ -4377,8 +4377,14 @@ index 3e9cba226..99e0a5494 100644 +static void *displayClockThread(void *) { + struct { long sec, nsec; } deadline; + if (__lolsys_syscall(1,(long)&deadline,0,0,0,0,228)<0) return NULL; ++ static uint64_t cvIntervalNS = 0; ++ if (!cvIntervalNS) { ++ const char *hz = getenv("RCLOL_REFRESH_HZ"); ++ int rate = hz ? atoi(hz) : 0; ++ cvIntervalNS = (rate > 0 && rate <= 1000) ? 1000000000ull / (uint64_t)rate : (1000000000ull / 144ull); ++ } + for (;;) { -+ deadline.nsec += NSEC_PER_SEC / 60; ++ deadline.nsec += cvIntervalNS; + if(deadline.nsec>=NSEC_PER_SEC){deadline.nsec-=NSEC_PER_SEC;++deadline.sec;} + long result; + do { result=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230); } while(result==-4); @@ -4386,7 +4392,7 @@ index 3e9cba226..99e0a5494 100644 + dispatchTimerFire(NULL); + struct { long sec, nsec; } now; + if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0 && -+ (now.sec>deadline.sec || (now.sec==deadline.sec && now.nsec-deadline.nsec>NSEC_PER_SEC/60))) { ++ (now.sec>deadline.sec || (now.sec==deadline.sec && (uint64_t)(now.nsec-deadline.nsec)>cvIntervalNS))) { + deadline.sec=now.sec;deadline.nsec=now.nsec; + } + } diff --git a/patches/xnu.patch b/patches/xnu.patch index 0bd1ad0..6f1b399 100644 --- a/patches/xnu.patch +++ b/patches/xnu.patch @@ -5944,7 +5944,7 @@ index b998456..2783a4a 100644 + * refresh rates. Use sched_yield for cooperative yields, and only sleep + * when a timed wait was explicitly requested. */ + if (option_time == 0) { -+ __linux_sched_yield(); ++ __linux_sched_yield_site("THRSWT"); + } else { + __linux_nanosleep(&tv, &tv); + } diff --git a/tools/ensure-darling-runtime.py b/tools/ensure-darling-runtime.py index d475114..981c0a9 100644 --- a/tools/ensure-darling-runtime.py +++ b/tools/ensure-darling-runtime.py @@ -2,6 +2,7 @@ """Clear only a dead Darling server's stale PID marker and buffers before startup.""" import os from pathlib import Path +import subprocess import time prefix = Path(os.environ.get('DPREFIX', str(Path.home() / '.darling'))) @@ -31,6 +32,11 @@ if marker.exists(): # Only clean up stale Riot temporary files and shared memory frames if the server is NOT running if not server_running: + try: + subprocess.run(['killall', '-q', 'chrome_crashpad_handler'], check=False) + except Exception: + pass + try: tmp_dir = prefix / 'private/tmp' if tmp_dir.is_dir(): diff --git a/tools/install-riot-gpu-runtime.sh b/tools/install-riot-gpu-runtime.sh index 4914e40..68f887f 100755 --- a/tools/install-riot-gpu-runtime.sh +++ b/tools/install-riot-gpu-runtime.sh @@ -32,7 +32,9 @@ build/riot-host-message-check cc -shared -fPIC -O2 -Wall -Wextra -Wl,-soname,"$project_dir/build/libdarling-xshm.so" tools/m58f299-xshm.c -o build/libdarling-xshm.so.next -pthread $(pkg-config --cflags --libs x11 x11-xcb xcb-shm xext) python3 tools/stop-darling-runtime.py # Host fileports own SCM_RIGHTS descriptors and must die with their last sender. -sudo make -C "$project_dir/build/darling-src/build/src/external/darlingserver" install -j12 +if sudo -n true 2>/dev/null; then + sudo make -C "$project_dir/build/darling-src/build/src/external/darlingserver" install -j12 +fi # Electron's window-visibility correction lives in the AppKit overlay # (NSWindow -occlusionState, cocotron.patch); no Riot binary is modified here. for name in IOSurface OpenGL QuartzCore CoreVideo CoreAudio AudioToolbox CoreGraphics CoreText CoreFoundation Metal; do diff --git a/tools/riot-desktop-entry.py b/tools/riot-desktop-entry.py index 51d6a74..477b575 100755 --- a/tools/riot-desktop-entry.py +++ b/tools/riot-desktop-entry.py @@ -142,13 +142,14 @@ def main(): # 3. Create .desktop file run_script = PROJECT_ROOT / "scripts/run.sh" exec_path = str(run_script).replace("%", "%%") + exec_cmd = f'"{exec_path}"' if " " in exec_path else exec_path desktop_content = f"""[Desktop Entry] Version=1.0 Type=Application Name=League of Legends GenericName=MOBA Game Comment=Play League of Legends on Linux with darling-lol -Exec="{exec_path}" +Exec={exec_cmd} Path={PROJECT_ROOT} Icon=league-of-legends Terminal=false diff --git a/tools/run-riot-client.sh b/tools/run-riot-client.sh index 8781842..bf0474a 100755 --- a/tools/run-riot-client.sh +++ b/tools/run-riot-client.sh @@ -61,6 +61,12 @@ else fi fi # --- darling-lol perf: scheduling & driver latency hints --- +# Refresh rate for display link, async presenter, and frame pacing (default 144Hz) +export RCLOL_REFRESH_HZ="${RCLOL_REFRESH_HZ:-144}" +# Vulkan WSI & Mesa low-latency overrides +export MESA_VK_WSI_PRESENT_MODE="${MESA_VK_WSI_PRESENT_MODE:-immediate}" +export vblank_mode="${vblank_mode:-0}" +export MESA_NO_ERROR="${MESA_NO_ERROR:-1}" # NVIDIA proprietary driver: limit pre-rendered frames. export __GL_MaxFramesAllowed="${__GL_MaxFramesAllowed:-1}" export __GL_THREADED_OPTIMIZATIONS="${__GL_THREADED_OPTIMIZATIONS:-1}"