Riot's splash screens draw on a black rectangle: LeagueClient's is a
borderless NSWindow with setOpaque:NO and a pattern-image background, the
Riot Client's an Electron BrowserWindow with transparent: true. X11Window
created every window with a 24-bit visual and -setOpaque: only stored the
flag, so there was no alpha channel.
A non-opaque window whose background can show through (alpha below 1, or a
pattern image) now gets the 32-bit ARGB visual. League's game window is
non-opaque on an opaque background and stays on the opaque visual, which
X11IsLeagueGameWindow also enforces. The X window exists before apps set
opacity and background, so the choice is checked again when the first
subwindow is created (layer subwindows inherit the visual) and before the
first map, and the window recreated while unmapped if it changed. Its
background is transparent instead of None, so no black frame shows before
the first draw. Its surface comes from an alpha EGL config for that visual
(CGLGetWindowWithAlpha) and its compositing context from the surface's
config (CGLCreateContextForWindow): NVIDIA refuses the default config on a
32-bit window, and no driver makes a context from it current on the alpha
surface. Without an alpha config the window falls back to the opaque
visual.
A translucent window above the normal level (Electron's alwaysOnTop
splash) is typed _NET_WM_WINDOW_TYPE_SPLASH with _NET_WM_STATE_ABOVE before
it is mapped: window managers float it and draw no frame around it.
Electron's level never arrived, though: CGWindowLevelForKey returned the
normal level for every key, and the SDK turns NSFloatingWindowLevel into a
call to it. It now returns the levels macOS does; Cocotron only stores
levels, so nothing else changes.
The drag loop runs run loop sources and main queue blocks between moves,
and Chromium can tear down a web view or close a window there. Hold both
until the drag ends, after -draggedImage:endedAt:operation:.
With DARLING_METAL_MAC_GPU_NAME=1, MTLDevice.name is the GPU the
MacBookPro16,1 that IOKit reports shipped with, from the host's vendor:
"AMD Radeon Pro 5500M" (vendor 0x1002) or "Intel UHD Graphics 630"
(0x8086). NVIDIA keeps its own name so the game never takes another
vendor's driver paths. recommendedMaxWorkingSetSize and every other value
stay the host's, which was the risky part of #61's identity.
The Riot launcher turns it on by default (0 or empty turns it off); other
Darling apps keep the host name. DARLING_METAL_GPU_NAME still wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From #61, without the default "AMD Radeon Pro 5500M" / 4 GB identity: the
host GPU's name and Vulkan working set stay the default for every app, and
the existing DARLING_METAL_GPU_NAME and DARLING_METAL_VRAM_MB overrides
remain the opt-in. Reporting 4 GB on a 2 GB card invites allocation
failures, and under-reports larger cards.
Kept from #61:
- registryID returns MTLDeviceInfo::kDefaultRegistryID instead of 0; a
real Mac never reports 0 and apps key devices by it.
- Vertex buffer layout and attribute arrays release the old item after
retiring it; the retire step read the item after the release could free it.
- newDepthStencilState, newSamplerState and the device name return nil (or
the fallback name) when Indium throws, instead of unwinding through
Objective-C.
- MTLDepthStencilGetIndium is declared only with DARLING_METAL_ENABLED.
- The replaceRegion trace probes at most `width` bytes, which stays inside
the first row for every pixel format.
metal.patch is regenerated in its existing file order, so the diff is
these changes only. tests/test_metal_device_info.py compiles the host
check against MTLDeviceInfo.h as shipped in the patch and runs in CI.
Split from #61.
Co-authored-by: DevWebeloper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The X11 backend has no NSDraggingManager, so -[NSWindow dragImage:...]
returned at once without calling -draggedImage:endedAt:operation:.
Chromium waits for that callback before the page takes mouse input again,
so any HTML5 drag in a CEF/Electron window (the League client's emote
wheel) left the page ignoring the mouse.
NSWindow now runs the drag itself between the app's own windows, with the
drag image under the pointer, and always ends it with the callback.
X11Pasteboard sends the optional -pasteboardChangedOwner: only to owners
that implement it (Chromium's drag source doesn't; the second drag
crashed). NSView's default -prepareForDragOperation: returns YES, as
AppKit does for destinations that don't implement it.
With __GL_THREADED_OPTIMIZATIONS=1, NVIDIA 580 (the last branch for Pascal
GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY on
every thread that has a context current. A native EGL program reproduces it
outside Darling. In the Riot client the main thread loses EGL after its first
window frame, so CGLCreateContext fails with error 10004 and the game window
stays black. Default the launcher to 0; setting the variable restores 1.
CGLDestroyWindow also returns early for the NULL window CGLGetWindow gives
when eglCreateWindowSurface fails, instead of passing EGL_NO_SURFACE on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the merged per-device completion budget, bounded parsing, idle blocking and timeout accounting. Scope the registration/blocking handshake to each device, restrict live batch windows to the timer resolution, and test registration interleavings, spin-disabled polling, shutdown, opt-out and independent devices with actual patched methods.
Co-authored-by: SypherrDev <[email protected]>
Ignore both invalidation methods on the shared session; deliver task completion through the owning session delegate queue. Extend the standalone probe to exercise singleton reuse and a suspended custom queue. Require stable old-client process identities and complete window data before optional recovery, and continue launch after shutdown failures.
Rebuild the remaining NVIDIA changes on current main, preserving the merged visual, draw-buffer, descriptor ownership and mixed Metal-tree fixes. Scope native-thread forwarding to resize notifications and make completion polling per device, bounded and part of the caller timeout budget.
Co-authored-by: Pirrandi <[email protected]>
The stub returned nil and left the language output untouched. Chromium
pre-fills that output with a sentinel and reads it whenever it is
non-NULL, so the League client UI read garbage. Return CTFontCopyName
and always write NULL to the language output. The CoreText font-list
check now covers both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ubuntu 24.04 ships Linux 6.8 headers, which predate the PROCMAP_QUERY
ioctl (6.11), so darlingserver failed to build. The ioctl ABI is fixed,
so define it locally; on an older running kernel the ioctl fails and
the /proc/<pid>/maps text parser is used as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-[MTLDevice name] returned "Darling Indium Device" for every GPU. It now
returns the selected Vulkan device's name in macOS Metal style: the
trailing driver note in brackets and the (R)/(TM) marks are removed, and
NVIDIA names are unchanged. The name is never replaced with another
vendor's. recommendedMaxWorkingSetSize is the device-local heap size on
discrete GPUs and all heaps on integrated/CPU devices, with the old 1 GiB
as fallback. Both are computed once per device.
hasUnifiedMemory stays YES by default: synchronizeResource:/
synchronizeTexture: are missing and Private buffers created with data
abort in Indium, so reporting NO on discrete GPUs would send apps down
unimplemented paths. DARLING_METAL_HOST_UNIFIED_MEMORY=1 opts in to the
GPU-type answer. DARLING_METAL_GPU_NAME and DARLING_METAL_VRAM_MB
override the name and working set.
The cleaning logic lives in private-include/Metal/MTLDeviceInfo.h and is
covered by the host check tools/test-metal-device-info.cpp. Metal now
links indium_private for the Vulkan properties, and the runtime build
script adds Indium's private headers before compiling MTLDevice.mm.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every install reported the same IOPlatformUUID and an Apple-format serial
(C02ZL0GVMD6N) that may belong to a real MacBook Pro. Derive both from a
SHA-256 of the host machine-id and a fixed app string: stable per host,
distinct between hosts, and the raw machine-id is not exposed. The serial
is DARLING-<8 hex>, a form no Mac uses. iokitd is built without ARC, so
the derived strings are owned for the daemon's lifetime. Fix the header
comment that said the values came from DMI.
Refs #24
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The unimplemented method returned whatever was left in a register.
Chromium forwards the style to its renderer, which rejected the
out-of-range value (VALIDATION_ERROR_DESERIALIZATION_FAILED) and left
the Riot Client window black. Report NSScrollerStyleLegacy, as macOS
does without a trackpad.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Riot Client (Electron) aborted with unrecognized selectors in
Cocotron's AppKit:
- -[NSOpenPanel setShowsTagField:]: Electron sets it on every file
dialog. Add the missing NSSavePanel setters it uses (showsTagField,
nameFieldLabel, extensionHidden, canSelectHiddenExtension) as no-ops,
and implement beginWithCompletionHandler: and
beginSheetModalForWindow:completionHandler: by running the panel
modally and calling the handler (the old stub never called it).
- -[MacWorkspace openURLs:withAppBundleIdentifier:options:
additionalEventParamDescriptor:launchIdentifiers:]: used by
shell.openExternal for the third-party sign-in buttons. Open each URL
through openURL: (LaunchServices).
Fixes#28
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On NVIDIA the game aborts within minutes: a thread's darlingserver RPC
socket is closed behind Darling's back, and its next RPC fails with
EBADF (or ENOTSOCK once the number is reused), then __simple_abort in
mach_msg_overwrite_trap_impl (#23).
__mldr_create_rpc_socket moved each new socket onto its slot with
dup2(), which silently closes whatever already holds that number. The
slots count down from the soft RLIMIT_NOFILE read at the process's first
socket. The game starts with a soft limit of 1024 (systemd's default,
inherited from the terminal) and then holds ~5500 fds, one per NVIDIA
driver allocation, so the kernel hands numbers in the slot range
(900-1023) to the driver whenever a slot is free, for instance after a
thread exits. The next new thread's dup2() replaced the driver's fd with
its socket; when the driver later closed "its" fd, it closed the socket.
Mesa never comes near 1000 fds, which is why AMD works.
Use fcntl(F_DUPFD, slot) instead: it never replaces a descriptor. If the
slot is taken, give it back and keep the socket at the lowest free
number above it, which is above all of the bitmap's slots and owned by
nobody else; socket_bitmap_put ignores sockets above the slots. The
process lifetime pipe helper gets the same treatment.
This is the remaining case of the 2026-10-10 LeagueClient crash: that
fix stopped two Darling threads from sharing a slot, this one stops
Darling from taking a slot that belongs to someone else.
Second half of the NVIDIA black screen (#3). With the visual fix the
compositor gets a window surface and drawables recycle, but the window
stays black: after CARenderer draws, the back buffer reads back black and
even a glClear to blue changes nothing. GL_DRAW_BUFFER and GL_READ_BUFFER
are GL_NONE.
CALayerContext -setSubwindow: creates the context and then notifies the
layer tree; CAMetalLayer -_setContext: makes the context current (to
create its textures) before any window surface is attached. Per
EGL_KHR_surfaceless_context, a context first made current without a
default framebuffer starts with DRAW_BUFFER = NONE, and attaching a
window later doesn't change it. NVIDIA follows that; Mesa resets it on
bind, which is why AMD works.
After CGLContextMakeCurrentAndAttachToWindow in -renderLayer:, set the
draw and read buffers to GL_BACK.
On NVIDIA (RTX 2070 SUPER, driver 615.78, Xwayland) the game showed a
black screen and logged "Metal Device timed out giving us a Drawable to
present" every second (#3).
X11Window picked its visual with glXChooseVisual, which on NVIDIA returns
a GLX-only visual (0x304 here). NVIDIA's EGL configs only map to the
default visual (0x69) and the 32-bit one (0x99), so CGLGetWindow's
eglCreateWindowSurface failed with EGL_BAD_CONFIG for the window and its
subwindows, for every one of the 40 window configs. CGLGetWindow returns
NULL without logging, CALayerContext keeps _cglWindow == NULL, -render
returns before prepareMetalLayers, and CAMetalLayer never gets its three
drawables back. Mesa accepts the GLX visual, which is why AMD works.
Use the default visual when it is TrueColor with depth >= 24, falling
back to glXChooseVisual otherwise. This also fixes every other Darling
window on NVIDIA: plain AppKit windows (title bars included),
layer-backed views and NSOpenGLView were all black without it.
League enters relative mouse mode on GNOME (Xwayland) and inside gamescope, but
keeps a visible cursor and warps it every frame at its own mouse speed. Xwayland
cannot move a visible pointer, so those warps never took effect: the virtual
cursor, where clicks land, drifted hundreds of pixels from the cursor on screen
and never reached the screen edges, which also stopped edge panning.
Where warps are not honoured, the virtual cursor now follows the physical
pointer and the game's warps leave it in place. With a hidden cursor, or on a
server that honours warps, nothing changes. The host model test gains a case
replaying the drift from an in-game trace.
Tested manually in the practice tool under gamescope 3.16.31 on GNOME 51 with
AMD RADV: clicks land on the cursor and edge panning works.
Co-Authored-By: Claude Opus 5.5 <[email protected]>