mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Merge pull request #19 from lucannez64/pr16-fixes-v2
Integrate runtime fixes with fullscreen and visible-cursor repairs
This commit is contained in:
commit
e490e2e353
38 files changed
+2062
-547
No files matched your search
@@ -11,3 +11,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.12'
|
python-version: '3.12'
|
||||||
- run: python scripts/validate.py
|
- run: python scripts/validate.py
|
||||||
|
- run: python tools/riot-input-offline-check.py
|
||||||
|
- run: python tools/riot-runtime-cleanup-offline-check.py
|
||||||
+3
-1
@@ -18,6 +18,8 @@ The build uses Unix Makefiles because the incremental runtime helpers read CMake
|
|||||||
|
|
||||||
The supported build is x86-64 only (`TARGET_i386=OFF`). The patched OpenDirectory uses Objective-C ivars that the 32-bit fragile ABI cannot compile. Keep the empty `CMAKE_BUILD_TYPE` used by `scripts/build.sh`: selecting `Release` introduces `NDEBUG` while objc4 defines `OBJC_IS_DEBUG_BUILD=1`, causing a debug-macro mismatch. The base patch requests the macOS 10.15 Objective-C runtime code generation to avoid undefined `__NSArray0__struct` / `__NSDictionary0__struct` symbols with newer Clang. Indium handles both the LLVM 22 and LLVM 23 branch opcode names.
|
The supported build is x86-64 only (`TARGET_i386=OFF`). The patched OpenDirectory uses Objective-C ivars that the 32-bit fragile ABI cannot compile. Keep the empty `CMAKE_BUILD_TYPE` used by `scripts/build.sh`: selecting `Release` introduces `NDEBUG` while objc4 defines `OBJC_IS_DEBUG_BUILD=1`, causing a debug-macro mismatch. The base patch requests the macOS 10.15 Objective-C runtime code generation to avoid undefined `__NSArray0__struct` / `__NSDictionary0__struct` symbols with newer Clang. Indium handles both the LLVM 22 and LLVM 23 branch opcode names.
|
||||||
|
|
||||||
|
`scripts/build.sh` compiles the whole tree with `-O3 -march=native -mtune=generic`, so the binaries only run on CPUs with the build machine's instruction set. Keep `-mtune=generic`: clang 23 tuning for AMD Zen (`-mtune=native` or `-mtune=znver*`) miscompiles Darwin thread-local variables, which crashes launchd at boot (see [troubleshooting](TROUBLESHOOTING.md#launchd-crashes-at-boot-after-changing-compiler-flags)). Two kernel files keep `-O0` because they depend on the unoptimized stack layout: `bsdthread_register.c` and `sigexc.c`.
|
||||||
|
|
||||||
Linux 5.14 or newer is required for `MADV_POPULATE_WRITE`, used when Darwin's allocator reuses lazily reclaimable pages. The memory installer checks this requirement; do not bypass it.
|
Linux 5.14 or newer is required for `MADV_POPULATE_WRITE`, used when Darwin's allocator reuses lazily reclaimable pages. The memory installer checks this requirement; do not bypass it.
|
||||||
|
|
||||||
The runtime installer requires native `pkg-config` entries for `x11`, `x11-xcb`, `xcb-shm` and `xext`, plus the base Darling SDK.
|
The runtime installer requires native `pkg-config` entries for `x11`, `x11-xcb`, `xcb-shm` and `xext`, plus the base Darling SDK.
|
||||||
@@ -69,7 +71,7 @@ sh build/runtime/tools/build-riot-libs-runtime.sh
|
|||||||
python3 build/runtime/tools/install-riot-libs-runtime.py
|
python3 build/runtime/tools/install-riot-libs-runtime.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The builders recompile libsystem_c (618 of its 641 objects), libc++ and libc++abi (38 and 18) and the libsystem_platform string primitives at `-O2`, each object with its own configured defines, includes and per-file options, and relink with the configured link line. `-fno-strict-aliasing` is added for this old BSD C and no `-DNDEBUG`, `-ffast-math` or LTO is. Files whose correctness depends on frame layout, on running in a forked child, or on the floating-point environment keep their configured flags: the fork paths and the stack guard handler in libc and the frame-pointer backtrace walker. libsystem_m is not rebuilt: its sources are built at `-O0` upstream, and an `-O2` rebuild made the Riot client UI render a black window. The libkqueue objects libsystem_c also links belong to the kernel library's target and are left alone. Each builder stops while a League game process is running, because compiling would skew the FPS the user measures.
|
The builders recompile libsystem_c (618 of its 641 objects), libc++ and libc++abi (38 and 18) and the libsystem_platform string primitives at `-O3 -march=native -mtune=generic`, each object with its own configured defines, includes and per-file options, and relink with the configured link line. `-fno-strict-aliasing` is added for this old BSD C and no `-DNDEBUG`, `-ffast-math` or LTO is. Files whose correctness depends on frame layout, on running in a forked child, or on the floating-point environment keep their configured flags: the fork paths and the stack guard handler in libc and the frame-pointer backtrace walker. libsystem_m is not rebuilt: its sources are built at `-O0` upstream, and an `-O2` rebuild made the Riot client UI render a black window. The libkqueue objects libsystem_c also links belong to the kernel library's target and are left alone. Each builder stops while a League game process is running, because compiling would skew the FPS the user measures. `scripts/install-runtime.sh` builds and installs these overlays, then runs `tools/check-darwin-tlv-calls.py` on everything in `RuntimeLibraries` and `RuntimeFrameworks` and fails if any binary has the thread-local miscompile.
|
||||||
|
|
||||||
The installer writes the four files into `~/.darling/Users/<user>/RuntimeLibraries`, the directory the launcher puts in `DYLD_LIBRARY_PATH`; dyld searches those directories by leaf name before the install path, which is why an overlay there replaces the prefix and SDK copies of `libsystem_c.dylib`, `libc++.1.dylib`, `libc++abi.dylib` and `libsystem_platform.dylib` without touching either. Every replaced file is backed up first (`build/libs-runtime-backups`), the staged file is verified against the built artifact, and the rename is atomic. The installer never stops a process, so a running guest keeps the library it already loaded; exit the game and both clients and restart the namespace for activation. One command restores the previous state:
|
The installer writes the four files into `~/.darling/Users/<user>/RuntimeLibraries`, the directory the launcher puts in `DYLD_LIBRARY_PATH`; dyld searches those directories by leaf name before the install path, which is why an overlay there replaces the prefix and SDK copies of `libsystem_c.dylib`, `libc++.1.dylib`, `libc++abi.dylib` and `libsystem_platform.dylib` without touching either. Every replaced file is backed up first (`build/libs-runtime-backups`), the staged file is verified against the built artifact, and the rename is atomic. The installer never stops a process, so a running guest keeps the library it already loaded; exit the game and both clients and restart the namespace for activation. One command restores the previous state:
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -434,7 +434,7 @@ build/test-x11-relative-mouse
|
|||||||
The native probe requires the existing per-user AppKit and CoreGraphics overlays
|
The native probe requires the existing per-user AppKit and CoreGraphics overlays
|
||||||
and a running X11/Xwayland display. It rebuilds the changed X11 and CoreGraphics
|
and a running X11/Xwayland display. It rebuilds the changed X11 and CoreGraphics
|
||||||
objects and tests them in a temporary framework overlay, without installing them.
|
objects and tests them in a temporary framework overlay, without installing them.
|
||||||
Six `DISPLAY_GEOMETRY ...=pass` lines are required; full output is saved in
|
Seven `DISPLAY_GEOMETRY ...=pass` lines are required; full output is saved in
|
||||||
`build/pr12-display-check.log`.
|
`build/pr12-display-check.log`.
|
||||||
|
|
||||||
It checks the live primary screen's origin, synthetic single-monitor and offset
|
It checks the live primary screen's origin, synthetic single-monitor and offset
|
||||||
@@ -445,8 +445,12 @@ relative deltas, both RandR cache-invalidation events, and the root-screen fallb
|
|||||||
Warps are intercepted: the probe does not move the desktop pointer or send game
|
Warps are intercepted: the probe does not move the desktop pointer or send game
|
||||||
input. Synthetic layouts exercise coordinate conversion and display API behavior;
|
input. Synthetic layouts exercise coordinate conversion and display API behavior;
|
||||||
they do not simulate physical monitor hotplug or validate League rendering.
|
they do not simulate physical monitor hotplug or validate League rendering.
|
||||||
Fullscreen policy is unchanged. Manually verify game launch, window placement,
|
The probe also checks that managed borderless windows covering a whole monitor
|
||||||
click alignment and edge panning on the affected monitor layout before rollout.
|
request EWMH fullscreen, including on secondary monitors, and clear the property
|
||||||
|
when resized before mapping. Titled windows, unmanaged popups and windows sized
|
||||||
|
to the panel-reduced work area are excluded. Mapped-window transitions and panel
|
||||||
|
stacking require manual verification under KDE Plasma/Xwayland. Also verify game
|
||||||
|
launch, window placement, click alignment and edge panning before rollout.
|
||||||
|
|
||||||
After installing the tested X11 and CoreGraphics binaries locally, the user
|
After installing the tested X11 and CoreGraphics binaries locally, the user
|
||||||
reported that everything was working (2026-10-16). Fullscreen policy was unchanged.
|
reported that everything was working (2026-10-16). Fullscreen policy was unchanged.
|
||||||
+57
-1
@@ -156,6 +156,20 @@ A separate multi-window path stopped the active async presenter when another dif
|
|||||||
- **Prebuilt Swift file contains Git LFS pointer text:** install Git LFS and fetch the original upstream assets; pointer text is not a usable library.
|
- **Prebuilt Swift file contains Git LFS pointer text:** install Git LFS and fetch the original upstream assets; pointer text is not a usable library.
|
||||||
- **Submodule fetch stops with no output right after `src/external/sqlite`:** the next submodule is `src/external/swift`, whose pinned `.lfsconfig` points at the retired `git-lfs.darlinghq.org`. The checkout is blocked on that host, not on sqlite. `prepare.py` redirects that submodule's LFS requests to GitHub. To finish a fetch that was interrupted before any patches were applied, run `git -C build/darling-src -c lfs.url=https://github.com/darlinghq/darling-swift.git/info/lfs submodule update --init --recursive --progress`, then re-run `./scripts/build.sh`. Do not run that submodule update once `prepare.py` has applied patches: it resets every submodule working tree to the pinned revision and discards the patches.
|
- **Submodule fetch stops with no output right after `src/external/sqlite`:** the next submodule is `src/external/swift`, whose pinned `.lfsconfig` points at the retired `git-lfs.darlinghq.org`. The checkout is blocked on that host, not on sqlite. `prepare.py` redirects that submodule's LFS requests to GitHub. To finish a fetch that was interrupted before any patches were applied, run `git -C build/darling-src -c lfs.url=https://github.com/darlinghq/darling-swift.git/info/lfs submodule update --init --recursive --progress`, then re-run `./scripts/build.sh`. Do not run that submodule update once `prepare.py` has applied patches: it resets every submodule working tree to the pinned revision and discards the patches.
|
||||||
|
|
||||||
|
## launchd crashes at boot after changing compiler flags
|
||||||
|
|
||||||
|
`darling shell` fails with `Error connecting to shellspawn` and `/tmp/lolsig.log` shows `LOLSIGFAIL pid=1 sig=11 code=1 addr=0x0` in `memmove`, called from `snprintf` in libkqueue's `epoll_event_dump`. The binaries were tuned for AMD Zen: clang 23 with `-mtune=znver*` (also selected by `-march=native` or `-mtune=native` on Zen) loads a thread-local variable's descriptor into a register other than `%rdi`, but dyld's `tlv_get_addr` reads `%rdi`, so every `__thread` and `thread_local` access returns NULL. Rebuild with `-mtune=generic` (the default in `scripts/build.sh` and the overlay builders), and check any binary with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tools/check-darwin-tlv-calls.py <binary>...
|
||||||
|
```
|
||||||
|
|
||||||
|
It prints each thread-local access that calls its thunk without the descriptor in `%rdi` and exits 1. A plain `darling shell` boot does not load the `RuntimeLibraries` overlays; to boot the way the launcher does, run `env DYLD_LIBRARY_PATH=/Users/<user>/RuntimeLibraries DYLD_FRAMEWORK_PATH=/Users/<user>/RuntimeFrameworks darling shell /usr/bin/true`.
|
||||||
|
|
||||||
|
## Base install refuses to continue while Darling is not running
|
||||||
|
|
||||||
|
`sudo cmake --install` prints `Darling currently running for <user>, shutting it down...` several times and then `Darling launchd PID … remains alive after shutdown`. A guest launchd outlived its darlingserver, so `darling shutdown` sees no container to stop. `python3 build/runtime/tools/ensure-darling-runtime.py` stops such a launchd (your own, only when no darlingserver runs); `scripts/run.sh` and `scripts/install-runtime.sh` run it too.
|
||||||
|
|
||||||
## Lower fight FPS than Windows
|
## Lower fight FPS than Windows
|
||||||
|
|
||||||
The game code itself runs natively under Darling: the x86-64 slice of the universal binary executes unchanged, picks its instruction-set paths with `cpuid` on the real CPU, and sizes its worker pool from the real logical CPU count. What differs from Windows is the path around it. Every Metal call goes through Objective-C, the Metal wrapper, Indium and Vulkan instead of a native Direct3D driver, and kernel services go through the emulation and darlingserver. In an ARAM fight the game's own code now takes most of a frame; see [status](STATUS.md#game-fps-2026-10-1314) for measurements.
|
The game code itself runs natively under Darling: the x86-64 slice of the universal binary executes unchanged, picks its instruction-set paths with `cpuid` on the real CPU, and sizes its worker pool from the real logical CPU count. What differs from Windows is the path around it. Every Metal call goes through Objective-C, the Metal wrapper, Indium and Vulkan instead of a native Direct3D driver, and kernel services go through the emulation and darlingserver. In an ARAM fight the game's own code now takes most of a frame; see [status](STATUS.md#game-fps-2026-10-1314) for measurements.
|
||||||
@@ -232,10 +246,40 @@ The X11 backend previously called `XSync` after every cursor-shape update, waiti
|
|||||||
|
|
||||||
A later input trace showed League setting the cursor about 190 times and unhiding it about 100 times per second, each time sending the same cursor to the X server. The backend now skips the update when the window already has that cursor. The effect on latency has not been measured.
|
A later input trace showed League setting the cursor about 190 times and unhiding it about 100 times per second, each time sending the same cursor to the X server. The backend now skips the update when the window already has that cursor. The effect on latency has not been measured.
|
||||||
|
|
||||||
The backend's relative mouse mode (`CGAssociateMouseAndMouseCursorPosition(false)`) previously warped the pointer to the window center, treated the game's own cursor warps as motion and reported `deltaY` upside down. It now follows macOS: the cursor stays where it is, events carry the physical motion as deltas (positive `deltaY` downwards, also for ordinary motion), the game's warps move the cursor without a delta, and `+[NSEvent mouseLocation]` returns that cursor. On Xwayland a warp takes effect only while the cursor is hidden, so with a visible cursor the pointer is not held in place. `tools/test-x11-relative-mouse.c` checks this logic on the host (`cc tools/test-x11-relative-mouse.c && ./a.out` after `scripts/prepare.py`). League on the tested Hyprland setup never enters this mode, so it has not been confirmed in game.
|
The backend's relative mouse mode (`CGAssociateMouseAndMouseCursorPosition(false)`) previously warped the pointer to the window center, treated the game's own cursor warps as motion and reported `deltaY` upside down. It now follows macOS: the cursor stays where it is, events carry the physical motion as deltas (positive `deltaY` downwards, also for ordinary motion), the game's warps move the cursor without a delta, and `+[NSEvent mouseLocation]` returns that cursor. On Xwayland a warp takes effect only while the cursor is hidden, so with a visible cursor the pointer is not held in place. `python3 tools/riot-input-offline-check.py` checks the shipped patch on the host without fetching Darling. Add `--xvfb /path/to/Xvfb` to exercise real Xorg protocol on an isolated virtual display; it never uses your desktop display. For Xorg/XLibre (issue #17), relative capture now starts from a fresh `XQueryPointer` snapshot and ignores older queued motion. Warp echoes match both their X request serial and position, so a real motion crossing the anchor cannot be mistaken for an echo. Application warp targets are clipped to the grabbed window in root coordinates, including on a secondary monitor; events, `mouseLocation`, and rewarps share that target. Queued motion before a pending warp does not generate another rewarp. A failed pointer query leaves relative capture suspended, and focus loss releases the grab until the window receives keyboard focus again. Host regression tests cover these paths, but League on the tested Hyprland setup never enters this mode; in-game confirmation on XLibre is still required.
|
||||||
|
|
||||||
For a manual comparison, keep the scene, FPS cap, display refresh and compositor settings the same. Record whether the delay affects the cursor itself, camera movement, or actions after clicking, and compare the in-game VSync setting on and off. Include the desktop/compositor, X11 or Xwayland session, GPU/driver, monitor refresh and fullscreen/windowed mode in the report. Do not automate game input. The fullscreen cursor-shape problem below is a separate known symptom.
|
For a manual comparison, keep the scene, FPS cap, display refresh and compositor settings the same. Record whether the delay affects the cursor itself, camera movement, or actions after clicking, and compare the in-game VSync setting on and off. Include the desktop/compositor, X11 or Xwayland session, GPU/driver, monitor refresh and fullscreen/windowed mode in the report. Do not automate game input. The fullscreen cursor-shape problem below is a separate known symptom.
|
||||||
|
|
||||||
|
## Click offset grows towards the bottom of the screen
|
||||||
|
|
||||||
|
A growing vertical offset has been reported on KDE Plasma/Wayland with Xwayland.
|
||||||
|
The cause is unconfirmed, including whether it is related to commit `426fb75`.
|
||||||
|
The borderless fullscreen request has been restored separately; retest alignment
|
||||||
|
once the game covers the panel.
|
||||||
|
|
||||||
|
If it persists, enable the passive input trace before the next manual game launch:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
darling shell touch /tmp/rclol-input-trace
|
||||||
|
```
|
||||||
|
|
||||||
|
The flag is checked once per process, so enable it before launching the game.
|
||||||
|
During a short manual test, compare clicks near the top, middle and bottom, and
|
||||||
|
note the direction and approximate size of the error. Include the in-game
|
||||||
|
resolution and window mode, Plasma's scale setting for each monitor, and the
|
||||||
|
host output of `xrandr --listmonitors`. Collect the guest log with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
darling shell cat /tmp/rclol-input-trace.log > rclol-input-trace.log
|
||||||
|
darling shell rm -f /tmp/rclol-input-trace
|
||||||
|
```
|
||||||
|
|
||||||
|
Removing the flag disables tracing for subsequently started processes. The log
|
||||||
|
records motion, warps, pointer-position queries and relative-mode transitions;
|
||||||
|
it can establish whether the relative path is active, but cannot by itself
|
||||||
|
establish where the game rendered a clicked target. If practical, compare the
|
||||||
|
same resolution in a smaller window and in borderless fullscreen.
|
||||||
|
|
||||||
## Cursor stops changing shape in fullscreen
|
## Cursor stops changing shape in fullscreen
|
||||||
|
|
||||||
On Hyprland the game's cursor can keep its first shape (for example, it never switches to the attack cursor) until you leave and re-enter fullscreen. Darling sends every change to the X server; what freezes the shape on screen is Hyprland's direct scanout of the fullscreen Xwayland window. Turn it off in `hyprland.conf`:
|
On Hyprland the game's cursor can keep its first shape (for example, it never switches to the attack cursor) until you leave and re-enter fullscreen. Darling sends every change to the X server; what freezes the shape on screen is Hyprland's direct scanout of the fullscreen Xwayland window. Turn it off in `hyprland.conf`:
|
||||||
@@ -247,3 +291,15 @@ render {
|
|||||||
```
|
```
|
||||||
|
|
||||||
`hyprctl keyword render:direct_scanout 0` applies it until Hyprland restarts.
|
`hyprctl keyword render:direct_scanout 0` applies it until Hyprland restarts.
|
||||||
|
|
||||||
|
### Black layer over a running Metal game
|
||||||
|
|
||||||
|
The X11 Chromium presenter searches shared Core Animation frame slots by surface size. Native League game windows now bypass that presenter, so a same-size client frame cannot create a presentation child over the game's Metal drawable. This also disables the remote-frame polling timer for the game while preserving Chromium client presentation.
|
||||||
|
|
||||||
|
`tools/riot-remote-frame-ownership-check.m` exercises the cached-frame path without creating a display or window. `tools/build-riot-x11-runtime.py` builds this probe. For an unidentified overlay, `python3 tools/trace-riot-windows.py --output build/riot-window-trace.jsonl` passively records Riot/League windows, including unmanaged windows and child stacking, for one hour. It requires the host `python-xlib` package and does not send input.
|
||||||
|
|
||||||
|
If a black fullscreen game has only its normal Metal child and becomes visible after removing and restoring fullscreen, inspect the initial fullscreen transition. Automatic fullscreen now requires the window to cover an active monitor, so temporary 1x1 startup windows and ordinary windowed sizes are not promoted. Explicit Cocoa fullscreen requests are preserved. `riot-fullscreen-geometry-check` (built by the X11 builder) checks the policy against active XRandR monitor geometry using only read requests; the next game launch is still required to validate the startup sequence.
|
||||||
|
|
||||||
|
### Visible cursor while relative on Xwayland (PR #18)
|
||||||
|
|
||||||
|
When Xwayland does not honour warps, the virtual cursor follows real pointer motion and application warps leave it in place. Expected warp echoes are still discarded using their request serial and coordinates. This keeps clicks and camera edge panning aligned with the visible cursor on GNOME/gamescope. The contributor confirmed this path in a gamescope practice-tool session; plain GNOME and NVIDIA still require manual verification.
|
||||||
+837
-424
File diff suppressed because it is too large.
Load diff
+42
-17
@@ -3557,7 +3557,7 @@ diff --git a/src/frameworks/CoreVideo/src/CVDisplayLink.mm b/src/frameworks/Core
|
|||||||
index 3e9cba226..99e0a5494 100644
|
index 3e9cba226..99e0a5494 100644
|
||||||
--- a/src/frameworks/CoreVideo/src/CVDisplayLink.mm
|
--- a/src/frameworks/CoreVideo/src/CVDisplayLink.mm
|
||||||
+++ b/src/frameworks/CoreVideo/src/CVDisplayLink.mm
|
+++ b/src/frameworks/CoreVideo/src/CVDisplayLink.mm
|
||||||
@@ -1,38 +1,878 @@
|
@@ -1,38 +1,887 @@
|
||||||
#include <CoreVideo/CVDisplayLink.h>
|
#include <CoreVideo/CVDisplayLink.h>
|
||||||
+#include <stdint.h>
|
+#include <stdint.h>
|
||||||
+#include <string.h>
|
+#include <string.h>
|
||||||
@@ -4377,17 +4377,26 @@ index 3e9cba226..99e0a5494 100644
|
|||||||
+static void *displayClockThread(void *) {
|
+static void *displayClockThread(void *) {
|
||||||
+ struct { long sec, nsec; } deadline;
|
+ struct { long sec, nsec; } deadline;
|
||||||
+ if (__lolsys_syscall(1,(long)&deadline,0,0,0,0,228)<0) return NULL;
|
+ 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 / 60ull);
|
||||||
|
+ }
|
||||||
+ for (;;) {
|
+ for (;;) {
|
||||||
+ deadline.nsec += NSEC_PER_SEC / 60;
|
+ deadline.nsec += cvIntervalNS;
|
||||||
+ if(deadline.nsec>=NSEC_PER_SEC){deadline.nsec-=NSEC_PER_SEC;++deadline.sec;}
|
+ if(deadline.nsec>=NSEC_PER_SEC){deadline.nsec-=NSEC_PER_SEC;++deadline.sec;}
|
||||||
+ long result;
|
+ long result;
|
||||||
+ do { result=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230); } while(result==-4);
|
+ do { result=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230); } while(result==-4);
|
||||||
+ if(result<0) return NULL;
|
+ if(result<0) return NULL;
|
||||||
+ dispatchTimerFire(NULL);
|
+ dispatchTimerFire(NULL);
|
||||||
+ struct { long sec, nsec; } now;
|
+ struct { long sec, nsec; } now;
|
||||||
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0 &&
|
+ 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))) {
|
+ long long diffNS = ((long long)now.sec - (long long)deadline.sec) * 1000000000LL + (now.nsec - deadline.nsec);
|
||||||
+ deadline.sec=now.sec;deadline.nsec=now.nsec;
|
+ if (diffNS > (long long)cvIntervalNS) {
|
||||||
|
+ deadline.sec = now.sec;
|
||||||
|
+ deadline.nsec = now.nsec;
|
||||||
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+}
|
+}
|
||||||
@@ -4444,7 +4453,7 @@ index 3e9cba226..99e0a5494 100644
|
|||||||
*displayLinkOut = (CVDisplayLinkRef) self;
|
*displayLinkOut = (CVDisplayLinkRef) self;
|
||||||
return kCVReturnSuccess;
|
return kCVReturnSuccess;
|
||||||
}
|
}
|
||||||
@@ -41,35 +881,128 @@ CVReturn CVDisplayLinkStart(CVDisplayLinkRef displayLink)
|
@@ -41,35 +890,128 @@ CVReturn CVDisplayLinkStart(CVDisplayLinkRef displayLink)
|
||||||
{
|
{
|
||||||
if (!displayLink)
|
if (!displayLink)
|
||||||
return kCVReturnInvalidArgument;
|
return kCVReturnInvalidArgument;
|
||||||
@@ -4584,7 +4593,7 @@ index 3e9cba226..99e0a5494 100644
|
|||||||
return kCVReturnSuccess;
|
return kCVReturnSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +1024,7 @@ CVReturn CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(CVDisplayLinkRef disp
|
@@ -91,7 +1033,7 @@ CVReturn CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(CVDisplayLinkRef disp
|
||||||
CGDirectDisplayID displayID = [window.screen cgDirectDisplayID];
|
CGDirectDisplayID displayID = [window.screen cgDirectDisplayID];
|
||||||
NSMutableDictionary* self = (NSMutableDictionary*) displayLink;
|
NSMutableDictionary* self = (NSMutableDictionary*) displayLink;
|
||||||
|
|
||||||
@@ -4593,7 +4602,7 @@ index 3e9cba226..99e0a5494 100644
|
|||||||
forKey: kDirectDisplayArray];
|
forKey: kDirectDisplayArray];
|
||||||
return kCVReturnSuccess;
|
return kCVReturnSuccess;
|
||||||
}
|
}
|
||||||
@@ -117,14 +1050,31 @@ CVReturn CVDisplayLinkCreateWithCGDisplay(
|
@@ -117,14 +1059,31 @@ CVReturn CVDisplayLinkCreateWithCGDisplay(
|
||||||
CGDirectDisplayID displayID,
|
CGDirectDisplayID displayID,
|
||||||
CV_RETURNS_RETAINED_PARAMETER CVDisplayLinkRef CV_NULLABLE * CV_NONNULL displayLinkOut )
|
CV_RETURNS_RETAINED_PARAMETER CVDisplayLinkRef CV_NULLABLE * CV_NONNULL displayLinkOut )
|
||||||
{
|
{
|
||||||
@@ -6248,7 +6257,7 @@ index deca16b87..d75d5e22d 100644
|
|||||||
|
|
||||||
set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS
|
set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS
|
||||||
diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c
|
diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c
|
||||||
index ffd7fe054..a74ffc63f 100644
|
index ffd7fe054..1ff212d34 100644
|
||||||
--- a/src/frameworks/OpenGL/OpenGL.c
|
--- a/src/frameworks/OpenGL/OpenGL.c
|
||||||
+++ b/src/frameworks/OpenGL/OpenGL.c
|
+++ b/src/frameworks/OpenGL/OpenGL.c
|
||||||
@@ -2,11 +2,22 @@
|
@@ -2,11 +2,22 @@
|
||||||
@@ -6725,7 +6734,7 @@ index ffd7fe054..a74ffc63f 100644
|
|||||||
|
|
||||||
free(context);
|
free(context);
|
||||||
}
|
}
|
||||||
@@ -383,9 +608,329 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
@@ -383,9 +608,330 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
||||||
return kCGLNoError;
|
return kCGLNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6948,12 +6957,13 @@ index ffd7fe054..a74ffc63f 100644
|
|||||||
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0){
|
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0){
|
||||||
+ uint64_t current=(uint64_t)now.tv_sec*1000000000ull+now.tv_nsec;
|
+ uint64_t current=(uint64_t)now.tv_sec*1000000000ull+now.tv_nsec;
|
||||||
+ uint64_t deadline=ctx->native_next_present_ns;
|
+ uint64_t deadline=ctx->native_next_present_ns;
|
||||||
+ if(!deadline || (current>deadline && current-deadline>16666667ull))deadline=current;
|
+ uint64_t paceNS=nativeRefreshIntervalNS();
|
||||||
|
+ if(!deadline || (current>deadline && current-deadline>paceNS))deadline=current;
|
||||||
+ if(deadline>current){
|
+ if(deadline>current){
|
||||||
+ struct timespec wake={deadline/1000000000ull,deadline%1000000000ull};
|
+ struct timespec wake={deadline/1000000000ull,deadline%1000000000ull};
|
||||||
+ while(__lolsys_syscall(1,1,(long)&wake,0,0,0,230)==-4){}
|
+ while(__lolsys_syscall(1,1,(long)&wake,0,0,0,230)==-4){}
|
||||||
+ }
|
+ }
|
||||||
+ ctx->native_next_present_ns=deadline+16666667ull;
|
+ ctx->native_next_present_ns=deadline+paceNS;
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ if(getenv("RCLOL_GPU_DIRECT_ASYNC")){int queued=enqueueAsyncPresent(ctx,binding.texture,binding.target,width,height);if(!queued){stopAsyncPresent(ctx);__atomic_store_n(&target->ready,0,__ATOMIC_RELEASE);}CGLReleaseContext(ctx);return queued;}
|
+ if(getenv("RCLOL_GPU_DIRECT_ASYNC")){int queued=enqueueAsyncPresent(ctx,binding.texture,binding.target,width,height);if(!queued){stopAsyncPresent(ctx);__atomic_store_n(&target->ready,0,__ATOMIC_RELEASE);}CGLReleaseContext(ctx);return queued;}
|
||||||
@@ -7057,7 +7067,7 @@ index ffd7fe054..a74ffc63f 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) {
|
CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) {
|
||||||
@@ -394,8 +939,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
@@ -394,8 +940,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
||||||
|
|
||||||
if (parameter == kCGLCPSwapInterval)
|
if (parameter == kCGLCPSwapInterval)
|
||||||
{
|
{
|
||||||
@@ -7068,7 +7078,7 @@ index ffd7fe054..a74ffc63f 100644
|
|||||||
if (success)
|
if (success)
|
||||||
context->swap_interval = v;
|
context->swap_interval = v;
|
||||||
return success ? kCGLNoError : kCGLBadValue;
|
return success ? kCGLNoError : kCGLBadValue;
|
||||||
@@ -417,14 +963,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
@@ -417,14 +964,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
||||||
return kCGLNoError;
|
return kCGLNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8418,12 +8428,26 @@ new file mode 100644
|
|||||||
+}
|
+}
|
||||||
diff --git a/src/frameworks/OpenGL/CGLAsyncPresent.inc b/src/frameworks/OpenGL/CGLAsyncPresent.inc
|
diff --git a/src/frameworks/OpenGL/CGLAsyncPresent.inc b/src/frameworks/OpenGL/CGLAsyncPresent.inc
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
|
index 000000000..e5363c06a
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/src/frameworks/OpenGL/CGLAsyncPresent.inc
|
+++ b/src/frameworks/OpenGL/CGLAsyncPresent.inc
|
||||||
@@ -0,0 +1,190 @@
|
@@ -0,0 +1,204 @@
|
||||||
+/* Bounded GPU-only staging: the producer never waits for display cadence.
|
+/* Bounded GPU-only staging: the producer never waits for display cadence.
|
||||||
+ * Three textures cover the currently displayed, pending, and writing frames.
|
+ * Three textures cover the currently displayed, pending, and writing frames.
|
||||||
+ * GPU fences order both the completed producer copy and subsequent reuse. */
|
+ * GPU fences order both the completed producer copy and subsequent reuse. */
|
||||||
|
+
|
||||||
|
+/* darling-lol: software refresh period for the paced paths, from RCLOL_REFRESH_HZ
|
||||||
|
+ * (1-1000 Hz). Unset or out of range keeps the previous 60 Hz period. */
|
||||||
|
+static uint64_t nativeRefreshIntervalNS(void){
|
||||||
|
+ static uint64_t interval;
|
||||||
|
+ if(!interval){
|
||||||
|
+ const char *hz=getenv("RCLOL_REFRESH_HZ");
|
||||||
|
+ long rate=hz?strtol(hz,NULL,10):0;
|
||||||
|
+ interval=(rate>0&&rate<=1000)?1000000000ull/(uint64_t)rate:16666667ull;
|
||||||
|
+ }
|
||||||
|
+ return interval;
|
||||||
|
+}
|
||||||
|
+
|
||||||
+struct DarlingAsyncPresent {
|
+struct DarlingAsyncPresent {
|
||||||
+ CGLContextObj owner;
|
+ CGLContextObj owner;
|
||||||
+ pthread_mutex_t mutex;
|
+ pthread_mutex_t mutex;
|
||||||
@@ -8526,11 +8550,12 @@ new file mode 100644
|
|||||||
+ /* Native VSync owns the refresh cadence; avoid a second clock that
|
+ /* Native VSync owns the refresh cadence; avoid a second clock that
|
||||||
+ * can miss the compositor's next refresh after a variable swap wait. */
|
+ * can miss the compositor's next refresh after a variable swap wait. */
|
||||||
+ if(nativeVSync)continue;
|
+ if(nativeVSync)continue;
|
||||||
+ deadline+=16666667ull;
|
+ uint64_t refreshNS=nativeRefreshIntervalNS();
|
||||||
|
+ deadline += refreshNS;
|
||||||
+ uint64_t now=nativeMonotonicNS();
|
+ uint64_t now=nativeMonotonicNS();
|
||||||
+ /* Preserve the refresh phase across small scheduling delays. Reset only
|
+ /* Preserve the refresh phase across small scheduling delays. Reset only
|
||||||
+ * after a full missed period, so lateness does not accumulate as drift. */
|
+ * after a full missed period, so lateness does not accumulate as drift. */
|
||||||
+ if(now>deadline && now-deadline>16666667ull){__atomic_fetch_add(&target->reserved[2],1,__ATOMIC_RELAXED);deadline=now;}
|
+ if(now>deadline && now-deadline>refreshNS){__atomic_fetch_add(&target->reserved[2],1,__ATOMIC_RELAXED);deadline=now;}
|
||||||
+ nativeWaitUntil(deadline);
|
+ nativeWaitUntil(deadline);
|
||||||
+ }
|
+ }
|
||||||
+ if(fbo)glDeleteFramebuffers(1,&fbo);
|
+ if(fbo)glDeleteFramebuffers(1,&fbo);
|
||||||
|
|||||||
@@ -1931,7 +1931,7 @@ index 23fd2d3..1a48b81 100644
|
|||||||
+ return _gpuAddress;
|
+ return _gpuAddress;
|
||||||
};
|
};
|
||||||
diff --git a/src/indium/command-buffer.cpp b/src/indium/command-buffer.cpp
|
diff --git a/src/indium/command-buffer.cpp b/src/indium/command-buffer.cpp
|
||||||
index 43f4a84..46a4f72 100644
|
index 43f4a84..aaa1acb 100644
|
||||||
--- a/src/indium/command-buffer.cpp
|
--- a/src/indium/command-buffer.cpp
|
||||||
+++ b/src/indium/command-buffer.cpp
|
+++ b/src/indium/command-buffer.cpp
|
||||||
@@ -1,3 +1,8 @@
|
@@ -1,3 +1,8 @@
|
||||||
@@ -2268,7 +2268,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
auto encoder = std::make_shared<Indium::PrivateComputeCommandEncoder>(shared_from_this(), descriptor);
|
auto encoder = std::make_shared<Indium::PrivateComputeCommandEncoder>(shared_from_this(), descriptor);
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(_mutex);
|
std::scoped_lock lock(_mutex);
|
||||||
@@ -91,10 +354,237 @@ std::shared_ptr<Indium::ComputeCommandEncoder> Indium::PrivateCommandBuffer::com
|
@@ -91,10 +354,239 @@ std::shared_ptr<Indium::ComputeCommandEncoder> Indium::PrivateCommandBuffer::com
|
||||||
return computeCommandEncoder(ComputePassDescriptor { {}, dispatchType });
|
return computeCommandEncoder(ComputePassDescriptor { {}, dispatchType });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2400,10 +2400,12 @@ index 43f4a84..46a4f72 100644
|
|||||||
+ for (;;) {
|
+ for (;;) {
|
||||||
+ {
|
+ {
|
||||||
+ std::unique_lock<std::mutex> lock(wakeMutex);
|
+ std::unique_lock<std::mutex> lock(wakeMutex);
|
||||||
+ submitBatchWake().wait_for(lock, std::chrono::milliseconds(2));
|
+ /* darling-lol perf: 500µs batch window balances submission
|
||||||
|
+ * merging with latency; the old 2ms added visible input lag. */
|
||||||
|
+ submitBatchWake().wait_for(lock, std::chrono::microseconds(500));
|
||||||
+ }
|
+ }
|
||||||
+ std::unique_lock<std::mutex> queueLock(device->graphicsQueueMutex());
|
+ std::unique_lock<std::mutex> queueLock(device->graphicsQueueMutex());
|
||||||
+ if (!state.items.empty() && std::chrono::steady_clock::now() - state.since >= std::chrono::milliseconds(2)) flushSubmitBatchLocked();
|
+ if (!state.items.empty() && std::chrono::steady_clock::now() - state.since >= std::chrono::microseconds(500)) flushSubmitBatchLocked();
|
||||||
+ }
|
+ }
|
||||||
+ }).detach();
|
+ }).detach();
|
||||||
+}
|
+}
|
||||||
@@ -2506,7 +2508,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
|
|
||||||
for (const auto& encoder: _commandEncoders) {
|
for (const auto& encoder: _commandEncoders) {
|
||||||
if (auto renderEncoder = std::dynamic_pointer_cast<PrivateRenderCommandEncoder>(encoder)) {
|
if (auto renderEncoder = std::dynamic_pointer_cast<PrivateRenderCommandEncoder>(encoder)) {
|
||||||
@@ -110,16 +600,28 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -110,16 +602,28 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2536,7 +2538,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
|
|
||||||
// for the event loop
|
// for the event loop
|
||||||
auto timelineSemaphore = _privateDevice->getWrappedTimelineSemaphore();
|
auto timelineSemaphore = _privateDevice->getWrappedTimelineSemaphore();
|
||||||
@@ -138,14 +640,49 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -138,14 +642,49 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
readWriteTextures.insert(readWriteTextures.end(), readWrite.begin(), readWrite.end());
|
readWriteTextures.insert(readWriteTextures.end(), readWrite.begin(), readWrite.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2589,7 +2591,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<VkSemaphoreSubmitInfo> signalInfos;
|
std::vector<VkSemaphoreSubmitInfo> signalInfos;
|
||||||
@@ -202,11 +739,13 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -202,11 +741,13 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
auto privateTexture = std::dynamic_pointer_cast<PrivateTexture>(texture);
|
auto privateTexture = std::dynamic_pointer_cast<PrivateTexture>(texture);
|
||||||
const auto& presentSema = presentationSemaphores[i];
|
const auto& presentSema = presentationSemaphores[i];
|
||||||
|
|
||||||
@@ -2608,7 +2610,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
|
|
||||||
handleTextureSemaphores(privateTexture);
|
handleTextureSemaphores(privateTexture);
|
||||||
}
|
}
|
||||||
@@ -215,7 +754,15 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -215,7 +756,15 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
// but it also leaves the resources for this command buffer tied up, essentially becoming a memory leak.
|
// but it also leaves the resources for this command buffer tied up, essentially becoming a memory leak.
|
||||||
// i've observed this in the cube example, and it happens more than once (because the example display semaphore is exhausted and never signaled).
|
// i've observed this in the cube example, and it happens more than once (because the example display semaphore is exhausted and never signaled).
|
||||||
// UPDATE: upon further testing, it seems that this only occurs when the view is off-screen/hidden. weird.
|
// UPDATE: upon further testing, it seems that this only occurs when the view is off-screen/hidden. weird.
|
||||||
@@ -2625,7 +2627,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
{
|
{
|
||||||
std::unique_lock lock(self->_mutex);
|
std::unique_lock lock(self->_mutex);
|
||||||
self->_completed = true;
|
self->_completed = true;
|
||||||
@@ -234,8 +781,47 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -234,8 +783,47 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
// the handlers may have captured a reference to us (from their surrounding scope), so clear out handlers so those references go away
|
// the handlers may have captured a reference to us (from their surrounding scope), so clear out handlers so those references go away
|
||||||
self->_scheduledHandlers.clear();
|
self->_scheduledHandlers.clear();
|
||||||
self->_completedHandlers.clear();
|
self->_completedHandlers.clear();
|
||||||
@@ -2673,7 +2675,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||||
commandBufferInfo.commandBuffer = _commandBuffer;
|
commandBufferInfo.commandBuffer = _commandBuffer;
|
||||||
|
|
||||||
@@ -252,23 +838,41 @@ void Indium::PrivateCommandBuffer::commit() {
|
@@ -252,23 +840,41 @@ void Indium::PrivateCommandBuffer::commit() {
|
||||||
// the Device constructor tries to choose command queues that support as many operations as possible, but it's possible
|
// the Device constructor tries to choose command queues that support as many operations as possible, but it's possible
|
||||||
// that a particular device only supports certain operations on certain queues (e.g. maybe it only supports transfer operations
|
// that a particular device only supports certain operations on certain queues (e.g. maybe it only supports transfer operations
|
||||||
// on an exclusive queue that doesn't support graphics or compute).
|
// on an exclusive queue that doesn't support graphics or compute).
|
||||||
@@ -2720,7 +2722,7 @@ index 43f4a84..46a4f72 100644
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Indium::PrivateCommandBuffer::presentDrawable(std::shared_ptr<Drawable> drawable) {
|
void Indium::PrivateCommandBuffer::presentDrawable(std::shared_ptr<Drawable> drawable) {
|
||||||
@@ -307,9 +911,12 @@ void Indium::PrivateCommandBuffer::addCompletedHandler(std::function<void(std::s
|
@@ -307,9 +913,12 @@ void Indium::PrivateCommandBuffer::addCompletedHandler(std::function<void(std::s
|
||||||
};
|
};
|
||||||
|
|
||||||
void Indium::PrivateCommandBuffer::waitUntilCompleted() {
|
void Indium::PrivateCommandBuffer::waitUntilCompleted() {
|
||||||
|
|||||||
+35
-7
@@ -29,7 +29,7 @@ index e14e7ea..024981b 100644
|
|||||||
#target_link_libraries(system_kernel system_duct platform_static32 platform_static64)
|
#target_link_libraries(system_kernel system_duct platform_static32 platform_static64)
|
||||||
make_fat(system_kernel)
|
make_fat(system_kernel)
|
||||||
diff --git a/darling/src/libsystem_kernel/emulation/CMakeLists.txt b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
diff --git a/darling/src/libsystem_kernel/emulation/CMakeLists.txt b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
||||||
index 5b2e765..64d35f5 100644
|
index 5b2e765..40308eb 100644
|
||||||
--- a/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
--- a/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
||||||
+++ b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
+++ b/darling/src/libsystem_kernel/emulation/CMakeLists.txt
|
||||||
@@ -6,13 +6,12 @@ enable_language(ASM)
|
@@ -6,13 +6,12 @@ enable_language(ASM)
|
||||||
@@ -68,6 +68,18 @@ index 5b2e765..64d35f5 100644
|
|||||||
src/xnu_syscall/bsd/impl/misc/proc_info.c
|
src/xnu_syscall/bsd/impl/misc/proc_info.c
|
||||||
src/xnu_syscall/bsd/impl/misc/ptrace.c
|
src/xnu_syscall/bsd/impl/misc/ptrace.c
|
||||||
src/xnu_syscall/bsd/impl/misc/reboot.c
|
src/xnu_syscall/bsd/impl/misc/reboot.c
|
||||||
|
@@ -359,6 +360,11 @@ set(emulation_sources
|
||||||
|
${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c
|
||||||
|
)
|
||||||
|
|
||||||
|
+# darling-lol: keep these at -O0 even when the tree is built optimized. bsdthread_register.c
|
||||||
|
+# sets %rsp from the thread argument in inline assembly and sigexc.c runs on the fault and
|
||||||
|
+# suspend signal stacks; both depend on the unoptimized stack layout.
|
||||||
|
+set_source_files_properties(src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c
|
||||||
|
+ src/linux_premigration/signal/sigexc.c PROPERTIES COMPILE_OPTIONS "-O0")
|
||||||
|
set_source_files_properties(src/legacy_path/signal/duct_signals.c PROPERTIES COMPILE_FLAGS "-nostdinc")
|
||||||
|
set_source_files_properties(src/legacy_path/signal/sigexc.c PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/legacy_path/signal/exc.h)
|
||||||
|
set_source_files_properties(${CMAKE_BINARY_DIR}/src/external/darlingserver/src/rpc.c PROPERTIES
|
||||||
diff --git a/darling/src/libsystem_kernel/emulation/include/common/base.h b/darling/src/libsystem_kernel/emulation/include/common/base.h
|
diff --git a/darling/src/libsystem_kernel/emulation/include/common/base.h b/darling/src/libsystem_kernel/emulation/include/common/base.h
|
||||||
index 207dae6..f5e96db 100644
|
index 207dae6..f5e96db 100644
|
||||||
--- a/darling/src/libsystem_kernel/emulation/include/common/base.h
|
--- a/darling/src/libsystem_kernel/emulation/include/common/base.h
|
||||||
@@ -4812,7 +4824,7 @@ index c1b2d50..4e19963 100644
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
diff --git a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
||||||
index b998456..2783a4a 100644
|
index b998456..c6281e6 100644
|
||||||
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
--- a/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
||||||
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
+++ b/darling/src/libsystem_kernel/emulation/src/xnu_syscall/mach/impl/mach_traps.c
|
||||||
@@ -12,9 +12,28 @@
|
@@ -12,9 +12,28 @@
|
||||||
@@ -5894,10 +5906,19 @@ index b998456..2783a4a 100644
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,6 +1613,41 @@ kern_return_t syscall_thread_switch_impl(
|
@@ -692,12 +1609,53 @@ kern_return_t syscall_thread_switch_impl(
|
||||||
.tv_sec = 0,
|
int option,
|
||||||
.tv_nsec = 1000000
|
mach_msg_timeout_t option_time)
|
||||||
|
{
|
||||||
|
+ /* option_time is in milliseconds (mach_msg_timeout_t). */
|
||||||
|
struct timespec tv = {
|
||||||
|
- .tv_sec = 0,
|
||||||
|
- .tv_nsec = 1000000
|
||||||
|
+ .tv_sec = option_time / 1000,
|
||||||
|
+ .tv_nsec = (long) (option_time % 1000) * 1000000
|
||||||
};
|
};
|
||||||
|
- // Sleep for 1ms
|
||||||
|
- __linux_nanosleep(&tv, &tv);
|
||||||
+
|
+
|
||||||
+ /* darling-lol: opt-in trace (host file /tmp/rclol-select-trace) of who calls thread_switch and with which option:
|
+ /* darling-lol: opt-in trace (host file /tmp/rclol-select-trace) of who calls thread_switch and with which option:
|
||||||
+ * every 50th call writes thread id, option, time and up to 6 caller addresses (frame-pointer walk) to
|
+ * every 50th call writes thread id, option, time and up to 6 caller addresses (frame-pointer walk) to
|
||||||
@@ -5933,9 +5954,16 @@ index b998456..2783a4a 100644
|
|||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
// Sleep for 1ms
|
+ /* darling-lol perf: a plain yield (option_time == 0) only gives up the CPU; sleeping a
|
||||||
__linux_nanosleep(&tv, &tv);
|
+ * full millisecond there is longer than a frame budget at high refresh rates. Timed
|
||||||
|
+ * switches (spin locks pass 1 ms) keep sleeping for the requested time. */
|
||||||
|
+ if (option_time == 0)
|
||||||
|
+ __linux_sched_yield_site("THRSW");
|
||||||
|
+ else
|
||||||
|
+ __linux_nanosleep(&tv, &tv);
|
||||||
|
|
||||||
|
// TODO: we could implement this with yield_to() in LKM
|
||||||
|
return KERN_SUCCESS;
|
||||||
diff --git a/libsyscall/CMakeLists.txt b/libsyscall/CMakeLists.txt
|
diff --git a/libsyscall/CMakeLists.txt b/libsyscall/CMakeLists.txt
|
||||||
index 57cebad..3014255 100755
|
index 57cebad..3014255 100755
|
||||||
--- a/libsyscall/CMakeLists.txt
|
--- a/libsyscall/CMakeLists.txt
|
||||||
|
|||||||
+9
-1
@@ -9,7 +9,15 @@ python3 scripts/prepare.py
|
|||||||
# so Release's -DNDEBUG trips its "mismatch in debug-ness macros" #error.
|
# so Release's -DNDEBUG trips its "mismatch in debug-ness macros" #error.
|
||||||
# x86-64 only: the patched OpenDirectory declares ivars in @implementation, which the
|
# x86-64 only: the patched OpenDirectory declares ivars in @implementation, which the
|
||||||
# 32-bit (fragile ABI) slice cannot compile, and the macOS client is 64-bit.
|
# 32-bit (fragile ABI) slice cannot compile, and the macOS client is 64-bit.
|
||||||
|
# darling-lol perf: inject -O3 and -march=native even with empty build type,
|
||||||
|
# so the base Darling runtime is not compiled at -O0. The empty build type
|
||||||
|
# is required by objc4's debug-ness macro check.
|
||||||
|
# -mtune=generic, not native: clang 23 tuning for AMD Zen (znver*) passes the thread-local
|
||||||
|
# variable descriptor in the wrong register on Darwin targets, so every __thread access
|
||||||
|
# returns NULL (launchd crashed in libkqueue's epoll_event_dump).
|
||||||
cmake -S build/darling-src -B build/darling-src/build -G 'Unix Makefiles' \
|
cmake -S build/darling-src -B build/darling-src/build -G 'Unix Makefiles' \
|
||||||
-DCMAKE_BUILD_TYPE= -DTARGET_i386=OFF -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_METAL=ON
|
-DCMAKE_BUILD_TYPE= -DTARGET_i386=OFF -DCMAKE_INSTALL_PREFIX=/usr/local -DENABLE_METAL=ON \
|
||||||
|
-DCMAKE_C_FLAGS="-O3 -march=native -mtune=generic -pipe" \
|
||||||
|
-DCMAKE_CXX_FLAGS="-O3 -march=native -mtune=generic -pipe"
|
||||||
cmake --build build/darling-src/build --parallel "${JOBS:-$(nproc)}"
|
cmake --build build/darling-src/build --parallel "${JOBS:-$(nproc)}"
|
||||||
echo 'Build complete. Install the base runtime with: sudo cmake --install build/darling-src/build'
|
echo 'Build complete. Install the base runtime with: sudo cmake --install build/darling-src/build'
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Create/update GNOME desktop entry and launcher icon.
|
||||||
|
set -eu
|
||||||
|
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
cd "$repo"
|
||||||
|
python3 scripts/configure.py >/dev/null
|
||||||
|
python3 build/runtime/tools/riot-desktop-entry.py
|
||||||
@@ -5,6 +5,8 @@ repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|||||||
cd "$repo"
|
cd "$repo"
|
||||||
python3 scripts/configure.py
|
python3 scripts/configure.py
|
||||||
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
||||||
|
# A server killed by the base install leaves a stale .init.pid that makes darling shell fail.
|
||||||
|
python3 build/runtime/tools/ensure-darling-runtime.py
|
||||||
# Initialize the normal user-owned prefix before installing overrides.
|
# Initialize the normal user-owned prefix before installing overrides.
|
||||||
darling shell /usr/bin/true
|
darling shell /usr/bin/true
|
||||||
# The prefix has no /private/tmp of its own, so the guest falls through to the root-owned
|
# The prefix has no /private/tmp of its own, so the guest falls through to the root-owned
|
||||||
@@ -14,4 +16,13 @@ chmod 1777 "$HOME/.darling/private/tmp"
|
|||||||
python3 build/runtime/tools/stop-darling-runtime.py
|
python3 build/runtime/tools/stop-darling-runtime.py
|
||||||
sh build/runtime/tools/install-riot-presentation-runtime.sh
|
sh build/runtime/tools/install-riot-presentation-runtime.sh
|
||||||
sh build/runtime/tools/install-riot-gpu-runtime.sh
|
sh build/runtime/tools/install-riot-gpu-runtime.sh
|
||||||
|
# darling-lol perf: build and install optimized libc/libc++/libplatform.
|
||||||
|
if [ -f build/runtime/tools/build-riot-libs-runtime.sh ]; then
|
||||||
|
sh build/runtime/tools/build-riot-libs-runtime.sh
|
||||||
|
python3 build/runtime/tools/install-riot-libs-runtime.py
|
||||||
|
fi
|
||||||
python3 build/runtime/tools/install-darling-system-profiler.py
|
python3 build/runtime/tools/install-darling-system-profiler.py
|
||||||
|
# Every guest process loads these overlays; refuse a set the compiler broke (check-darwin-tlv-calls.py).
|
||||||
|
find "$HOME/.darling/Users/$(id -un)/RuntimeLibraries" "$HOME/.darling/Users/$(id -un)/RuntimeFrameworks" \
|
||||||
|
-type f ! -name '*.before-*' ! -name '*.rt-*' ! -name '*.llvm*' -size +4k -print0 |
|
||||||
|
xargs -0 python3 build/runtime/tools/check-darwin-tlv-calls.py
|
||||||
@@ -4,4 +4,9 @@ repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|||||||
cd "$repo"
|
cd "$repo"
|
||||||
python3 scripts/configure.py >/dev/null
|
python3 scripts/configure.py >/dev/null
|
||||||
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
if [ -d build/venv/bin ]; then PATH="$repo/build/venv/bin:$PATH"; export PATH; fi
|
||||||
|
# darling-lol perf: optionally wrap with gamemoderun for automatic
|
||||||
|
# CPU governor, nice level, and scheduler optimizations.
|
||||||
|
if [ "${RCLOL_GAMEMODE:-auto}" != "0" ] && command -v gamemoderun >/dev/null 2>&1; then
|
||||||
|
exec gamemoderun sh build/runtime/tools/run-riot-client.sh
|
||||||
|
fi
|
||||||
exec sh build/runtime/tools/run-riot-client.sh
|
exec sh build/runtime/tools/run-riot-client.sh
|
||||||
+8
-1
@@ -4,7 +4,10 @@ import ast,json,re,subprocess,sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from common import ROOT
|
from common import ROOT
|
||||||
subprocess.run([sys.executable,str(ROOT/'scripts/configure.py')],check=True)
|
subprocess.run([sys.executable,str(ROOT/'scripts/configure.py')],check=True)
|
||||||
files=subprocess.check_output(['git','-C',str(ROOT),'ls-files','-z']).decode().split('\0')
|
try:
|
||||||
|
files=subprocess.check_output(['git','-C',str(ROOT),'ls-files','-z'],stderr=subprocess.DEVNULL).decode().split('\0')
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
files=[str(p.relative_to(ROOT)) for p in ROOT.rglob('*') if p.is_file() and not any(part.startswith('.') or part in ['build','__pycache__'] for part in p.relative_to(ROOT).parts)]
|
||||||
errors=[]
|
errors=[]
|
||||||
secret_patterns=[r'gh[pousr]_[A-Za-z0-9]{30,}',r'AKIA[0-9A-Z]{16}',r'eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}',r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----']
|
secret_patterns=[r'gh[pousr]_[A-Za-z0-9]{30,}',r'AKIA[0-9A-Z]{16}',r'eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}',r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----']
|
||||||
for name in filter(None,files):
|
for name in filter(None,files):
|
||||||
@@ -28,6 +31,10 @@ manifest=json.loads((ROOT/'upstream/manifest.json').read_text())
|
|||||||
for c in manifest['components']:
|
for c in manifest['components']:
|
||||||
if not re.fullmatch('[0-9a-f]{40}',c['revision']):errors.append(c['path']+': invalid pinned revision')
|
if not re.fullmatch('[0-9a-f]{40}',c['revision']):errors.append(c['path']+': invalid pinned revision')
|
||||||
if not (ROOT/c['patch']).is_file():errors.append(c['path']+': patch missing')
|
if not (ROOT/c['patch']).is_file():errors.append(c['path']+': patch missing')
|
||||||
|
else:
|
||||||
|
parsed=subprocess.run(['git','apply','--numstat','-'],input=(ROOT/c['patch']).read_text(),
|
||||||
|
text=True,capture_output=True)
|
||||||
|
if parsed.returncode:errors.append(c['patch']+': '+parsed.stderr.strip())
|
||||||
if errors:
|
if errors:
|
||||||
print('\n'.join(errors));raise SystemExit(1)
|
print('\n'.join(errors));raise SystemExit(1)
|
||||||
print('PASS: tracked source audit, rendered Python/shell syntax and',len(manifest['components']),'pinned patch entries')
|
print('PASS: tracked source audit, rendered Python/shell syntax and',len(manifest['components']),'pinned patch entries')
|
||||||
@@ -15,7 +15,7 @@ link = shlex.split((framework / 'CMakeFiles/AppKit.dir/link.txt').read_text())
|
|||||||
for name in ['NSResponder', 'NSApplication', 'NSWindow', 'NSDisplay', 'NSView', 'NSThemeFrame', 'NSTextInput', 'NSBitmapImageRep', 'NSEvent.subproj/NSEvent_mouse']:
|
for name in ['NSResponder', 'NSApplication', 'NSWindow', 'NSDisplay', 'NSView', 'NSThemeFrame', 'NSTextInput', 'NSBitmapImageRep', 'NSEvent.subproj/NSEvent_mouse']:
|
||||||
obj = project / f'build/riot-{Path(name).name}.o'
|
obj = project / f'build/riot-{Path(name).name}.o'
|
||||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||||
'-O2', '-Wno-macro-redefined', '-c',
|
'-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c',
|
||||||
str(project / f'build/darling-src/src/external/cocotron/AppKit/{name}.m'),
|
str(project / f'build/darling-src/src/external/cocotron/AppKit/{name}.m'),
|
||||||
'-o', str(obj)], check=True)
|
'-o', str(obj)], check=True)
|
||||||
link[link.index(f'CMakeFiles/AppKit.dir/{name}.m.o')] = str(obj)
|
link[link.index(f'CMakeFiles/AppKit.dir/{name}.m.o')] = str(obj)
|
||||||
@@ -24,7 +24,7 @@ subprocess.run(link, cwd=framework, check=True)
|
|||||||
for name in ['riot-event-coalescing-check', 'riot-scroll-event-check', 'riot-appkit-pump-check', 'riot-window-layout-check']:
|
for name in ['riot-event-coalescing-check', 'riot-scroll-event-check', 'riot-appkit-pump-check', 'riot-window-layout-check']:
|
||||||
probe = project / f'build/{name}.o'
|
probe = project / f'build/{name}.o'
|
||||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||||
'-O2', '-fblocks', '-Wno-macro-redefined', '-c',
|
'-O3', '-march=native', '-mtune=generic', '-fblocks', '-Wno-macro-redefined', '-c',
|
||||||
str(project / f'tools/{name}.m'), '-o', str(probe)], check=True)
|
str(project / f'tools/{name}.m'), '-o', str(probe)], check=True)
|
||||||
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
||||||
'-isysroot', '@DARLING_SDK@', str(probe), '-lSystem',
|
'-isysroot', '@DARLING_SDK@', str(probe), '-lSystem',
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rebuild libc++abi and libc++ with -O2. The CMake tree has no build type, so the stock objects (and the earlier
|
"""Rebuild libc++abi and libc++ with -O3. The CMake tree has no build type, so the stock objects (and the earlier
|
||||||
single-file libc++abi rebuild) are -O0: std::type_info::operator==, __dynamic_cast, shared_ptr internals ran 3-5x slower
|
single-file libc++abi rebuild) are -O0: std::type_info::operator==, __dynamic_cast, shared_ptr internals ran 3-5x slower
|
||||||
than they should (about 2% of the League main thread). Outputs build/riot-opt-libc++abi.dylib / riot-opt-libc++.1.dylib.
|
than they should (about 2% of the League main thread). Outputs build/riot-opt-libc++abi.dylib / riot-opt-libc++.1.dylib.
|
||||||
"""
|
"""
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re, shlex, subprocess, sys
|
import os, re, shlex, subprocess, sys
|
||||||
root = Path(__file__).resolve().parent.parent
|
root = Path(__file__).resolve().parent.parent
|
||||||
resource_dir = subprocess.check_output(['clang', '-print-resource-dir'], text=True).strip()
|
resource_dir = subprocess.check_output(['clang', '-print-resource-dir'], text=True).strip()
|
||||||
def current_clang(values):
|
def current_clang(values):
|
||||||
@@ -26,9 +26,9 @@ def build(lib, objdir, linkdir, output):
|
|||||||
jobs.append((rel, obj)); link[i] = str(obj)
|
jobs.append((rel, obj)); link[i] = str(obj)
|
||||||
def compile_one(job):
|
def compile_one(job):
|
||||||
rel, obj = job
|
rel, obj = job
|
||||||
subprocess.run(['clang++', *flags['CXX_DEFINES'], *flags['CXX_INCLUDES'], *flags['CXX_FLAGS'], '-O2', '-DNDEBUG',
|
subprocess.run(['clang++', *flags['CXX_DEFINES'], *flags['CXX_INCLUDES'], *flags['CXX_FLAGS'], '-O3', '-march=native', '-mtune=generic', '-DNDEBUG',
|
||||||
'-c', str(src / rel), '-o', str(obj)], check=True)
|
'-c', str(src / rel), '-o', str(obj)], check=True)
|
||||||
with ThreadPoolExecutor(max_workers=8) as pool:
|
with ThreadPoolExecutor(max_workers=os.cpu_count() or 8) as pool:
|
||||||
list(pool.map(compile_one, jobs))
|
list(pool.map(compile_one, jobs))
|
||||||
link[link.index('-o') + 1] = str(root / 'build' / output)
|
link[link.index('-o') + 1] = str(root / 'build' / output)
|
||||||
subprocess.run(link, cwd=folder, check=True)
|
subprocess.run(link, cwd=folder, check=True)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rebuild libc++ and libc++abi with -O2.
|
"""Rebuild libc++ and libc++abi with -O3 -march=native -mtune=generic.
|
||||||
|
|
||||||
The CMake tree has no build type, so both libraries are clang's -O0 output: the
|
The CMake tree has no build type, so both libraries are clang's -O0 output: the
|
||||||
game imports 131 functions from libc++.1.dylib (out-of-line std::string,
|
game imports 131 functions from libc++.1.dylib (out-of-line std::string,
|
||||||
@@ -15,6 +15,7 @@ generation only. Outputs build/riot-libc++.1.dylib and build/riot-libc++abi.dyli
|
|||||||
"""
|
"""
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -47,20 +48,22 @@ def build(library, object_dir, link_dir, output):
|
|||||||
obj = root / ("build/riot-opt-%s-%s.o" % (library, relative.replace("/", "_")))
|
obj = root / ("build/riot-opt-%s-%s.o" % (library, relative.replace("/", "_")))
|
||||||
jobs.append((relative, obj))
|
jobs.append((relative, obj))
|
||||||
link[index] = str(obj)
|
link[index] = str(obj)
|
||||||
print(library, "recompiling", len(jobs), "objects at -O2", flush=True)
|
print(library, "recompiling", len(jobs), "objects at -O3", flush=True)
|
||||||
|
|
||||||
def compile_one(job):
|
def compile_one(job):
|
||||||
relative, obj = job
|
relative, obj = job
|
||||||
subprocess.run(["clang++", *flags["CXX_DEFINES"], *flags["CXX_INCLUDES"], *flags["CXX_FLAGS"],
|
cxx_flags = [f for f in flags["CXX_FLAGS"] if f not in ("-g", "-O0", "-O1", "-O2")]
|
||||||
"-O2", "-fno-strict-aliasing", "-c", str(source / relative), "-o", str(obj)],
|
subprocess.run(["clang++", *flags["CXX_DEFINES"], *flags["CXX_INCLUDES"], *cxx_flags,
|
||||||
|
"-O3", "-march=native", "-mtune=generic", "-fno-strict-aliasing",
|
||||||
|
"-c", str(source / relative), "-o", str(obj)],
|
||||||
check=True, capture_output=True)
|
check=True, capture_output=True)
|
||||||
|
|
||||||
riot_build_gate.wait_until_free()
|
riot_build_gate.wait_until_free()
|
||||||
with ThreadPoolExecutor(max_workers=6) as pool:
|
with ThreadPoolExecutor(max_workers=os.cpu_count() or 8) as pool:
|
||||||
list(pool.map(compile_one, jobs))
|
list(pool.map(compile_one, jobs))
|
||||||
link[link.index("-o") + 1] = str(root / "build" / output)
|
link[link.index("-o") + 1] = str(root / "build" / output)
|
||||||
subprocess.run(link, cwd=folder, check=True)
|
subprocess.run(link, cwd=folder, check=True)
|
||||||
print("built build/" + output, len(jobs), "objects at -O2", flush=True)
|
print("built build/" + output, len(jobs), "objects at -O3", flush=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ def configured_flags(directory):
|
|||||||
if line.startswith('C_') and ' = ' in line:
|
if line.startswith('C_') and ' = ' in line:
|
||||||
key,value=line.split(' = ',1)
|
key,value=line.split(' = ',1)
|
||||||
result[key]=[re.sub(r'/usr/lib/clang/\d+/include',resource_dir+'/include',v) for v in shlex.split(value)]
|
result[key]=[re.sub(r'/usr/lib/clang/\d+/include',resource_dir+'/include',v) for v in shlex.split(value)]
|
||||||
|
if key == 'C_FLAGS': result[key].append('-mtune=generic')
|
||||||
return result
|
return result
|
||||||
kqueue_flags=configured_flags(build/'src/external/libkqueue/CMakeFiles/kqueue.dir')
|
kqueue_flags=configured_flags(build/'src/external/libkqueue/CMakeFiles/kqueue.dir')
|
||||||
emulation_flags=configured_flags(kernel/'emulation/CMakeFiles/emulation.dir')
|
emulation_flags=configured_flags(kernel/'emulation/CMakeFiles/emulation.dir')
|
||||||
@@ -29,8 +30,8 @@ syscall_flags=configured_flags(kernel/'libsyscall/CMakeFiles/libsyscall.dir')
|
|||||||
kqueue_flags={**kqueue_flags,'C_FLAGS':[*kqueue_flags['C_FLAGS'],'-O2','-fno-strict-aliasing']}
|
kqueue_flags={**kqueue_flags,'C_FLAGS':[*kqueue_flags['C_FLAGS'],'-O2','-fno-strict-aliasing']}
|
||||||
kqueue_sources=[value.split('/kqueue.dir/',1)[1][:-2] for value in objects if '/kqueue.dir/' in value and value.endswith('.c.o')]
|
kqueue_sources=[value.split('/kqueue.dir/',1)[1][:-2] for value in objects if '/kqueue.dir/' in value and value.endswith('.c.o')]
|
||||||
assert kqueue_sources, 'Configured kqueue objects missing'
|
assert kqueue_sources, 'Configured kqueue objects missing'
|
||||||
# The configured emulation target is a plain development build: its flags.make
|
# The configured emulation flags carry scripts/build.sh's -O3; the -O2 appended below
|
||||||
# carries no -O flag, so every object recompiled with `emulation_flags` is -O0.
|
# comes last and wins, and the stack-layout-dependent files below pin -O0.
|
||||||
# These files are on the per-call path of the guest (BSD trap dispatch, read/
|
# These files are on the per-call path of the guest (BSD trap dispatch, read/
|
||||||
# write, network, kqueue, psynch/ulock, clocks, madvise), so rebuild them
|
# write, network, kqueue, psynch/ulock, clocks, madvise), so rebuild them
|
||||||
# optimized. -fno-strict-aliasing matches the other optimized rebuilds in this
|
# optimized. -fno-strict-aliasing matches the other optimized rebuilds in this
|
||||||
@@ -56,13 +57,16 @@ repairs += [('src/xnu_syscall/bsd/impl/'+name,repair_flags,
|
|||||||
# configured forced-include header, without changing unrelated kernel objects.
|
# configured forced-include header, without changing unrelated kernel objects.
|
||||||
emulation_root=project/'build/darling-src/src/external/xnu/darling/src/libsystem_kernel/emulation'
|
emulation_root=project/'build/darling-src/src/external/xnu/darling/src/libsystem_kernel/emulation'
|
||||||
rpc_flags={**emulation_flags,'C_FLAGS':[*emulation_flags['C_FLAGS'],'-O2','-fno-omit-frame-pointer']}
|
rpc_flags={**emulation_flags,'C_FLAGS':[*emulation_flags['C_FLAGS'],'-O2','-fno-omit-frame-pointer']}
|
||||||
# bsdthread_register.c keeps emulation_flags (-O0): its pthread/wqueue entry
|
# bsdthread_register.c stays at -O0: its pthread/wqueue entry
|
||||||
# wrappers jump into _thread_start/_start_wqthread through inline assembly that
|
# wrappers jump into _thread_start/_start_wqthread through inline assembly that
|
||||||
# sets %rsp from the thread argument, i.e. it depends on the stack layout.
|
# sets %rsp from the thread argument, i.e. it depends on the stack layout.
|
||||||
# Signal entry with stack-layout-dependent frame walking stays unoptimized on
|
# Signal entry with stack-layout-dependent frame walking stays unoptimized on
|
||||||
# purpose (emulation_flags): it runs on the fault/suspend signal stacks.
|
# purpose: it runs on the fault/suspend signal stacks.
|
||||||
repairs += [('src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c',emulation_flags,emulation_root)]
|
# The configured flags are optimized now (scripts/build.sh), so pin -O0 explicitly, as the
|
||||||
repairs += [('src/linux_premigration/signal/sigexc.c',emulation_flags,emulation_root)]
|
# emulation CMakeLists does for these two files.
|
||||||
|
unoptimized_flags={**emulation_flags,'C_FLAGS':[*emulation_flags['C_FLAGS'],'-O0']}
|
||||||
|
repairs += [('src/xnu_syscall/bsd/impl/bsdthread/bsdthread_register.c',unoptimized_flags,emulation_root)]
|
||||||
|
repairs += [('src/linux_premigration/signal/sigexc.c',unoptimized_flags,emulation_root)]
|
||||||
repairs += [('src/xnu_syscall/mach/impl/mach_traps.c',rpc_flags,emulation_root)]
|
repairs += [('src/xnu_syscall/mach/impl/mach_traps.c',rpc_flags,emulation_root)]
|
||||||
# User-space clock (per-CPU TSC calibration), its gettimeofday entry and the cached kern.boottime.
|
# User-space clock (per-CPU TSC calibration), its gettimeofday entry and the cached kern.boottime.
|
||||||
repairs += [('src/linux_premigration/ext/clock_gettime.c',rpc_flags,emulation_root)]
|
repairs += [('src/linux_premigration/ext/clock_gettime.c',rpc_flags,emulation_root)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Rebuild libsystem_c (libc) with -O2.
|
"""Rebuild libsystem_c (libc) with -O3 -march=native -mtune=generic.
|
||||||
|
|
||||||
The CMake tree is configured with an empty build type, so the installed
|
The CMake tree is configured with an empty build type, so the installed
|
||||||
libsystem_c.dylib is clang's -O0 output except for the three string files
|
libsystem_c.dylib is clang's -O0 output except for the three string files
|
||||||
@@ -7,7 +7,7 @@ libplatform marks optimized. The game imports 122 functions from this library
|
|||||||
(stdio, string, stdlib, locale, time) and it is on the path of every frame;
|
(stdio, string, stdlib, locale, time) and it is on the path of every frame;
|
||||||
this builder takes each object's own compile command out of the generated
|
this builder takes each object's own compile command out of the generated
|
||||||
build.make, keeps every define, include and per-file option it has, replaces
|
build.make, keeps every define, include and per-file option it has, replaces
|
||||||
the optimization level with -O2 and relinks the result with the configured
|
the optimization level with -O3 and relinks the result with the configured
|
||||||
link line. -fno-strict-aliasing matches what the existing optimized rebuilds
|
link line. -fno-strict-aliasing matches what the existing optimized rebuilds
|
||||||
use for this ten-year-old BSD C.
|
use for this ten-year-old BSD C.
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ def source_of(head, relative):
|
|||||||
|
|
||||||
def compile_command(target_folder, source, object_path):
|
def compile_command(target_folder, source, object_path):
|
||||||
"""The configured compile command for one object, with the object path and
|
"""The configured compile command for one object, with the object path and
|
||||||
the dependency flags taken out and -O2 -fno-strict-aliasing added."""
|
the dependency flags taken out and -O3 -fno-strict-aliasing added."""
|
||||||
for line in (target_folder / "build.make").read_text().splitlines():
|
for line in (target_folder / "build.make").read_text().splitlines():
|
||||||
if "$(C_DEFINES)" not in line or " -c " not in line or str(source) not in line:
|
if "$(C_DEFINES)" not in line or " -c " not in line or str(source) not in line:
|
||||||
continue
|
continue
|
||||||
@@ -100,9 +100,8 @@ def compile_command(target_folder, source, object_path):
|
|||||||
skip = True
|
skip = True
|
||||||
continue
|
continue
|
||||||
cleaned.append(token)
|
cleaned.append(token)
|
||||||
if "-O2" not in cleaned:
|
cleaned = [t for t in cleaned if t not in ("-g", "-O0", "-O1", "-O2")]
|
||||||
cleaned.append("-O2")
|
cleaned.extend(["-O3", "-march=native", "-mtune=generic", "-fno-strict-aliasing"])
|
||||||
cleaned.append("-fno-strict-aliasing")
|
|
||||||
return cleaned
|
return cleaned
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Build every overlay of the optimized library batch, in order.
|
# Build every overlay of the optimized library batch, in order.
|
||||||
#
|
#
|
||||||
# Each builder recompiles the component's own objects at -O2 with the flags
|
# Each builder recompiles the component's own objects at -O3 -march=native -mtune=generic with the flags
|
||||||
# the configured build gives them and relinks with the configured link line;
|
# the configured build gives them and relinks with the configured link line;
|
||||||
# none of them runs a full cmake build. The builders stop while a League game
|
# none of them runs a full cmake build. The builders stop while a League game
|
||||||
# process is running, because a compile job would skew the FPS the user is
|
# process is running, because a compile job would skew the FPS the user is
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Build the Metal layer presentation bridge and its window regression check."""
|
"""Build the Metal layer presentation bridge and its window regression check."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import json
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
root = Path(__file__).resolve().parent.parent
|
root = Path(__file__).resolve().parent.parent
|
||||||
|
config_path = root / 'build/config.json'
|
||||||
|
sdk = json.loads(config_path.read_text())['sdk'] if config_path.exists() else '/usr/local/libexec/darling'
|
||||||
folder = root / 'build/darling-src/build/src/external/cocotron/QuartzCore'
|
folder = root / 'build/darling-src/build/src/external/cocotron/QuartzCore'
|
||||||
source = root / 'build/darling-src/src/external/cocotron/QuartzCore'
|
source = root / 'build/darling-src/src/external/cocotron/QuartzCore'
|
||||||
flags = {}
|
flags = {}
|
||||||
@@ -28,6 +31,6 @@ obj = root / 'build/riot-metal-presentation-check.o'
|
|||||||
subprocess.run(['clang++', *flags['CXX_DEFINES'], *flags['CXX_INCLUDES'], *flags['CXX_FLAGS'],
|
subprocess.run(['clang++', *flags['CXX_DEFINES'], *flags['CXX_INCLUDES'], *flags['CXX_FLAGS'],
|
||||||
'-c', str(root / 'tools/riot-metal-presentation-check.mm'), '-o', str(obj)], check=True)
|
'-c', str(root / 'tools/riot-metal-presentation-check.mm'), '-o', str(obj)], check=True)
|
||||||
subprocess.run(['clang++', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
subprocess.run(['clang++', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib',
|
||||||
'-isysroot', '@DARLING_SDK@', str(obj), '-lSystem', '-lc++', '-lobjc',
|
'-isysroot', sdk, str(obj), '-lSystem', '-lc++', '-lobjc',
|
||||||
'-framework', 'AppKit', '-framework', 'OpenGL', '-framework', 'Foundation', str(root / 'build/riot-Metal'),
|
'-framework', 'AppKit', '-framework', 'OpenGL', '-framework', 'Foundation', str(root / 'build/riot-Metal'),
|
||||||
str(root / 'build/riot-QuartzCore'), '-o', str(root / 'build/riot-metal-presentation-check')], check=True)
|
str(root / 'build/riot-QuartzCore'), '-o', str(root / 'build/riot-metal-presentation-check')], check=True)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Optimize configured string helpers, preserving every other platform object.
|
"""Optimize configured string helpers, preserving every other platform object.
|
||||||
|
|
||||||
Every src/string/generic source is recompiled at -O2 -fno-builtin (and
|
Every src/string/generic source is recompiled at -O3 -fno-builtin (and
|
||||||
-fno-strict-aliasing for this old BSD C) with the object library's own defines
|
-fno-strict-aliasing for this old BSD C) with the object library's own defines
|
||||||
and includes, then relinked into build/riot-libsystem_platform.dylib. The AVX2
|
and includes, then relinked into build/riot-libsystem_platform.dylib. The AVX2
|
||||||
and REP paths for memcpy/memmove/memset/bzero/memcmp/memchr live in those same
|
and REP paths for memcpy/memmove/memset/bzero/memcmp/memchr live in those same
|
||||||
@@ -27,10 +27,11 @@ for original in list(link):
|
|||||||
if not original.startswith('CMakeFiles/platform_obj.dir/src/string/generic/') or not original.endswith('.c.o'):continue
|
if not original.startswith('CMakeFiles/platform_obj.dir/src/string/generic/') or not original.endswith('.c.o'):continue
|
||||||
relative=original.split('platform_obj.dir/',1)[1].removesuffix('.o')
|
relative=original.split('platform_obj.dir/',1)[1].removesuffix('.o')
|
||||||
obj=root/('build/riot-platform-'+Path(relative).name+'.o')
|
obj=root/('build/riot-platform-'+Path(relative).name+'.o')
|
||||||
subprocess.run(['clang',*flags['C_DEFINES'],*flags['C_INCLUDES'],*flags['C_FLAGS'],'-O2','-fno-builtin','-fno-strict-aliasing','-c',str(root/'build/darling-src/src/external/libplatform'/relative),'-o',str(obj)],check=True)
|
c_flags = [f for f in flags['C_FLAGS'] if f not in ('-g', '-O0', '-O1', '-O2')]
|
||||||
|
subprocess.run(['clang',*flags['C_DEFINES'],*flags['C_INCLUDES'],*c_flags,'-O3','-march=native','-mtune=generic','-fno-builtin','-fno-strict-aliasing','-c',str(root/'build/darling-src/src/external/libplatform'/relative),'-o',str(obj)],check=True)
|
||||||
link[link.index(original)]=str(obj)
|
link[link.index(original)]=str(obj)
|
||||||
recompiled+=1
|
recompiled+=1
|
||||||
link[link.index('-o')+1]=str(root/'build/riot-libsystem_platform.dylib')
|
link[link.index('-o')+1]=str(root/'build/riot-libsystem_platform.dylib')
|
||||||
subprocess.run(link,cwd=folder,check=True)
|
subprocess.run(link,cwd=folder,check=True)
|
||||||
print('built build/riot-libsystem_platform.dylib with',recompiled,'string objects at -O2')
|
print('built build/riot-libsystem_platform.dylib with',recompiled,'string objects at -O3')
|
||||||
p=root/'build/m810-platform-runtime';p.mkdir(exist_ok=True);shutil.copy2(root/'build/riot-libsystem_platform.dylib',p/'libsystem_platform.dylib')
|
p=root/'build/m810-platform-runtime';p.mkdir(exist_ok=True);shutil.copy2(root/'build/riot-libsystem_platform.dylib',p/'libsystem_platform.dylib')
|
||||||
@@ -16,7 +16,7 @@ for line in (framework / 'CMakeFiles/X11_backend.dir/flags.make').read_text().sp
|
|||||||
link = shlex.split((framework / 'CMakeFiles/X11_backend.dir/link.txt').read_text())
|
link = shlex.split((framework / 'CMakeFiles/X11_backend.dir/link.txt').read_text())
|
||||||
for name in ('X11Display', 'X11Window'):
|
for name in ('X11Display', 'X11Window'):
|
||||||
obj = project / f'build/riot-{name}.o'
|
obj = project / f'build/riot-{name}.o'
|
||||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'], '-O2', '-Wno-macro-redefined', '-c', str(project / f'build/darling-src/src/external/cocotron/AppKit/X11.backend/{name}.m'), '-o', str(obj)], check=True)
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'], '-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c', str(project / f'build/darling-src/src/external/cocotron/AppKit/X11.backend/{name}.m'), '-o', str(obj)], check=True)
|
||||||
link[link.index(f'CMakeFiles/X11_backend.dir/X11.backend/{name}.m.o')] = str(obj)
|
link[link.index(f'CMakeFiles/X11_backend.dir/X11.backend/{name}.m.o')] = str(obj)
|
||||||
link[link.index('-o') + 1] = str(project / 'build/riot-X11')
|
link[link.index('-o') + 1] = str(project / 'build/riot-X11')
|
||||||
subprocess.run(link, cwd=framework, check=True)
|
subprocess.run(link, cwd=framework, check=True)
|
||||||
@@ -25,9 +25,27 @@ print('Built isolated X11 backend')
|
|||||||
# Exercise actual backend modifier transitions without compositor shortcuts.
|
# Exercise actual backend modifier transitions without compositor shortcuts.
|
||||||
probe = project / 'build/riot-modifier-check.o'
|
probe = project / 'build/riot-modifier-check.o'
|
||||||
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||||
'-O2', '-Wno-macro-redefined', '-c', str(project / 'tools/riot-modifier-check.m'),
|
'-O3', '-march=native', '-mtune=generic', '-Wno-macro-redefined', '-c', str(project / 'tools/riot-modifier-check.m'),
|
||||||
'-o', str(probe)], check=True)
|
'-o', str(probe)], check=True)
|
||||||
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
||||||
'-nostdlib', '-isysroot', sdk, str(probe),
|
'-nostdlib', '-isysroot', sdk, str(probe),
|
||||||
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation', '-lobjc',
|
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation', '-lobjc',
|
||||||
'-o', str(project / 'build/riot-modifier-check')], check=True)
|
'-o', str(project / 'build/riot-modifier-check')], check=True)
|
||||||
|
|
||||||
|
# Verify that cached Chromium presentation cannot cover a native game drawable.
|
||||||
|
probe = project / 'build/riot-remote-frame-ownership-check.o'
|
||||||
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||||
|
'-O3', '-march=native', '-mtune=generic', '-UNDEBUG', '-Wno-macro-redefined', '-c',
|
||||||
|
str(project / 'tools/riot-remote-frame-ownership-check.m'), '-o', str(probe)], check=True)
|
||||||
|
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
||||||
|
'-nostdlib', '-isysroot', sdk, str(probe), '-lSystem', '-lobjc',
|
||||||
|
'-framework', 'Foundation', '-framework', 'CoreFoundation',
|
||||||
|
'-o', str(project / 'build/riot-remote-frame-ownership-check')], check=True)
|
||||||
|
|
||||||
|
probe = project / 'build/riot-fullscreen-geometry-check.o'
|
||||||
|
subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'],
|
||||||
|
'-O3', '-march=native', '-mtune=generic', '-UNDEBUG', '-Wno-macro-redefined', '-c',
|
||||||
|
str(project / 'tools/riot-fullscreen-geometry-check.m'), '-o', str(probe)], check=True)
|
||||||
|
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
||||||
|
'-nostdlib', '-isysroot', sdk, str(probe), '-lSystem', '-lobjc', '-framework', 'Foundation',
|
||||||
|
'-o', str(project / 'build/riot-fullscreen-geometry-check')], check=True)
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fail if a Mach-O binary calls a thread-local variable's thunk without its descriptor in %rdi.
|
||||||
|
|
||||||
|
dyld's tlv_get_addr reads the descriptor from %rdi. clang 23 tuning for AMD Zen (-mtune=znver*,
|
||||||
|
also implied by -march=native on Zen) can load the descriptor into another register and call
|
||||||
|
through it, leaving %rdi unrelated: the thunk then returns NULL and the first write crashes.
|
||||||
|
Usage: check-darwin-tlv-calls.py BINARY... ; exits 1 and lists every bad access it finds.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def thread_vars_ranges(path):
|
||||||
|
"""Address ranges of the __thread_vars sections, which hold the TLV descriptors."""
|
||||||
|
output = subprocess.run(['llvm-objdump', '--macho', '--section-headers', path],
|
||||||
|
capture_output=True, text=True, check=True).stdout
|
||||||
|
ranges = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
fields = line.split()
|
||||||
|
if len(fields) >= 4 and fields[1] == '__thread_vars':
|
||||||
|
start, size = int(fields[3], 16), int(fields[2], 16)
|
||||||
|
ranges.append((start, start + size))
|
||||||
|
return ranges
|
||||||
|
|
||||||
|
|
||||||
|
def bad_accesses(path):
|
||||||
|
with open(path, 'rb') as binary:
|
||||||
|
if binary.read(4) not in (b'\xcf\xfa\xed\xfe', b'\xca\xfe\xba\xbe'):
|
||||||
|
return []
|
||||||
|
ranges = thread_vars_ranges(path)
|
||||||
|
if not ranges:
|
||||||
|
return []
|
||||||
|
disassembly = subprocess.run(['llvm-objdump', '-d', '--no-show-raw-insn', path],
|
||||||
|
capture_output=True, text=True, check=True).stdout
|
||||||
|
load = re.compile(r'(?:leaq|movq)\s+-?0x[0-9a-f]+\(%rip\), %(\w+)\s+#+ 0x([0-9a-f]+)')
|
||||||
|
found, function, pending = [], '?', {}
|
||||||
|
for line in disassembly.splitlines():
|
||||||
|
if line.endswith('>:'):
|
||||||
|
function, pending = line.split('<', 1)[-1][:-2], {}
|
||||||
|
continue
|
||||||
|
match = load.search(line)
|
||||||
|
if match and any(start <= int(match[2], 16) < end for start, end in ranges):
|
||||||
|
pending[match[1]] = line.strip()
|
||||||
|
continue
|
||||||
|
call = re.search(r'callq\s+\*\(%(\w+)\)', line)
|
||||||
|
if call and call[1] in pending:
|
||||||
|
if call[1] != 'rdi':
|
||||||
|
found.append('%s: %s | %s' % (function, pending[call[1]], line.strip()))
|
||||||
|
pending.pop(call[1])
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
status = 0
|
||||||
|
for binary in sys.argv[1:]:
|
||||||
|
problems = bad_accesses(binary)
|
||||||
|
if problems:
|
||||||
|
status = 1
|
||||||
|
print('%s: %d thread-local access(es) without the descriptor in %%rdi' % (binary, len(problems)))
|
||||||
|
for problem in problems[:20]:
|
||||||
|
print(' ' + problem)
|
||||||
|
sys.exit(status)
|
||||||
@@ -41,7 +41,8 @@ subprocess.run(['clang', *flags_for(frameworks / 'AppKit', 'X11_backend'),
|
|||||||
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld',
|
||||||
'-nostdlib', '-isysroot', config['sdk'], str(probe) + '.o',
|
'-nostdlib', '-isysroot', config['sdk'], str(probe) + '.o',
|
||||||
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation',
|
'-lSystem', '-framework', 'AppKit', '-framework', 'Foundation',
|
||||||
'-framework', 'CoreGraphics', '-lobjc', '-o', str(probe)], check=True)
|
'-framework', 'CoreGraphics', '-lobjc',
|
||||||
|
'-L' + str(Path(config['sdk']) / 'usr/lib/native'), '-lX11', '-o', str(probe)], check=True)
|
||||||
|
|
||||||
original = Path(config['prefix']) / config['guest_home'].lstrip('/') / 'RuntimeFrameworks'
|
original = Path(config['prefix']) / config['guest_home'].lstrip('/') / 'RuntimeFrameworks'
|
||||||
with tempfile.TemporaryDirectory(prefix='pr12-display-frameworks-', dir=root / 'build') as directory:
|
with tempfile.TemporaryDirectory(prefix='pr12-display-frameworks-', dir=root / 'build') as directory:
|
||||||
@@ -66,5 +67,5 @@ with tempfile.TemporaryDirectory(prefix='pr12-display-frameworks-', dir=root / '
|
|||||||
output.seek(0)
|
output.seek(0)
|
||||||
lines = [line.rstrip() for line in output if line.startswith('DISPLAY_GEOMETRY')]
|
lines = [line.rstrip() for line in output if line.startswith('DISPLAY_GEOMETRY')]
|
||||||
print('\n'.join(lines))
|
print('\n'.join(lines))
|
||||||
if result.returncode or len(lines) != 6:
|
if result.returncode or len(lines) != 7:
|
||||||
raise SystemExit(f'Display probe failed ({result.returncode}); inspect {log}')
|
raise SystemExit(f'Display probe failed ({result.returncode}); inspect {log}')
|
||||||
@@ -1,24 +1,96 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Clear only a dead Darling server's stale PID marker before startup."""
|
"""Clear a dead Darling server's stale PID marker, and stale shared buffers once no server runs."""
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import signal
|
||||||
import time
|
import time
|
||||||
prefix = Path(os.environ.get('DPREFIX', str(Path.home() / '.darling')))
|
def recover_marker(prefix):
|
||||||
marker = prefix / '.init.pid'
|
marker = prefix / '.init.pid'
|
||||||
try:
|
try:
|
||||||
pid = int(marker.read_text().strip())
|
pid = int(marker.read_text().strip())
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
raise SystemExit(0)
|
pid = 0
|
||||||
if pid <= 0:
|
if pid > 0:
|
||||||
raise SystemExit(0)
|
status = Path('/proc') / str(pid) / 'status'
|
||||||
status = Path('/proc') / str(pid) / 'status'
|
try:
|
||||||
try:
|
state = next(line.split()[1] for line in status.read_text().splitlines() if line.startswith('State:'))
|
||||||
state = next(line.split()[1] for line in status.read_text().splitlines() if line.startswith('State:'))
|
dead = state == 'Z'
|
||||||
dead = state == 'Z'
|
except FileNotFoundError:
|
||||||
except FileNotFoundError:
|
dead = True
|
||||||
dead = True
|
except (OSError, StopIteration):
|
||||||
except (OSError, StopIteration):
|
dead = False
|
||||||
dead = False
|
if dead:
|
||||||
if dead:
|
os.replace(marker, prefix / f'.init.pid.stale-{pid}-{time.time_ns()}')
|
||||||
os.replace(marker, prefix / f'.init.pid.stale-{pid}-{time.time_ns()}')
|
print(f'Recovered stale Darling server marker for PID {pid}', flush=True)
|
||||||
print(f'Recovered stale Darling server marker for PID {pid}', flush=True)
|
|
||||||
|
def any_server_running(proc_root=Path('/proc')):
|
||||||
|
"""True if any darlingserver runs, for any prefix: /dev/shm buffers are not per prefix."""
|
||||||
|
for proc in proc_root.iterdir():
|
||||||
|
if not proc.name.isdigit():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if (proc / 'comm').read_text().strip() == 'darlingserver':
|
||||||
|
return True
|
||||||
|
except PermissionError:
|
||||||
|
# A hidden process might be a server; shared buffers are global, so fail closed.
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def stop_orphaned_launchd(proc_root=Path('/proc')):
|
||||||
|
"""A guest launchd that outlived its darlingserver cannot be shut down with `darling shutdown`
|
||||||
|
and makes the base install (tools/shutdown-user.sh) refuse to continue."""
|
||||||
|
uid = os.getuid()
|
||||||
|
for proc in proc_root.iterdir():
|
||||||
|
if not proc.name.isdigit():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if proc.stat().st_uid != uid or (proc / 'cmdline').read_bytes().rstrip(b'\0') != b'/sbin/launchd':
|
||||||
|
continue
|
||||||
|
os.kill(int(proc.name), signal.SIGKILL)
|
||||||
|
print(f'Stopped orphaned Darling launchd PID {proc.name}', flush=True)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop_riot_crashpad(proc_root=Path('/proc')):
|
||||||
|
"""A Riot crash handler outliving its server holds the old session's ports; spare every other app's."""
|
||||||
|
uid = os.getuid()
|
||||||
|
for proc in proc_root.iterdir():
|
||||||
|
if not proc.name.isdigit():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if not (proc / 'comm').read_text().startswith('chrome_crashpad') or proc.stat().st_uid != uid:
|
||||||
|
continue
|
||||||
|
argv = (proc / 'cmdline').read_bytes().split(b'\0')
|
||||||
|
executable = argv[0].decode(errors='replace')
|
||||||
|
# Match the executable's bundle, not arbitrary arguments (a browser may mention Riot).
|
||||||
|
if any(bundle in executable for bundle in (
|
||||||
|
'/Riot Client.app/', '/League of Legends.app/', '/League of Legends (TM) Client.app/'
|
||||||
|
)):
|
||||||
|
os.kill(int(proc.name), signal.SIGTERM)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def remove_owned(paths):
|
||||||
|
uid = os.getuid()
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
if path.is_file() and path.stat().st_uid == uid:
|
||||||
|
path.unlink()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main():
|
||||||
|
prefix = Path(os.environ.get('DPREFIX', str(Path.home() / '.darling')))
|
||||||
|
recover_marker(prefix)
|
||||||
|
# Frame and present buffers left by a killed session confuse the next one's publishers.
|
||||||
|
if not any_server_running():
|
||||||
|
stop_orphaned_launchd()
|
||||||
|
stop_riot_crashpad()
|
||||||
|
remove_owned((prefix / 'private/tmp').glob('.com.riotgames.*'))
|
||||||
|
remove_owned(Path('/dev/shm').glob('darling-*'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -32,7 +32,11 @@ 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)
|
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
|
python3 tools/stop-darling-runtime.py
|
||||||
# Host fileports own SCM_RIGHTS descriptors and must die with their last sender.
|
# 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
|
||||||
|
else
|
||||||
|
echo 'Skipped the darlingserver install (needs sudo); the base install already ships it: sudo cmake --install build/darling-src/build' >&2
|
||||||
|
fi
|
||||||
# Electron's window-visibility correction lives in the AppKit overlay
|
# Electron's window-visibility correction lives in the AppKit overlay
|
||||||
# (NSWindow -occlusionState, cocotron.patch); no Riot binary is modified here.
|
# (NSWindow -occlusionState, cocotron.patch); no Riot binary is modified here.
|
||||||
for name in IOSurface OpenGL QuartzCore CoreVideo CoreAudio AudioToolbox CoreGraphics CoreText CoreFoundation Metal; do
|
for name in IOSurface OpenGL QuartzCore CoreVideo CoreAudio AudioToolbox CoreGraphics CoreText CoreFoundation Metal; do
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ make -C "$build_dir/src/external/cfnetwork/src" CFNetwork -j8
|
|||||||
make -C "$build_dir/src/external/configd/SystemConfiguration.fproj" SystemConfiguration -j8
|
make -C "$build_dir/src/external/configd/SystemConfiguration.fproj" SystemConfiguration -j8
|
||||||
make -C "$build_dir/src/external/cocotron/QuartzCore" QuartzCore -j8
|
make -C "$build_dir/src/external/cocotron/QuartzCore" QuartzCore -j8
|
||||||
make -C "$build_dir/src/external/cocotron/AppKit" AppKit X11_backend -j8
|
make -C "$build_dir/src/external/cocotron/AppKit" AppKit X11_backend -j8
|
||||||
cc -shared -fPIC -O2 -Wall -Wextra \
|
cc -shared -fPIC -O3 -march=native -mtune=generic -Wall -Wextra \
|
||||||
"-Wl,-soname,$project_dir/build/libdarling-xshm.so" \
|
"-Wl,-soname,$project_dir/build/libdarling-xshm.so" \
|
||||||
"$project_dir/tools/m58f299-xshm.c" -o "$project_dir/build/libdarling-xshm.next.so" \
|
"$project_dir/tools/m58f299-xshm.c" -o "$project_dir/build/libdarling-xshm.next.so" \
|
||||||
-pthread $(pkg-config --cflags --libs x11 x11-xcb xcb-shm xext)
|
-pthread $(pkg-config --cflags --libs x11 x11-xcb xcb-shm xext)
|
||||||
clang -target x86_64-apple-macosx11.0 -fuse-ld=lld -dynamiclib -nostdlib \
|
clang -target x86_64-apple-macosx11.0 -fuse-ld=lld -dynamiclib -nostdlib -O3 -march=native -mtune=generic \
|
||||||
-isysroot @DARLING_SDK@ \
|
-isysroot @DARLING_SDK@ \
|
||||||
-I"$source_dir/src/startup/mldr/elfcalls" \
|
-I"$source_dir/src/startup/mldr/elfcalls" \
|
||||||
"-DDARLING_XSHM_HOST_LIBRARY=\"$project_dir/build/libdarling-xshm.so\"" \
|
"-DDARLING_XSHM_HOST_LIBRARY=\"$project_dir/build/libdarling-xshm.so\"" \
|
||||||
|
|||||||
@@ -256,7 +256,9 @@ static void *pumpFrames(void *arg) {
|
|||||||
XFlush(f->display);
|
XFlush(f->display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deadline.tv_nsec += 2000000;
|
/* darling-lol perf: 500µs poll interval (2000 Hz) reduces average frame
|
||||||
|
* detection latency from 1ms to 0.25ms at negligible CPU cost. */
|
||||||
|
deadline.tv_nsec += 500000;
|
||||||
if (deadline.tv_nsec >= 1000000000) {
|
if (deadline.tv_nsec >= 1000000000) {
|
||||||
deadline.tv_nsec -= 1000000000;
|
deadline.tv_nsec -= 1000000000;
|
||||||
deadline.tv_sec++;
|
deadline.tv_sec++;
|
||||||
|
|||||||
Executable
+202
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Create and install the GNOME / FreeDesktop desktop entry and launcher icon."""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path("@PROJECT_ROOT@")
|
||||||
|
HOST_HOME = Path("@HOST_HOME@")
|
||||||
|
DPREFIX = Path("@DPREFIX@")
|
||||||
|
|
||||||
|
SVG_ICON_CONTENT = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#091428"/>
|
||||||
|
<stop offset="50%" stop-color="#0a1c36"/>
|
||||||
|
<stop offset="100%" stop-color="#030811"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="borderGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#f0e6d2"/>
|
||||||
|
<stop offset="40%" stop-color="#c8aa6e"/>
|
||||||
|
<stop offset="80%" stop-color="#785a28"/>
|
||||||
|
<stop offset="100%" stop-color="#c89b3c"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="goldGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#fff2c6"/>
|
||||||
|
<stop offset="25%" stop-color="#f0e6d2"/>
|
||||||
|
<stop offset="50%" stop-color="#c8aa6e"/>
|
||||||
|
<stop offset="85%" stop-color="#9a7a35"/>
|
||||||
|
<stop offset="100%" stop-color="#785a28"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="goldHighlight" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.6"/>
|
||||||
|
<stop offset="100%" stop-color="#c8aa6e" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feGaussianBlur stdDeviation="6" result="blur"/>
|
||||||
|
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||||
|
<feDropShadow dx="0" dy="8" stdDeviation="14" flood-color="#000000" flood-opacity="0.85"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background Shield / Squircle -->
|
||||||
|
<rect x="24" y="24" width="464" height="464" rx="104" fill="url(#bgGrad)" stroke="url(#borderGrad)" stroke-width="12" filter="url(#shadow)"/>
|
||||||
|
<rect x="40" y="40" width="432" height="432" rx="90" fill="none" stroke="#c8aa6e" stroke-width="2" stroke-opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- Crest Emblem -->
|
||||||
|
<g filter="url(#glow)">
|
||||||
|
<path d="M172 108 L242 108 L242 316 L364 316 L352 388 L156 388 L156 124 Z"
|
||||||
|
fill="url(#goldGrad)" stroke="#463714" stroke-width="3"/>
|
||||||
|
<path d="M 180 120 L 230 120 L 230 250 L 180 200 Z"
|
||||||
|
fill="url(#goldHighlight)"/>
|
||||||
|
<path d="M 242 328 L 348 328 L 340 376 L 172 376 L 172 356 L 242 356 Z"
|
||||||
|
fill="#c8aa6e" opacity="0.45"/>
|
||||||
|
<polygon points="256,76 264,96 284,96 268,108 274,128 256,116 238,128 244,108 228,96 248,96" fill="#f0e6d2"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def extract_best_png_from_icns(icns_path: Path) -> bytes | None:
|
||||||
|
"""Extract the highest-resolution embedded PNG from an Apple ICNS file."""
|
||||||
|
try:
|
||||||
|
data = icns_path.read_bytes()
|
||||||
|
if not data.startswith(b"icns"):
|
||||||
|
return None
|
||||||
|
offset = 8
|
||||||
|
best_png = None
|
||||||
|
best_len = 0
|
||||||
|
while offset + 8 <= len(data):
|
||||||
|
size = int.from_bytes(data[offset + 4 : offset + 8], "big")
|
||||||
|
if size < 8 or offset + size > len(data):
|
||||||
|
break
|
||||||
|
chunk = data[offset + 8 : offset + size]
|
||||||
|
if chunk.startswith(b"\x89PNG\r\n\x1a\n") and len(chunk) > best_len:
|
||||||
|
best_png = chunk
|
||||||
|
best_len = len(chunk)
|
||||||
|
offset += size
|
||||||
|
return best_png
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_candidate_icns() -> Path | None:
|
||||||
|
"""Find any available Riot or League .icns icon files."""
|
||||||
|
candidates = [
|
||||||
|
*list((PROJECT_ROOT / "build/installer").glob("*League of Legends*.app/Contents/Resources/*.icns")),
|
||||||
|
*list(DPREFIX.glob("Users/Shared/Riot Games/Riot Client.app/Contents/Resources/*.icns")),
|
||||||
|
*list(DPREFIX.glob("Applications/Riot Client.app/Contents/Resources/*.icns")),
|
||||||
|
*list(DPREFIX.glob("Applications/League of Legends.app/Contents/Resources/*.icns")),
|
||||||
|
]
|
||||||
|
for c in candidates:
|
||||||
|
if c.is_file() and c.stat().st_size > 1024:
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
icons_base = HOST_HOME / ".local/share/icons"
|
||||||
|
apps_base = HOST_HOME / ".local/share/applications"
|
||||||
|
desktop_dir = HOST_HOME / "Desktop"
|
||||||
|
|
||||||
|
scalable_dir = icons_base / "hicolor/scalable/apps"
|
||||||
|
png_dir = icons_base / "hicolor/512x512/apps"
|
||||||
|
scalable_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
png_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
apps_base.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 1. Install scalable SVG icon
|
||||||
|
svg_target = scalable_dir / "league-of-legends.svg"
|
||||||
|
svg_target.write_text(SVG_ICON_CONTENT)
|
||||||
|
shutil.copyfile(svg_target, icons_base / "league-of-legends.svg")
|
||||||
|
|
||||||
|
# 2. Extract or generate PNG icon
|
||||||
|
png_target = png_dir / "league-of-legends.png"
|
||||||
|
icns_file = find_candidate_icns()
|
||||||
|
extracted = False
|
||||||
|
if icns_file:
|
||||||
|
png_data = extract_best_png_from_icns(icns_file)
|
||||||
|
if png_data:
|
||||||
|
png_target.write_bytes(png_data)
|
||||||
|
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
|
||||||
|
extracted = True
|
||||||
|
print(f"Extracted official icon from {icns_file.name} to {png_target}")
|
||||||
|
|
||||||
|
if not extracted and not png_target.exists():
|
||||||
|
# Try to rasterize SVG via rsvg-convert or magick
|
||||||
|
rsvg = shutil.which("rsvg-convert")
|
||||||
|
magick = shutil.which("magick") or shutil.which("convert")
|
||||||
|
if rsvg:
|
||||||
|
subprocess.run([rsvg, "-w", "512", "-h", "512", str(svg_target), "-o", str(png_target)], check=False)
|
||||||
|
if png_target.exists():
|
||||||
|
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
|
||||||
|
elif magick:
|
||||||
|
subprocess.run([magick, "-background", "none", "-resize", "512x512", str(svg_target), str(png_target)], check=False)
|
||||||
|
if png_target.exists():
|
||||||
|
shutil.copyfile(png_target, icons_base / "league-of-legends.png")
|
||||||
|
|
||||||
|
# 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_cmd}
|
||||||
|
Path={PROJECT_ROOT}
|
||||||
|
Icon=league-of-legends
|
||||||
|
Terminal=false
|
||||||
|
Categories=Game;ActionGame;StrategyGame;
|
||||||
|
Keywords=riot;league;lol;darling;games;
|
||||||
|
StartupWMClass=Riot Client
|
||||||
|
StartupNotify=false
|
||||||
|
"""
|
||||||
|
|
||||||
|
desktop_file = apps_base / "league-of-legends.desktop"
|
||||||
|
desktop_file.write_text(desktop_content)
|
||||||
|
os.chmod(desktop_file, 0o755)
|
||||||
|
print(f"Created application launcher: {desktop_file}")
|
||||||
|
|
||||||
|
target_desktop_dirs = []
|
||||||
|
try:
|
||||||
|
res = subprocess.run(["xdg-user-dir", "DESKTOP"], capture_output=True, text=True, check=False)
|
||||||
|
if res.returncode == 0 and res.stdout.strip():
|
||||||
|
d = Path(res.stdout.strip())
|
||||||
|
if d.is_dir() and d not in target_desktop_dirs:
|
||||||
|
target_desktop_dirs.append(d)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for cand in [HOST_HOME / "Desktop"]:
|
||||||
|
if cand.is_dir() and cand not in target_desktop_dirs:
|
||||||
|
target_desktop_dirs.append(cand)
|
||||||
|
|
||||||
|
for ddir in target_desktop_dirs:
|
||||||
|
desktop_shortcut = ddir / "league-of-legends.desktop"
|
||||||
|
desktop_shortcut.write_text(desktop_content)
|
||||||
|
os.chmod(desktop_shortcut, 0o755)
|
||||||
|
gio = shutil.which("gio")
|
||||||
|
if gio:
|
||||||
|
subprocess.run([gio, "set", str(desktop_shortcut), "metadata::trusted", "true"], check=False)
|
||||||
|
print(f"Created desktop shortcut: {desktop_shortcut}")
|
||||||
|
|
||||||
|
# 4. Refresh icon and desktop databases
|
||||||
|
update_desktop = shutil.which("update-desktop-database")
|
||||||
|
if update_desktop:
|
||||||
|
subprocess.run([update_desktop, str(apps_base)], check=False)
|
||||||
|
|
||||||
|
update_icons = shutil.which("gtk-update-icon-cache")
|
||||||
|
if update_icons:
|
||||||
|
subprocess.run([update_icons, "-f", "-t", str(icons_base / "hicolor")], check=False)
|
||||||
|
|
||||||
|
print("GNOME application launcher and icon installed successfully.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Display.h"
|
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Display.h"
|
||||||
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Window.h"
|
#import "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11Window.h"
|
||||||
#include <X11/extensions/Xrandr.h>
|
#include <X11/extensions/Xrandr.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -20,6 +21,27 @@ extern CGError CGWarpMouseCursorPosition(CGPoint position);
|
|||||||
- (void)postXEvent:(XEvent *)event;
|
- (void)postXEvent:(XEvent *)event;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface X11Window (FullscreenProbe)
|
||||||
|
- (BOOL)wantsFullscreenWithStyleMask:(NSUInteger)mask rootFrame:(O2Rect)frame;
|
||||||
|
@end
|
||||||
|
@interface FullscreenProbeWindow : NSWindow
|
||||||
|
@end
|
||||||
|
@implementation FullscreenProbeWindow
|
||||||
|
- (BOOL)canBecomeKeyWindow { return YES; }
|
||||||
|
@end
|
||||||
|
static BOOL fullscreenProperty(X11Display *display, X11Window *window) {
|
||||||
|
Display *d = [display display];
|
||||||
|
Atom actual, fs = XInternAtom(d,"_NET_WM_STATE_FULLSCREEN",False);
|
||||||
|
int format; unsigned long count, remaining; unsigned char *data = NULL;
|
||||||
|
assert(XGetWindowProperty(d,[window windowHandle],XInternAtom(d,"_NET_WM_STATE",False),
|
||||||
|
0,32,False,XA_ATOM,&actual,&format,&count,&remaining,&data) == Success);
|
||||||
|
BOOL found = NO;
|
||||||
|
assert(actual == XA_ATOM && format == 32 && remaining == 0);
|
||||||
|
for (unsigned long i=0; i<count; ++i) if (((Atom *)data)[i] == fs) found=YES;
|
||||||
|
if (data) XFree(data);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
static void *slot(id obj, const char *name) {
|
static void *slot(id obj, const char *name) {
|
||||||
Ivar ivar = class_getInstanceVariable([obj class], name);
|
Ivar ivar = class_getInstanceVariable([obj class], name);
|
||||||
assert(ivar);
|
assert(ivar);
|
||||||
@@ -102,16 +124,40 @@ int main(void) {
|
|||||||
assert(CGGetDisplaysWithRect(CGRectMake(3900,-800,10,10),4,ids,&count) == 0 && count == 1 && ids[0] == 3);
|
assert(CGGetDisplaysWithRect(CGRectMake(3900,-800,10,10),4,ids,&count) == 0 && count == 1 && ids[0] == 3);
|
||||||
puts("DISPLAY_GEOMETRY reordered_ids_bounds_enumeration_queries=pass");
|
puts("DISPLAY_GEOMETRY reordered_ids_bounds_enumeration_queries=pass");
|
||||||
|
|
||||||
|
// Use cached monitor frames so the policy is tested for the reporter's layout.
|
||||||
|
FullscreenProbeWindow *game = [[FullscreenProbeWindow alloc]
|
||||||
|
initWithContentRect:NSMakeRect(0,0,3840,2160) styleMask:NSBorderlessWindowMask
|
||||||
|
backing:NSBackingStoreBuffered defer:NO];
|
||||||
|
X11Window *gameNative = (id)[game platformWindow];
|
||||||
|
assert(!*(BOOL *)slot(gameNative,"_overrideRedirect"));
|
||||||
|
assert(fullscreenProperty(d,gameNative));
|
||||||
|
[gameNative setFrame:NSMakeRect(0,0,3840,2100)]; // panel-sized work area is not a monitor
|
||||||
|
assert(!fullscreenProperty(d,gameNative));
|
||||||
|
[gameNative setFrame:NSMakeRect(-3840,102,3840,2160)]; // secondary monitor
|
||||||
|
assert(fullscreenProperty(d,gameNative));
|
||||||
|
NSRect full = [d rootRectForFrame:NSMakeRect(0,0,3840,2160)];
|
||||||
|
assert(![gameNative wantsFullscreenWithStyleMask:NSTitledWindowMask rootFrame:full]);
|
||||||
|
assert([gameNative wantsFullscreenWithStyleMask:NSWindowStyleMaskFullScreen
|
||||||
|
rootFrame:NSMakeRect(1,2,640,480)]);
|
||||||
|
*(BOOL *)slot(gameNative,"_overrideRedirect") = YES;
|
||||||
|
assert(![gameNative wantsFullscreenWithStyleMask:0 rootFrame:full]);
|
||||||
|
*(BOOL *)slot(gameNative,"_overrideRedirect") = NO;
|
||||||
|
[gameNative setFrame:NSMakeRect(20,30,800,600)];
|
||||||
|
assert(!fullscreenProperty(d,gameNative));
|
||||||
|
[game release];
|
||||||
|
puts("DISPLAY_GEOMETRY borderless_fullscreen_monitor_and_resize_policy=pass");
|
||||||
|
|
||||||
Method method = class_getInstanceMethod([d class],@selector(warpPointerToRootX:y:));
|
Method method = class_getInstanceMethod([d class],@selector(warpPointerToRootX:y:));
|
||||||
assert(method);
|
assert(method);
|
||||||
IMP original = method_setImplementation(method,(IMP)captureWarp);
|
IMP original = method_setImplementation(method,(IMP)captureWarp);
|
||||||
X11RelativeMouse *relative = slot(d,"_relativeMouse");
|
X11RelativeMouse *relative = slot(d,"_relativeMouse");
|
||||||
X11RelativeMouseBegin(relative,3900,1000);
|
X11RelativeMouseBegin(relative,3900,1000);
|
||||||
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
|
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
|
||||||
assert(relative->virtualX == 3940 && relative->virtualY == 1010);
|
BOOL honoured = !*(BOOL *)slot(d,"_isXwayland") || *(BOOL *)slot(d,"_cursorHidden");
|
||||||
|
assert(relative->virtualX == (honoured ? 3940 : 3900) && relative->virtualY == (honoured ? 1010 : 1000));
|
||||||
NSPoint location = [d mouseLocation];
|
NSPoint location = [d mouseLocation];
|
||||||
assert(location.x == 100 && location.y == 1960);
|
assert(location.x == (honoured ? 100 : 60) && location.y == (honoured ? 1960 : 1970));
|
||||||
X11RelativeMouseMotion motion = X11RelativeMouseHandleMotion(relative,3907,1009,false);
|
X11RelativeMouseMotion motion = X11RelativeMouseHandleMotion(relative,3907,1009,false,NextRequest([d display]));
|
||||||
assert(motion.deltaX == 7 && motion.deltaY == 9);
|
assert(motion.deltaX == 7 && motion.deltaY == 9);
|
||||||
X11RelativeMouseEnd(relative);
|
X11RelativeMouseEnd(relative);
|
||||||
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
|
assert(CGWarpMouseCursorPosition(CGPointMake(100,200)) == 0);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/* Read-only XRandR geometry probe: never creates, maps or changes a window. */
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xrandr.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
@autoreleasepool {
|
||||||
|
assert(argc == 2);
|
||||||
|
void *backend = dlopen(argv[1], RTLD_LAZY);
|
||||||
|
assert(backend);
|
||||||
|
BOOL (*covers)(Display *, NSRect) = (BOOL (*)(Display *, NSRect))dlsym(backend,"X11RootRectCoversMonitor");
|
||||||
|
assert(covers);
|
||||||
|
assert(!covers(NULL, NSMakeRect(0,0,1,1)));
|
||||||
|
assert(!covers(NULL, NSMakeRect(0,0,0,0)));
|
||||||
|
void *xlib = dlopen("/usr/lib/native/libX11.dylib", RTLD_LAZY);
|
||||||
|
void *randr = dlopen("/usr/lib/native/libXRandR.dylib", RTLD_LAZY);
|
||||||
|
assert(xlib && randr);
|
||||||
|
Display *(*openDisplay)(const char *) = dlsym(xlib,"XOpenDisplay");
|
||||||
|
int (*closeDisplay)(Display *) = dlsym(xlib,"XCloseDisplay");
|
||||||
|
Window (*rootWindow)(Display *) = dlsym(xlib,"XDefaultRootWindow");
|
||||||
|
XRRScreenResources *(*getResources)(Display *,Window) = dlsym(randr,"XRRGetScreenResourcesCurrent");
|
||||||
|
XRRCrtcInfo *(*getCrtc)(Display *,XRRScreenResources *,RRCrtc) = dlsym(randr,"XRRGetCrtcInfo");
|
||||||
|
void (*freeCrtc)(XRRCrtcInfo *) = dlsym(randr,"XRRFreeCrtcInfo");
|
||||||
|
void (*freeResources)(XRRScreenResources *) = dlsym(randr,"XRRFreeScreenResources");
|
||||||
|
assert(openDisplay && closeDisplay && rootWindow && getResources && getCrtc && freeCrtc && freeResources);
|
||||||
|
Display *d = openDisplay(NULL); assert(d);
|
||||||
|
XRRScreenResources *resources = getResources(d,rootWindow(d)); assert(resources);
|
||||||
|
unsigned checked = 0;
|
||||||
|
for (int i=0; i<resources->ncrtc; ++i) {
|
||||||
|
XRRCrtcInfo *c = getCrtc(d,resources,resources->crtcs[i]); assert(c);
|
||||||
|
if (c->mode) {
|
||||||
|
assert(covers(d,NSMakeRect(c->x,c->y,c->width,c->height)));
|
||||||
|
assert(!covers(d,NSMakeRect(c->x,c->y,20,20)));
|
||||||
|
assert(!covers(d,NSMakeRect(c->x,c->y,c->width/2,c->height/2)));
|
||||||
|
++checked;
|
||||||
|
}
|
||||||
|
freeCrtc(c);
|
||||||
|
}
|
||||||
|
freeResources(resources); closeDisplay(d);
|
||||||
|
assert(checked);
|
||||||
|
printf("FULLSCREEN_GEOMETRY startup_rejected=1 windowed_rejected=1 active_monitors=%u\n",checked);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test the shipped relative-mouse implementation without fetching Darling or opening X11."""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('--xvfb', help='also exercise Xorg protocol on this private Xvfb executable')
|
||||||
|
args = parser.parse_args()
|
||||||
|
root = Path(__file__).resolve().parent.parent
|
||||||
|
name = 'AppKit/X11.backend/X11RelativeMouse.h'
|
||||||
|
patch = (root / 'patches/cocotron.patch').read_text()
|
||||||
|
section = patch.split(f'diff --git a/{name} b/{name}\n', 1)[1].split('\ndiff --git ', 1)[0]
|
||||||
|
# This header is a new file: all of its source lines are additions after the first hunk header.
|
||||||
|
body = section.split('@@\n', 1)[1]
|
||||||
|
assert all(line.startswith('+') for line in body.splitlines()), 'relative mouse header must be a complete added file'
|
||||||
|
with tempfile.TemporaryDirectory(prefix='darling-input-check-') as folder:
|
||||||
|
folder = Path(folder)
|
||||||
|
header = folder / 'X11RelativeMouse.h'
|
||||||
|
header.write_text('\n'.join(line[1:] for line in body.splitlines()) + '\n')
|
||||||
|
binary = folder / 'relative-mouse-check'
|
||||||
|
subprocess.run([*shlex.split(os.environ.get('CC', 'cc')), '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||||
|
f'-DX11_RELATIVE_MOUSE_HEADER="{header}"', str(root / 'tools/test-x11-relative-mouse.c'),
|
||||||
|
'-o', str(binary)], check=True)
|
||||||
|
subprocess.run([str(binary)], check=True)
|
||||||
|
|
||||||
|
if args.xvfb:
|
||||||
|
protocol = folder / 'relative-mouse-xorg-check'
|
||||||
|
subprocess.run([*shlex.split(os.environ.get('CC', 'cc')), '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||||
|
f'-DX11_RELATIVE_MOUSE_HEADER="{header}"', str(root / 'tools/test-x11-relative-mouse-xorg.c'),
|
||||||
|
'-lX11', '-o', str(protocol)], check=True)
|
||||||
|
# -displayfd probes low display numbers and can unlink an existing Xwayland socket.
|
||||||
|
# Pick a high unused display and let Xvfb's normal lock handling protect it instead.
|
||||||
|
number = next(n for n in range(1000, 1100)
|
||||||
|
if not Path(f'/tmp/.X{n}-lock').exists()
|
||||||
|
and not Path(f'/tmp/.X11-unix/X{n}').exists()
|
||||||
|
and not Path(f'/tmp/.X11-unix/X{n}_').exists())
|
||||||
|
private_display = ':' + str(number)
|
||||||
|
server = subprocess.Popen([args.xvfb, private_display, '-screen', '0', '3840x1080x24',
|
||||||
|
'-nolisten', 'tcp', '-ac', '-noreset'],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||||
|
try:
|
||||||
|
deadline = time.monotonic() + 15
|
||||||
|
while not Path(f'/tmp/.X11-unix/X{number}').is_socket():
|
||||||
|
if server.poll() is not None:
|
||||||
|
raise RuntimeError('private Xvfb failed: ' + server.stderr.read().decode())
|
||||||
|
if time.monotonic() >= deadline:
|
||||||
|
raise RuntimeError('private Xvfb did not become ready')
|
||||||
|
time.sleep(0.02)
|
||||||
|
subprocess.run([str(protocol), private_display], check=True, timeout=20)
|
||||||
|
finally:
|
||||||
|
server.terminate()
|
||||||
|
server.communicate(timeout=10)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/* No display connection or window is created. Exercise the real backend with a cached remote
|
||||||
|
* frame to ensure a client slot cannot take ownership of League's native Metal drawable. */
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <CoreFoundation/CoreFoundation.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
@interface FrameOwner : NSObject
|
||||||
|
@property(copy) NSString *title;
|
||||||
|
- (void)platformWindowDidInvalidateCGContext:(id)window;
|
||||||
|
@end
|
||||||
|
@implementation FrameOwner
|
||||||
|
@synthesize title;
|
||||||
|
- (void)platformWindowDidInvalidateCGContext:(id)window {}
|
||||||
|
@end
|
||||||
|
@interface NSObject (RemoteFrameProbe)
|
||||||
|
- (BOOL)presentRemoteCAFrame:(id)timer;
|
||||||
|
@end
|
||||||
|
static void *slot(id object, const char *name) {
|
||||||
|
Ivar ivar = class_getInstanceVariable([object class], name);
|
||||||
|
assert(ivar);
|
||||||
|
return (char *)object + ivar_getOffset(ivar);
|
||||||
|
}
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
@autoreleasepool {
|
||||||
|
assert(argc == 2);
|
||||||
|
void *backend = dlopen(argv[1], RTLD_LAZY);
|
||||||
|
if (!backend) { fprintf(stderr,"%s\n",dlerror()); return 1; }
|
||||||
|
Class cls = NSClassFromString(@"X11Window");
|
||||||
|
assert(cls);
|
||||||
|
id native = [cls alloc]; /* Deliberately skip X11 initialization: cached-frame path needs no display. */
|
||||||
|
FrameOwner *owner = [FrameOwner new];
|
||||||
|
*(id *)slot(native,"_delegate") = owner;
|
||||||
|
*(BOOL *)slot(native,"_mapped") = YES;
|
||||||
|
*(BOOL *)slot(native,"_remoteFrameValid") = YES;
|
||||||
|
*(unsigned *)slot(native,"_remoteFrameTargetCount") = 1;
|
||||||
|
*(double *)slot(native,"_remoteFrameTargetRefresh") = CFAbsoluteTimeGetCurrent() + 60;
|
||||||
|
*(double *)slot(native,"_remoteFrameSelectionRefresh") = CFAbsoluteTimeGetCurrent() + 60;
|
||||||
|
owner.title = @"Riot Client";
|
||||||
|
assert([native presentRemoteCAFrame:nil]); /* Client's cached presentation remains usable. */
|
||||||
|
owner.title = @"League of Legends (TM) Client";
|
||||||
|
assert(![native presentRemoteCAFrame:nil]); /* Same cached frame cannot cover the game. */
|
||||||
|
[native release];
|
||||||
|
[owner release];
|
||||||
|
puts("REMOTE_FRAME_OWNERSHIP client_preserved=1 native_game_protected=1");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check stale-session cleanup against fake processes; never signal a real process."""
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
from unittest.mock import patch, Mock, MagicMock
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location('cleanup', Path(__file__).with_name('ensure-darling-runtime.py'))
|
||||||
|
cleanup = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(cleanup)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix='darling-cleanup-check-') as folder:
|
||||||
|
root = Path(folder)
|
||||||
|
def process(pid, comm, argv):
|
||||||
|
proc = root / str(pid)
|
||||||
|
proc.mkdir()
|
||||||
|
(proc / 'comm').write_text(comm + '\n')
|
||||||
|
(proc / 'cmdline').write_bytes(b'\0'.join(arg.encode() for arg in argv) + b'\0')
|
||||||
|
return proc
|
||||||
|
browser = process(101, 'chrome_crashpad', ['/opt/google/chrome/chrome_crashpad_handler', '--annotation=riot'])
|
||||||
|
riot = process(102, 'chrome_crashpad', ['/Applications/Riot Client.app/Contents/Frameworks/chrome_crashpad_handler'])
|
||||||
|
launchd = process(103, 'launchd', ['/sbin/launchd'])
|
||||||
|
(launchd / 'cmdline').write_bytes(b'/sbin/launchd' + b'\0' * 8)
|
||||||
|
process(104, 'unrelated', ['/usr/bin/unrelated', '/sbin/launchd'])
|
||||||
|
with patch.object(cleanup.os, 'kill') as kill:
|
||||||
|
cleanup.stop_riot_crashpad(root)
|
||||||
|
assert kill.call_args_list == [((102, cleanup.signal.SIGTERM),)]
|
||||||
|
kill.reset_mock()
|
||||||
|
cleanup.stop_orphaned_launchd(root)
|
||||||
|
assert kill.call_args_list == [((103, cleanup.signal.SIGKILL),)]
|
||||||
|
assert not cleanup.any_server_running(root)
|
||||||
|
process(105, 'darlingserver', ['darlingserver', '--prefix=another-prefix'])
|
||||||
|
assert cleanup.any_server_running(root)
|
||||||
|
hidden = MagicMock()
|
||||||
|
hidden.name = '106'
|
||||||
|
(hidden / 'comm').read_text.side_effect = PermissionError()
|
||||||
|
fake_root = Mock()
|
||||||
|
fake_root.iterdir.return_value = [hidden]
|
||||||
|
assert cleanup.any_server_running(fake_root)
|
||||||
|
# Foreign-owned Riot processes and buffers must survive even when no server runs.
|
||||||
|
with patch.object(Path, 'stat', return_value=Mock(st_uid=os.getuid() + 1)), patch.object(cleanup.os, 'kill') as kill:
|
||||||
|
cleanup.stop_riot_crashpad(root)
|
||||||
|
cleanup.stop_orphaned_launchd(root)
|
||||||
|
kill.assert_not_called()
|
||||||
|
buffer = root / 'darling-buffer'
|
||||||
|
buffer.write_text('stale')
|
||||||
|
with patch.object(Path, 'stat', return_value=Mock(st_uid=os.getuid() + 1, st_mode=0o100600)):
|
||||||
|
cleanup.remove_owned([buffer])
|
||||||
|
assert buffer.exists()
|
||||||
|
cleanup.remove_owned([buffer])
|
||||||
|
assert not buffer.exists()
|
||||||
|
# The entry point must preserve all sessions while ANY prefix has a server.
|
||||||
|
with patch.object(cleanup, 'recover_marker'), patch.object(cleanup, 'any_server_running', return_value=True), \
|
||||||
|
patch.object(cleanup, 'stop_riot_crashpad') as crashpad, \
|
||||||
|
patch.object(cleanup, 'stop_orphaned_launchd') as orphan, patch.object(cleanup, 'remove_owned') as remove:
|
||||||
|
cleanup.main()
|
||||||
|
crashpad.assert_not_called()
|
||||||
|
orphan.assert_not_called()
|
||||||
|
remove.assert_not_called()
|
||||||
|
print('RUNTIME_CLEANUP_OFFLINE pass=1 browser_preserved=1 other_prefix_preserved=1 foreign_owner_preserved=1 hidden_process_preserved=1 orphan_recovered=1')
|
||||||
@@ -17,7 +17,7 @@ unset RCLOL_APP_ARG3 RCLOL_APP_ARG3_SLOT RCLOL_UX_ARG1 RCLOL_UX_ARG1_SLOT
|
|||||||
unset RCLOL_UX_ARG2 RCLOL_UX_ARG2_SLOT
|
unset RCLOL_UX_ARG2 RCLOL_UX_ARG2_SLOT
|
||||||
# The paired runtime presents completed GPU textures without CPU readback.
|
# The paired runtime presents completed GPU textures without CPU readback.
|
||||||
# Explicit 0 or an empty value selects the CPU fallback for diagnostics.
|
# Explicit 0 or an empty value selects the CPU fallback for diagnostics.
|
||||||
for option in RCLOL_GPU_DIRECT_PRESENT RCLOL_GPU_DIRECT_ASYNC RCLOL_GPU_DIRECT_VSYNC DARLING_CA_PRESENT_AT_COMMIT DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER RCLOL_FORWARD_MOUSE_MOVED; do
|
for option in RCLOL_GPU_DIRECT_PRESENT RCLOL_GPU_DIRECT_ASYNC RCLOL_GPU_DIRECT_VSYNC DARLING_CA_PRESENT_AT_COMMIT DARLING_CA_SKIP_LOCAL_IOSURFACE_RENDER RCLOL_FORWARD_MOUSE_MOVED RCLOL_MOUSE_CONFINEMENT; do
|
||||||
eval 'value=${'"$option"'-1}'
|
eval 'value=${'"$option"'-1}'
|
||||||
case "$value" in 0|'') unset "$option";; *) export "$option=$value";; esac
|
case "$value" in 0|'') unset "$option";; *) export "$option=$value";; esac
|
||||||
done
|
done
|
||||||
@@ -60,6 +60,27 @@ else
|
|||||||
--launch-patchline="${RCLOL_LAUNCH_PATCHLINE-live}"
|
--launch-patchline="${RCLOL_LAUNCH_PATCHLINE-live}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# --- darling-lol perf: scheduling & driver latency hints ---
|
||||||
|
# Refresh rate for display link, async presenter, and frame pacing: the primary monitor's
|
||||||
|
# current mode (the first monitor without a primary); unset falls back to 60 Hz.
|
||||||
|
if [ -z "${RCLOL_REFRESH_HZ-}" ] && command -v xrandr >/dev/null 2>&1; then
|
||||||
|
RCLOL_REFRESH_HZ=$(xrandr --current 2>/dev/null | awk '
|
||||||
|
/ connected/ { primary = / primary / }
|
||||||
|
/\*/ { for (i = 2; i <= NF; i++) if ($i ~ /\*/) { rate = $i; sub(/[*+]+/, "", rate)
|
||||||
|
if (primary) { print int(rate + 0.5); found = 1; exit } else if (first == "") first = int(rate + 0.5) } }
|
||||||
|
END { if (!found && first != "") print first }')
|
||||||
|
fi
|
||||||
|
if [ -n "${RCLOL_REFRESH_HZ-}" ]; then export RCLOL_REFRESH_HZ; fi
|
||||||
|
# Vulkan WSI & Mesa optimizations
|
||||||
|
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}"
|
||||||
|
export __GL_YIELD="${__GL_YIELD:-NOTHING}"
|
||||||
|
# Reduce timer slack for tighter scheduling in the game process tree.
|
||||||
|
if [ -w /proc/self/timerslack_ns ]; then
|
||||||
|
echo 1 > /proc/self/timerslack_ns 2>/dev/null || true
|
||||||
|
fi
|
||||||
exec env -u ELECTRON_RUN_AS_NODE -u DYLD_INSERT_LIBRARIES EGL_PLATFORM=x11 \
|
exec env -u ELECTRON_RUN_AS_NODE -u DYLD_INSERT_LIBRARIES EGL_PLATFORM=x11 \
|
||||||
LANG="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" LC_ALL="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" \
|
LANG="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" LC_ALL="${RCLOL_GUEST_LOCALE:-en_US.UTF-8}" \
|
||||||
mesa_glthread="${RCLOL_MESA_GLTHREAD-${mesa_glthread-false}}" \
|
mesa_glthread="${RCLOL_MESA_GLTHREAD-${mesa_glthread-false}}" \
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/* Protocol regression on a PRIVATE Xvfb server, never the user's DISPLAY. */
|
||||||
|
#ifndef X11_RELATIVE_MOUSE_HEADER
|
||||||
|
#define X11_RELATIVE_MOUSE_HEADER "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11RelativeMouse.h"
|
||||||
|
#endif
|
||||||
|
#include X11_RELATIVE_MOUSE_HEADER
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static XEvent nextMotion(Display *d) {
|
||||||
|
XEvent event;
|
||||||
|
do { XNextEvent(d, &event); } while (event.type != MotionNotify);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
static void movePhysical(Display *other, int x, int y) {
|
||||||
|
XWarpPointer(other, None, DefaultRootWindow(other), 0, 0, 0, 0, x, y);
|
||||||
|
XSync(other, False);
|
||||||
|
}
|
||||||
|
static X11RelativeMouseMotion handle(X11RelativeMouse *mouse, XEvent event) {
|
||||||
|
return X11RelativeMouseHandleMotion(mouse, event.xmotion.x_root, event.xmotion.y_root, true,
|
||||||
|
event.xmotion.serial);
|
||||||
|
}
|
||||||
|
static unsigned warp(Display *d, int x, int y) {
|
||||||
|
XWarpPointer(d, None, DefaultRootWindow(d), 0, 0, 0, 0, x, y);
|
||||||
|
XSync(d, False);
|
||||||
|
return XEventsQueued(d, QueuedAlready);
|
||||||
|
}
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
assert(argc == 2); /* The runner supplies the dedicated server explicitly. */
|
||||||
|
Display *d = XOpenDisplay(argv[1]), *other = XOpenDisplay(argv[1]);
|
||||||
|
assert(d && other);
|
||||||
|
Window win = XCreateSimpleWindow(d, DefaultRootWindow(d), 1920, 24, 1920, 1056, 0, 0, 0);
|
||||||
|
XSelectInput(d, win, PointerMotionMask);
|
||||||
|
XMapWindow(d, win);
|
||||||
|
XSync(d, False);
|
||||||
|
movePhysical(other, 2500, 400);
|
||||||
|
XSync(d, True);
|
||||||
|
assert(XGrabPointer(d, win, False, PointerMotionMask, GrabModeAsync, GrabModeAsync,
|
||||||
|
win, None, CurrentTime) == GrabSuccess);
|
||||||
|
/* Leave an older motion queued while taking the fresh grab snapshot. */
|
||||||
|
movePhysical(other, 2510, 400);
|
||||||
|
Window root, child; int rx, ry, wx, wy; unsigned state;
|
||||||
|
unsigned long snapshot = NextRequest(d);
|
||||||
|
assert(XQueryPointer(d, win, &root, &child, &rx, &ry, &wx, &wy, &state));
|
||||||
|
X11RelativeMouse m = {0};
|
||||||
|
X11RelativeMouseBegin(&m, rx, ry); m.firstSerial = snapshot;
|
||||||
|
assert(rx == 2510 && ry == 400);
|
||||||
|
X11RelativeMouseMotion r = handle(&m, nextMotion(d));
|
||||||
|
assert(!r.post && !r.rewarp && m.physicalX == 2510);
|
||||||
|
|
||||||
|
movePhysical(other, 2520, 395);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.post && r.deltaX == 10 && r.deltaY == -5 && r.rewarp);
|
||||||
|
unsigned long serial = NextRequest(d);
|
||||||
|
unsigned queued = warp(d, r.warpX, r.warpY);
|
||||||
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, queued, serial);
|
||||||
|
XEvent echo = nextMotion(d);
|
||||||
|
assert(echo.xmotion.serial == serial);
|
||||||
|
r = handle(&m, echo);
|
||||||
|
assert(r.echo && !r.post && m.physicalX == 2510);
|
||||||
|
puts("PASS: real Xorg grab snapshot and warp echo request serial");
|
||||||
|
|
||||||
|
/* Queue real motion across the rewarp target BEFORE requesting a rewarp. */
|
||||||
|
movePhysical(other, 2530, 400);
|
||||||
|
movePhysical(other, 2510, 400);
|
||||||
|
serial = NextRequest(d);
|
||||||
|
queued = warp(d, 2510, 400);
|
||||||
|
X11RelativeMouseExpectEcho(&m, 2510, 400, queued, serial);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.post && !r.echo && r.deltaX == 20 && !r.rewarp);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.post && !r.echo && r.deltaX == -20);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.echo && !r.post);
|
||||||
|
puts("PASS: real queued motion crossing the anchor is not swallowed as an echo");
|
||||||
|
|
||||||
|
m.bounded = true; m.minX = 1920; m.minY = 24; m.maxX = 3839; m.maxY = 1079;
|
||||||
|
double x = 3900, y = -100;
|
||||||
|
X11RelativeMouseClamp(&m, &x, &y);
|
||||||
|
/* Send the raw target here to verify the X server clips it exactly as the backend does. */
|
||||||
|
serial = NextRequest(d); queued = warp(d, 3900, -100);
|
||||||
|
X11RelativeMouseWarp(&m, x, y, queued, serial, true);
|
||||||
|
assert(XQueryPointer(d, win, &root, &child, &rx, &ry, &wx, &wy, &state));
|
||||||
|
assert(rx == 3839 && ry == 24 && m.virtualX == rx && m.virtualY == ry);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.echo && !r.post);
|
||||||
|
movePhysical(other, 3830, 30);
|
||||||
|
r = handle(&m, nextMotion(d));
|
||||||
|
assert(r.post && r.deltaX == -9 && r.deltaY == 6);
|
||||||
|
puts("PASS: real Xorg confinement on a secondary monitor agrees with the virtual cursor");
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
|
XUngrabPointer(d, CurrentTime);
|
||||||
|
XDestroyWindow(d, win);
|
||||||
|
XCloseDisplay(other); XCloseDisplay(d);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+119
-20
@@ -1,4 +1,7 @@
|
|||||||
#include "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11RelativeMouse.h"
|
#ifndef X11_RELATIVE_MOUSE_HEADER
|
||||||
|
#define X11_RELATIVE_MOUSE_HEADER "../build/darling-src/src/external/cocotron/AppKit/X11.backend/X11RelativeMouse.h"
|
||||||
|
#endif
|
||||||
|
#include X11_RELATIVE_MOUSE_HEADER
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -6,7 +9,7 @@
|
|||||||
* A warp's echo is simulated as the MotionNotify the server queues at the target; `queued` is the number of
|
* A warp's echo is simulated as the MotionNotify the server queues at the target; `queued` is the number of
|
||||||
* X events queued after the backend's XSync. */
|
* X events queued after the backend's XSync. */
|
||||||
static X11RelativeMouseMotion motion(X11RelativeMouse *m, int x, int y, bool honoured) {
|
static X11RelativeMouseMotion motion(X11RelativeMouse *m, int x, int y, bool honoured) {
|
||||||
return X11RelativeMouseHandleMotion(m, x, y, honoured);
|
return X11RelativeMouseHandleMotion(m, x, y, honoured, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void xorgHonouredWarps(void) {
|
static void xorgHonouredWarps(void) {
|
||||||
@@ -16,22 +19,22 @@ static void xorgHonouredWarps(void) {
|
|||||||
X11RelativeMouseMotion r = motion(&m, 510, 395, true);
|
X11RelativeMouseMotion r = motion(&m, 510, 395, true);
|
||||||
assert(r.post && !r.echo && r.deltaX == 10 && r.deltaY == -5);
|
assert(r.post && !r.echo && r.deltaX == 10 && r.deltaY == -5);
|
||||||
assert(r.rewarp && r.warpX == 500 && r.warpY == 400);
|
assert(r.rewarp && r.warpX == 500 && r.warpY == 400);
|
||||||
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1);
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1, 0);
|
||||||
/* The rewarp's echo is consumed and is not a delta back to the centre. */
|
/* The rewarp's echo is consumed and is not a delta back to the centre. */
|
||||||
r = motion(&m, 500, 400, true);
|
r = motion(&m, 500, 400, true);
|
||||||
assert(!r.post && r.echo && !r.rewarp && m.echoCount == 0);
|
assert(!r.post && r.echo && !r.rewarp && m.echoCount == 0);
|
||||||
assert(m.physicalX == 500 && m.physicalY == 400);
|
assert(m.physicalX == 500 && m.physicalY == 400);
|
||||||
r = motion(&m, 503, 404, true);
|
r = motion(&m, 503, 404, true);
|
||||||
assert(r.post && r.deltaX == 3 && r.deltaY == 4 && r.rewarp);
|
assert(r.post && r.deltaX == 3 && r.deltaY == 4 && r.rewarp);
|
||||||
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1);
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1, 0);
|
||||||
|
|
||||||
/* A real motion queued before the echo still measures against the last physical position. */
|
/* A real motion queued before the echo still measures against the last physical position. */
|
||||||
X11RelativeMouseBegin(&m, 500, 400);
|
X11RelativeMouseBegin(&m, 500, 400);
|
||||||
r = motion(&m, 520, 400, true);
|
r = motion(&m, 520, 400, true);
|
||||||
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 2); /* the next motion was already queued */
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 2, 0); /* the next motion was already queued */
|
||||||
r = motion(&m, 530, 400, true);
|
r = motion(&m, 530, 400, true);
|
||||||
assert(r.post && r.deltaX == 10 && r.deltaY == 0 && r.rewarp);
|
assert(r.post && r.deltaX == 10 && r.deltaY == 0 && r.rewarp);
|
||||||
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1); /* same target: merged, not a second echo */
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1, 0); /* same target: merged, not a second echo */
|
||||||
assert(m.echoCount == 1);
|
assert(m.echoCount == 1);
|
||||||
r = motion(&m, 500, 400, true);
|
r = motion(&m, 500, 400, true);
|
||||||
assert(r.echo && m.echoCount == 0);
|
assert(r.echo && m.echoCount == 0);
|
||||||
@@ -43,8 +46,8 @@ static void xorgHonouredWarps(void) {
|
|||||||
X11RelativeMouseBegin(&m, 500, 400);
|
X11RelativeMouseBegin(&m, 500, 400);
|
||||||
r = motion(&m, 512, 400, true);
|
r = motion(&m, 512, 400, true);
|
||||||
assert(r.deltaX == 12 && r.rewarp);
|
assert(r.deltaX == 12 && r.rewarp);
|
||||||
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1);
|
X11RelativeMouseExpectEcho(&m, r.warpX, r.warpY, 1, 0);
|
||||||
X11RelativeMouseWarp(&m, 524.0, 400.0, 2); /* game: mouseLocation + 2 * delta */
|
X11RelativeMouseWarp(&m, 524.0, 400.0, 2, 0, true); /* game: mouseLocation + 2 * delta */
|
||||||
assert(m.virtualX == 524.0 && m.physicalX == 512);
|
assert(m.virtualX == 524.0 && m.physicalX == 512);
|
||||||
r = motion(&m, 500, 400, true);
|
r = motion(&m, 500, 400, true);
|
||||||
assert(r.echo && m.physicalX == 500);
|
assert(r.echo && m.physicalX == 500);
|
||||||
@@ -52,6 +55,7 @@ static void xorgHonouredWarps(void) {
|
|||||||
assert(r.echo && m.physicalX == 524 && m.echoCount == 0);
|
assert(r.echo && m.physicalX == 524 && m.echoCount == 0);
|
||||||
r = motion(&m, 530, 401, true);
|
r = motion(&m, 530, 401, true);
|
||||||
assert(r.post && r.deltaX == 6 && r.deltaY == 1 && r.rewarp && r.warpX == 524 && r.warpY == 400);
|
assert(r.post && r.deltaX == 6 && r.deltaY == 1 && r.rewarp && r.warpX == 524 && r.warpY == 400);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
puts("PASS: Xorg honoured warps rewarp to the frozen cursor and drop their echoes");
|
puts("PASS: Xorg honoured warps rewarp to the frozen cursor and drop their echoes");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,27 +65,50 @@ static void xwaylandVisibleCursor(void) {
|
|||||||
/* Warps are not honoured: no rewarp, the pointer moves freely, deltas follow it. */
|
/* Warps are not honoured: no rewarp, the pointer moves freely, deltas follow it. */
|
||||||
X11RelativeMouseMotion r = motion(&m, 810, 450, false);
|
X11RelativeMouseMotion r = motion(&m, 810, 450, false);
|
||||||
assert(r.post && r.deltaX == 10 && !r.rewarp);
|
assert(r.post && r.deltaX == 10 && !r.rewarp);
|
||||||
X11RelativeMouseWarp(&m, 820.0, 450.0, 1);
|
X11RelativeMouseWarp(&m, 820.0, 450.0, 1, 0, false);
|
||||||
/* The echo at the target is dropped without moving the physical position... */
|
/* The echo at the target is dropped without moving the physical position... */
|
||||||
r = motion(&m, 820, 450, false);
|
r = motion(&m, 820, 450, false);
|
||||||
assert(r.echo && !r.post && m.physicalX == 810 && m.physicalY == 450);
|
assert(r.echo && !r.post && m.physicalX == 810 && m.physicalY == 450);
|
||||||
/* ...so the next real motion, at the real absolute position, gives the true delta. */
|
/* ...so the next real motion, at the real absolute position, gives the true delta. */
|
||||||
r = motion(&m, 812, 453, false);
|
r = motion(&m, 812, 453, false);
|
||||||
assert(r.post && r.deltaX == 2 && r.deltaY == 3 && !r.rewarp);
|
assert(r.post && r.deltaX == 2 && r.deltaY == 3 && !r.rewarp);
|
||||||
assert(m.virtualX == 820.0 && m.virtualY == 450.0);
|
assert(m.virtualX == 812.0 && m.virtualY == 453.0);
|
||||||
/* An expected echo that never arrives expires after the queued events and cannot eat a later motion. */
|
/* An expected echo that never arrives expires after the queued events and cannot eat a later motion. */
|
||||||
X11RelativeMouseWarp(&m, 700.0, 400.0, 1);
|
X11RelativeMouseWarp(&m, 700.0, 400.0, 1, 0, false);
|
||||||
X11RelativeMouseTick(&m); /* some other X event was the one queued */
|
X11RelativeMouseTick(&m); /* some other X event was the one queued */
|
||||||
assert(m.echoCount == 0);
|
assert(m.echoCount == 0);
|
||||||
r = motion(&m, 700, 400, false);
|
r = motion(&m, 700, 400, false);
|
||||||
assert(r.post && !r.echo && r.deltaX == -112 && r.deltaY == -53);
|
assert(r.post && !r.echo && r.deltaX == -112 && r.deltaY == -53);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
puts("PASS: Xwayland visible cursor keeps the physical position across unhonoured warp echoes");
|
puts("PASS: Xwayland visible cursor keeps the physical position across unhonoured warp echoes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void leagueVisibleCursorWarps(void) {
|
||||||
|
X11RelativeMouse m = {0};
|
||||||
|
X11RelativeMouseBegin(&m, 2888, 30);
|
||||||
|
int x = 2888, y = 30;
|
||||||
|
for (int step = 0; step < 40; ++step) {
|
||||||
|
x -= 20;
|
||||||
|
y += 16;
|
||||||
|
X11RelativeMouseMotion r = motion(&m, x, y, false);
|
||||||
|
assert(r.post && r.deltaX == -20 && r.deltaY == 16 && !r.rewarp);
|
||||||
|
double gameX = m.virtualX + r.deltaX / 2, gameY = m.virtualY + r.deltaY / 2;
|
||||||
|
X11RelativeMouseWarp(&m, gameX, gameY, 1, 0, false);
|
||||||
|
r = motion(&m, (int) gameX, (int) gameY, false); /* the unhonoured warp's echo */
|
||||||
|
assert(r.echo && !r.post);
|
||||||
|
assert(m.virtualX == x && m.virtualY == y);
|
||||||
|
}
|
||||||
|
/* The pointer stops at the right edge of a 3440-pixel screen; the cursor is there too. */
|
||||||
|
X11RelativeMouseMotion r = motion(&m, 3439, 700, false);
|
||||||
|
assert(r.post && m.virtualX == 3439 && m.virtualY == 700);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
|
puts("PASS: League's visible-cursor warps leave the virtual cursor on the pointer, up to the screen edge");
|
||||||
|
}
|
||||||
|
|
||||||
static void gameWarpUpdatesVirtual(void) {
|
static void gameWarpUpdatesVirtual(void) {
|
||||||
X11RelativeMouse m = {0};
|
X11RelativeMouse m = {0};
|
||||||
X11RelativeMouseBegin(&m, 100, 100);
|
X11RelativeMouseBegin(&m, 100, 100);
|
||||||
X11RelativeMouseWarp(&m, 150.75, 120.5, 0); /* no event: the pointer was already there */
|
X11RelativeMouseWarp(&m, 150.75, 120.5, 0, 0, true); /* no event: the pointer was already there */
|
||||||
assert(m.virtualX == 150.75 && m.virtualY == 120.5 && m.echoCount == 0);
|
assert(m.virtualX == 150.75 && m.virtualY == 120.5 && m.echoCount == 0);
|
||||||
assert(m.physicalX == 100 && m.physicalY == 100);
|
assert(m.physicalX == 100 && m.physicalY == 100);
|
||||||
/* mouseLocation reports the virtual cursor, Cocoa bottom-up global, for a 1080-pixel screen. */
|
/* mouseLocation reports the virtual cursor, Cocoa bottom-up global, for a 1080-pixel screen. */
|
||||||
@@ -89,6 +116,7 @@ static void gameWarpUpdatesVirtual(void) {
|
|||||||
assert(cocoaX == 150.75 && cocoaY == 959.5);
|
assert(cocoaX == 150.75 && cocoaY == 959.5);
|
||||||
X11RelativeMouseMotion r = motion(&m, 101, 100, true);
|
X11RelativeMouseMotion r = motion(&m, 101, 100, true);
|
||||||
assert(r.post && r.deltaX == 1 && r.rewarp && r.warpX == 150 && r.warpY == 120);
|
assert(r.post && r.deltaX == 1 && r.rewarp && r.warpX == 150 && r.warpY == 120);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
puts("PASS: application warps while relative move the virtual cursor that mouseLocation returns");
|
puts("PASS: application warps while relative move the virtual cursor that mouseLocation returns");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,39 +129,110 @@ static void deltaSign(void) {
|
|||||||
/* Absolute motion in Cocoa window coordinates (bottom-up): moving down lowers y. */
|
/* Absolute motion in Cocoa window coordinates (bottom-up): moving down lowers y. */
|
||||||
X11AbsoluteMouseDelta(10, 300, 14, 290, &dx, &dy);
|
X11AbsoluteMouseDelta(10, 300, 14, 290, &dx, &dy);
|
||||||
assert(dx == 4 && dy == 10);
|
assert(dx == 4 && dy == 10);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
puts("PASS: deltaY is positive downwards for relative and absolute motion");
|
puts("PASS: deltaY is positive downwards for relative and absolute motion");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ungrabRestores(void) {
|
static void ungrabRestores(void) {
|
||||||
X11RelativeMouse m = {0};
|
X11RelativeMouse m = {0};
|
||||||
X11RelativeMouseBegin(&m, 10, 10);
|
X11RelativeMouseBegin(&m, 10, 10);
|
||||||
X11RelativeMouseWarp(&m, 40, 40, 3);
|
X11RelativeMouseWarp(&m, 40, 40, 3, 0, true);
|
||||||
X11RelativeMouseEnd(&m);
|
X11RelativeMouseEnd(&m);
|
||||||
assert(!m.active && m.echoCount == 0);
|
assert(!m.active && m.echoCount == 0);
|
||||||
X11RelativeMouseBegin(&m, 60, 70);
|
X11RelativeMouseBegin(&m, 60, 70);
|
||||||
assert(m.active && m.virtualX == 60 && m.virtualY == 70 && m.physicalX == 60 && m.physicalY == 70);
|
assert(m.active && m.virtualX == 60 && m.virtualY == 70 && m.physicalX == 60 && m.physicalY == 70);
|
||||||
X11RelativeMouseMotion r = motion(&m, 40, 40, true);
|
X11RelativeMouseMotion r = motion(&m, 40, 40, true);
|
||||||
assert(r.post && !r.echo && r.deltaX == -20 && r.deltaY == -30);
|
assert(r.post && !r.echo && r.deltaX == -20 && r.deltaY == -30);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
puts("PASS: ungrab clears expected echoes and a new grab starts from the current pointer");
|
puts("PASS: ungrab clears expected echoes and a new grab starts from the current pointer");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void echoQueueBound(void) {
|
static void echoQueueGrowth(void) {
|
||||||
X11RelativeMouse m = {0};
|
X11RelativeMouse m = {0};
|
||||||
X11RelativeMouseBegin(&m, 0, 0);
|
X11RelativeMouseBegin(&m, 0, 0);
|
||||||
for (int i = 1; i <= X11_RELATIVE_MOUSE_MAX_ECHOES + 3; ++i)
|
for (int i = 1; i <= X11_RELATIVE_MOUSE_INITIAL_ECHOES + 3; ++i)
|
||||||
X11RelativeMouseWarp(&m, i, 0, 100);
|
X11RelativeMouseWarp(&m, i, 0, 100, 0, true);
|
||||||
assert(m.echoCount == X11_RELATIVE_MOUSE_MAX_ECHOES && m.echoes[0].x == 4);
|
assert(m.echoCount == X11_RELATIVE_MOUSE_INITIAL_ECHOES + 3 && m.echoes[0].x == 1);
|
||||||
X11RelativeMouseMotion r = motion(&m, 6, 0, true);
|
X11RelativeMouseMotion r = motion(&m, 6, 0, true);
|
||||||
assert(r.echo && m.echoCount == X11_RELATIVE_MOUSE_MAX_ECHOES - 3 && m.echoes[0].x == 7);
|
assert(r.echo && m.echoCount == X11_RELATIVE_MOUSE_INITIAL_ECHOES - 3 && m.echoes[0].x == 7);
|
||||||
puts("PASS: expected echoes are bounded and consumed in order");
|
X11RelativeMouseEnd(&m);
|
||||||
|
puts("PASS: echo backlog grows without discarding warps and is consumed in order");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void serialsSeparateMotionFromEchoes(void) {
|
||||||
|
X11RelativeMouse m = {0};
|
||||||
|
X11RelativeMouseBegin(&m, 500, 400);
|
||||||
|
m.firstSerial = 100;
|
||||||
|
/* Events queued before the grab snapshot must not turn into a jump from that snapshot. */
|
||||||
|
X11RelativeMouseMotion r = X11RelativeMouseHandleMotion(&m, 0, 0, true, 99);
|
||||||
|
assert(!r.post && !r.rewarp && m.physicalX == 500 && m.physicalY == 400);
|
||||||
|
X11RelativeMouseExpectEcho(&m, 500, 400, 4, 110);
|
||||||
|
/* Real pre-warp motion happens to cross the anchor. Coordinates alone would discard it. */
|
||||||
|
m.physicalX = 510;
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 500, 400, true, 105);
|
||||||
|
assert(r.post && !r.echo && r.deltaX == -10 && m.echoCount == 1);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 500, 400, true, 110);
|
||||||
|
assert(r.echo && !r.post && m.echoCount == 0);
|
||||||
|
/* A later real event at the same target must not be swallowed either. */
|
||||||
|
X11RelativeMouseExpectEcho(&m, 520, 400, 2, 120);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 520, 400, true, 121);
|
||||||
|
assert(r.post && !r.echo && r.deltaX == 20);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
|
puts("PASS: grab snapshot and warp request serials distinguish queued motion from echoes");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queuedMotionDoesNotMultiplyWarps(void) {
|
||||||
|
X11RelativeMouse m = {0};
|
||||||
|
X11RelativeMouseBegin(&m, 500, 400);
|
||||||
|
X11RelativeMouseMotion r = X11RelativeMouseHandleMotion(&m, 510, 400, true, 100);
|
||||||
|
assert(r.post && r.deltaX == 10 && r.rewarp);
|
||||||
|
X11RelativeMouseExpectEcho(&m, 500, 400, 4, 110);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 520, 400, true, 101);
|
||||||
|
assert(r.post && r.deltaX == 10 && !r.rewarp);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 530, 400, true, 102);
|
||||||
|
assert(r.post && r.deltaX == 10 && !r.rewarp && m.echoCount == 1);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 500, 400, true, 110);
|
||||||
|
assert(r.echo && !r.post);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 502, 401, true, 111);
|
||||||
|
assert(r.post && r.deltaX == 2 && r.deltaY == 1 && r.rewarp);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
|
puts("PASS: queued physical motion uses one pending rewarp without losing deltas");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void confinedWarpAndApplicationReanchor(void) {
|
||||||
|
X11RelativeMouse m = {0};
|
||||||
|
X11RelativeMouseBegin(&m, 2500, 400);
|
||||||
|
/* A second monitor at +1920, with a 24-pixel panel above the game. */
|
||||||
|
m.bounded = true; m.minX = 1920; m.minY = 24; m.maxX = 3839; m.maxY = 1079;
|
||||||
|
double x = 3900, y = -100;
|
||||||
|
X11RelativeMouseClamp(&m, &x, &y);
|
||||||
|
assert(x == 3839 && y == 24);
|
||||||
|
X11RelativeMouseWarp(&m, x, y, 3, 210, true);
|
||||||
|
/* Another application warp must preserve both outstanding echoes, even at the same position. */
|
||||||
|
X11RelativeMouseWarp(&m, x, y, 4, 220, true);
|
||||||
|
assert(m.echoCount == 2);
|
||||||
|
X11RelativeMouseMotion r = X11RelativeMouseHandleMotion(&m, 3839, 24, true, 210);
|
||||||
|
assert(r.echo && !r.post && m.echoCount == 1);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 3839, 24, true, 220);
|
||||||
|
assert(r.echo && !r.post && m.echoCount == 0);
|
||||||
|
r = X11RelativeMouseHandleMotion(&m, 3830, 30, true, 221);
|
||||||
|
assert(r.post && r.deltaX == -9 && r.deltaY == 6);
|
||||||
|
assert(r.rewarp && r.warpX == 3839 && r.warpY == 24);
|
||||||
|
assert(m.virtualX == 3839 && m.virtualY == 24);
|
||||||
|
X11RelativeMouseEnd(&m);
|
||||||
|
puts("PASS: confined application warps share one position with events and rewarping on a secondary monitor");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
serialsSeparateMotionFromEchoes();
|
||||||
|
queuedMotionDoesNotMultiplyWarps();
|
||||||
|
confinedWarpAndApplicationReanchor();
|
||||||
xorgHonouredWarps();
|
xorgHonouredWarps();
|
||||||
xwaylandVisibleCursor();
|
xwaylandVisibleCursor();
|
||||||
|
leagueVisibleCursorWarps();
|
||||||
gameWarpUpdatesVirtual();
|
gameWarpUpdatesVirtual();
|
||||||
deltaSign();
|
deltaSign();
|
||||||
ungrabRestores();
|
ungrabRestores();
|
||||||
echoQueueBound();
|
echoQueueGrowth();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Passively record Riot/League X11 window lifetimes, geometry and child stacking (requires python-xlib)."""
|
||||||
|
import argparse
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import select
|
||||||
|
import time
|
||||||
|
from Xlib import X, display, error
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('--output', type=Path, required=True)
|
||||||
|
parser.add_argument('--duration', type=float, default=3600)
|
||||||
|
args = parser.parse_args()
|
||||||
|
connection = display.Display()
|
||||||
|
root = connection.screen().root
|
||||||
|
watched, known = set(), {}
|
||||||
|
connection.set_error_handler(lambda exc, request: None) # Windows may disappear between queries.
|
||||||
|
atoms = {name: connection.intern_atom(name) for name in (
|
||||||
|
'_NET_WM_PID', '_NET_WM_NAME', '_NET_WM_STATE', '_NET_WM_WINDOW_TYPE')}
|
||||||
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with args.output.open('a', buffering=1) as output:
|
||||||
|
def emit(record):
|
||||||
|
output.write(json.dumps({'time': datetime.now().astimezone().isoformat(), **record}) + '\n')
|
||||||
|
def property_value(window, name):
|
||||||
|
value = window.get_full_property(atoms[name], X.AnyPropertyType)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if name == '_NET_WM_NAME':
|
||||||
|
return bytes(value.value).decode(errors='replace')
|
||||||
|
return list(map(int, value.value))
|
||||||
|
def scan(window, event, inherited=False, depth=0):
|
||||||
|
if depth > 64:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
if window.id not in watched:
|
||||||
|
window.change_attributes(event_mask=X.SubstructureNotifyMask | X.StructureNotifyMask | X.PropertyChangeMask)
|
||||||
|
watched.add(window.id)
|
||||||
|
tree = window.query_tree()
|
||||||
|
cls = window.get_wm_class()
|
||||||
|
relevant = inherited or any(word in str(cls).lower() for word in ('riot', 'league'))
|
||||||
|
if relevant:
|
||||||
|
attrs, geom = window.get_attributes(), window.get_geometry()
|
||||||
|
point = root.translate_coords(window, 0, 0)
|
||||||
|
state = {
|
||||||
|
'window': hex(window.id), 'parent': hex(tree.parent.id),
|
||||||
|
'class': cls, 'title': property_value(window, '_NET_WM_NAME') or window.get_wm_name(),
|
||||||
|
'pid': property_value(window, '_NET_WM_PID'),
|
||||||
|
'size': [geom.width, geom.height], 'root_position': [point.x, point.y],
|
||||||
|
'mapped': attrs.map_state, 'override_redirect': bool(attrs.override_redirect),
|
||||||
|
'state': [connection.get_atom_name(n) for n in property_value(window, '_NET_WM_STATE') or []],
|
||||||
|
'type': [connection.get_atom_name(n) for n in property_value(window, '_NET_WM_WINDOW_TYPE') or []],
|
||||||
|
'children_bottom_to_top': [hex(child.id) for child in tree.children],
|
||||||
|
}
|
||||||
|
if known.get(window.id) != state or event in ('MapNotify', 'UnmapNotify'):
|
||||||
|
emit({'event': event, **state})
|
||||||
|
known[window.id] = state
|
||||||
|
for child in tree.children:
|
||||||
|
scan(child, event, relevant, depth + 1)
|
||||||
|
except (error.XError, OSError, AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
emit({'event': 'trace-start', 'display': connection.get_display_name()})
|
||||||
|
scan(root, 'initial')
|
||||||
|
connection.flush()
|
||||||
|
deadline = time.monotonic() + args.duration
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if not connection.pending_events():
|
||||||
|
select.select([connection.fileno()], [], [], min(1, max(0, deadline - time.monotonic())))
|
||||||
|
while connection.pending_events():
|
||||||
|
event = connection.next_event()
|
||||||
|
window = getattr(event, 'window', None)
|
||||||
|
if window is None:
|
||||||
|
continue
|
||||||
|
if event.type == X.DestroyNotify:
|
||||||
|
if window.id in known:
|
||||||
|
emit({'event': 'DestroyNotify', **known.pop(window.id)})
|
||||||
|
watched.discard(window.id)
|
||||||
|
else:
|
||||||
|
# Recover the owning top-level so new descendants inherit the Riot/League filter.
|
||||||
|
ancestor = window
|
||||||
|
try:
|
||||||
|
for _ in range(64):
|
||||||
|
parent = ancestor.query_tree().parent
|
||||||
|
if not hasattr(parent, 'id') or parent.id == root.id or parent.id == 0:
|
||||||
|
break
|
||||||
|
ancestor = parent
|
||||||
|
scan(ancestor, type(event).__name__)
|
||||||
|
except error.XError:
|
||||||
|
pass
|
||||||
|
connection.flush()
|
||||||
|
emit({'event': 'trace-end'})
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user