Files
darling-lol/patches/darling.patch
T
lucannez64 5609d96f16 Fix Darling runtime performance and new-PC compatibility
Preserve floating-point arguments in GL resolvers, retain presentation across differently sized windows, export AppKit click and URL fixes, and guard nullable LaunchServices errors. Include compiler/build and installer corrections for the new PC.

Reuse server receive buffers with descriptor-safe exception cleanup and avoid disabled debug-string allocation. Translate Darwin free/reuse memory hints, add atomic server/library installers with separate user-owned memory backups, and preserve active processes until a manual namespace restart.

Remove automatic inspectors, client helpers and forced unlimited UI frames from normal launches. Retain historical hooks with offline mocks and document their exclusion, unresolved installer handoff, activation requirements and unmeasured FPS/RAM benefits.

Validation: all 29 pinned patches apply; all 461 exported source files match; host receive and memory checks, launcher and JavaScript mocks, syntax and documentation-link checks pass. No live client/game was controlled or restarted.
2026-10-13 15:09:23 +02:00

8085 lines
319 KiB
Diff

diff --git a/src/CoreAudio/AudioToolbox/AUComponent.cpp b/src/CoreAudio/AudioToolbox/AUComponent.cpp
index d48eec1ef..c86959d55 100644
--- a/src/CoreAudio/AudioToolbox/AUComponent.cpp
+++ b/src/CoreAudio/AudioToolbox/AUComponent.cpp
@@ -22,13 +22,16 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <AudioToolbox/AudioOutputUnit.h>
#include <CarbonCore/MacErrors.h>
#include "ComponentDispatch.h"
+#include "../AudioTrace.h"
const CFStringRef kAudioComponentRegistrationsChangedNotification = CFSTR("AudioComponentRegistrationsChangedNotification");
const CFStringRef kAudioComponentInstanceInvalidationNotification = CFSTR("AudioComponentInstanceInvalidationNotification");
OSStatus AudioOutputUnitStart(AudioUnit ci)
{
- return dispatchCall(ci, kAudioOutputUnitStartSelect);
+ OSStatus status = dispatchCall(ci, kAudioOutputUnitStartSelect);
+ rclolAudioTrace("UNIT_START unit=%p status=%d\n", (void*) ci, (int) status);
+ return status;
}
OSStatus AudioOutputUnitStop(AudioUnit ci)
@@ -38,7 +41,9 @@ OSStatus AudioOutputUnitStop(AudioUnit ci)
OSStatus AudioUnitInitialize(AudioUnit inUnit)
{
- return dispatchCall(inUnit, kAudioUnitInitializeSelect);
+ OSStatus status = dispatchCall(inUnit, kAudioUnitInitializeSelect);
+ rclolAudioTrace("UNIT_INITIALIZE unit=%p status=%d\n", (void*) inUnit, (int) status);
+ return status;
}
OSStatus AudioUnitUninitialize(AudioUnit inUnit)
@@ -54,7 +59,9 @@ AudioUnitGetPropertyInfo(AudioUnit inUnit,
UInt32 * _Nullable outDataSize,
Boolean * _Nullable outWritable)
{
- return dispatchCall(inUnit, kAudioUnitGetPropertyInfoSelect, inID, inScope, inElement, outDataSize, outWritable);
+ OSStatus status = dispatchCall(inUnit, kAudioUnitGetPropertyInfoSelect, inID, inScope, inElement, outDataSize, outWritable);
+ rclolAudioTrace("UNIT_PROPINFO unit=%p prop=%u scope=%u elem=%u status=%d\n", (void*) inUnit, (unsigned) inID, (unsigned) inScope, (unsigned) inElement, (int) status);
+ return status;
}
OSStatus
@@ -65,7 +72,16 @@ AudioUnitGetProperty(AudioUnit inUnit,
void * outData,
UInt32 * ioDataSize)
{
- return dispatchCall(inUnit, kAudioUnitGetPropertySelect, inID, inScope, inElement, outData, ioDataSize);
+ OSStatus status = dispatchCall(inUnit, kAudioUnitGetPropertySelect, inID, inScope, inElement, outData, ioDataSize);
+ if (inID == 8 && status == noErr && outData && ioDataSize && *ioDataSize >= sizeof(AudioStreamBasicDescription))
+ {
+ const AudioStreamBasicDescription* d = static_cast<const AudioStreamBasicDescription*>(outData);
+ rclolAudioTrace("UNIT_GOT_FORMAT unit=%p scope=%u elem=%u rate=%.0f id=%08x flags=%08x bpf=%u ch=%u bits=%u\n",
+ (void*) inUnit, (unsigned) inScope, (unsigned) inElement, d->mSampleRate, (unsigned) d->mFormatID, (unsigned) d->mFormatFlags,
+ (unsigned) d->mBytesPerFrame, (unsigned) d->mChannelsPerFrame, (unsigned) d->mBitsPerChannel);
+ }
+ rclolAudioTrace("UNIT_GET unit=%p prop=%u scope=%u elem=%u status=%d\n", (void*) inUnit, (unsigned) inID, (unsigned) inScope, (unsigned) inElement, (int) status);
+ return status;
}
OSStatus
@@ -76,7 +92,16 @@ AudioUnitSetProperty(AudioUnit inUnit,
const void * _Nullable inData,
UInt32 inDataSize)
{
- return dispatchCall(inUnit, kAudioUnitSetPropertySelect, inID, inScope, inElement, inData, inDataSize);
+ if (inID == 8 /* kAudioUnitProperty_StreamFormat */ && inData && inDataSize >= sizeof(AudioStreamBasicDescription))
+ {
+ const AudioStreamBasicDescription* d = static_cast<const AudioStreamBasicDescription*>(inData);
+ rclolAudioTrace("UNIT_SET_FORMAT unit=%p scope=%u elem=%u rate=%.0f id=%08x flags=%08x bpp=%u fpp=%u bpf=%u ch=%u bits=%u\n",
+ (void*) inUnit, (unsigned) inScope, (unsigned) inElement, d->mSampleRate, (unsigned) d->mFormatID, (unsigned) d->mFormatFlags,
+ (unsigned) d->mBytesPerPacket, (unsigned) d->mFramesPerPacket, (unsigned) d->mBytesPerFrame, (unsigned) d->mChannelsPerFrame, (unsigned) d->mBitsPerChannel);
+ }
+ OSStatus status = dispatchCall(inUnit, kAudioUnitSetPropertySelect, inID, inScope, inElement, inData, inDataSize);
+ rclolAudioTrace("UNIT_SET unit=%p prop=%u scope=%u elem=%u size=%u status=%d\n", (void*) inUnit, (unsigned) inID, (unsigned) inScope, (unsigned) inElement, (unsigned) inDataSize, (int) status);
+ return status;
}
OSStatus
@@ -103,7 +128,9 @@ AudioUnitAddRenderNotify(AudioUnit inUnit,
AURenderCallback inProc,
void * _Nullable inProcUserData)
{
- return dispatchCall(inUnit, kAudioUnitAddRenderNotifySelect, inProc, inProcUserData);
+ OSStatus status = dispatchCall(inUnit, kAudioUnitAddRenderNotifySelect, inProc, inProcUserData);
+ rclolAudioTrace("UNIT_ADD_RENDER_NOTIFY unit=%p status=%d\n", (void*) inUnit, (int) status);
+ return status;
}
OSStatus
diff --git a/src/CoreAudio/AudioToolbox/AUGraph.cpp b/src/CoreAudio/AudioToolbox/AUGraph.cpp
index ca20b90df..e90b37163 100644
--- a/src/CoreAudio/AudioToolbox/AUGraph.cpp
+++ b/src/CoreAudio/AudioToolbox/AUGraph.cpp
@@ -1 +1,548 @@
+#include "../AudioTrace.h"
+/*
+This file is part of Darling.
+
+Darling is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Darling is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Darling. If not, see <http://www.gnu.org/licenses/>.
+*/
#include "AUGraph.h"
+#include <AudioToolbox/AudioComponent.h>
+#include <AudioToolbox/AudioOutputUnit.h>
+#include <algorithm>
+#include <mutex>
+#include <vector>
+
+// An AUGraph is a set of nodes (audio units created from component descriptions), connections
+// between them and render callbacks feeding their inputs. Connections and callbacks are recorded
+// while the graph is edited and applied to the audio units when the graph is initialized.
+
+namespace
+{
+struct Node
+{
+ AUNode id;
+ AudioComponentDescription description;
+ AudioUnit unit = nullptr;
+};
+}
+
+struct AUGraphData
+{
+ std::recursive_mutex mutex;
+ std::vector<Node> nodes;
+ std::vector<AUNodeConnection> connections;
+ std::vector<AUNodeRenderCallback> callbacks;
+ AUNode nextNode = 1;
+ bool open = false;
+ bool initialized = false;
+ bool running = false;
+};
+
+namespace
+{
+Node* findNode(AUGraphData* graph, AUNode id)
+{
+ for (Node& node : graph->nodes)
+ {
+ if (node.id == id)
+ return &node;
+ }
+ return nullptr;
+}
+
+bool isOutputNode(const Node& node)
+{
+ return node.description.componentType == kAudioUnitType_Output;
+}
+
+// Undoes whatever initialization has done to the units, keeping the nodes
+void uninitializeUnits(AUGraphData* graph)
+{
+ for (Node& node : graph->nodes)
+ {
+ if (node.unit)
+ AudioUnitUninitialize(node.unit);
+ }
+ graph->initialized = false;
+}
+}
+
+static OSStatus AUGraphOpenImpl(AUGraph inGraph);
+static OSStatus AUGraphConnectNodeInputImpl(AUGraph inGraph, AUNode inSourceNode, UInt32 inSourceOutputNumber, AUNode inDestNode, UInt32 inDestInputNumber);
+static OSStatus AUGraphSetNodeInputCallbackImpl(AUGraph inGraph, AUNode inDestNode, UInt32 inDestInputNumber, const AURenderCallbackStruct* inInputCallback);
+static OSStatus AUGraphInitializeImpl(AUGraph inGraph);
+static OSStatus AUGraphStartImpl(AUGraph inGraph);
+OSStatus NewAUGraph(AUGraph* outGraph)
+{
+ rclolAudioTrace("NEW_GRAPH\n");
+ if (!outGraph)
+ return kAudio_ParamError;
+
+ *outGraph = new AUGraphData;
+ return noErr;
+}
+
+OSStatus DisposeAUGraph(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ AUGraphStop(inGraph);
+ AUGraphUninitialize(inGraph);
+ AUGraphClose(inGraph);
+ delete inGraph;
+ return noErr;
+}
+
+OSStatus AUGraphAddNode(AUGraph inGraph, const AudioComponentDescription* inDescription, AUNode* outNode)
+{
+ if(inDescription) rclolAudioTrace("ADD_NODE type=%08x subtype=%08x\n",inDescription->componentType,inDescription->componentSubType);
+ if (!inGraph || !inDescription || !outNode)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+
+ Node node;
+ node.id = inGraph->nextNode;
+ node.description = *inDescription;
+
+ if (inGraph->open)
+ {
+ // A node added to an open graph gets its unit right away
+ AudioComponent component = AudioComponentFindNext(nullptr, inDescription);
+ OSStatus status = component ? AudioComponentInstanceNew(component, &node.unit)
+ : kAudioUnitErr_InvalidProperty;
+ rclolAudioTrace("GRAPH_NODE_UNIT subtype=%08x unit=%p status=%d\n", (unsigned) node.description.componentSubType, (void*) node.unit, (int) status);
+ if (status != noErr)
+ return status;
+ }
+
+ inGraph->nextNode++;
+ inGraph->nodes.push_back(node);
+ *outNode = node.id;
+ return noErr;
+}
+
+OSStatus AUGraphRemoveNode(AUGraph inGraph, AUNode inNode)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+
+ auto node = std::find_if(inGraph->nodes.begin(), inGraph->nodes.end(), [&](const Node& n) { return n.id == inNode; });
+ if (node == inGraph->nodes.end())
+ return kAudioUnitErr_InvalidProperty;
+
+ if (node->unit)
+ {
+ AudioUnitUninitialize(node->unit);
+ AudioComponentInstanceDispose(node->unit);
+ }
+ inGraph->nodes.erase(node);
+
+ inGraph->connections.erase(std::remove_if(inGraph->connections.begin(), inGraph->connections.end(),
+ [&](const AUNodeConnection& c) { return c.sourceNode == inNode || c.destNode == inNode; }), inGraph->connections.end());
+ inGraph->callbacks.erase(std::remove_if(inGraph->callbacks.begin(), inGraph->callbacks.end(),
+ [&](const AUNodeRenderCallback& c) { return c.destNode == inNode; }), inGraph->callbacks.end());
+ return noErr;
+}
+
+OSStatus AUGraphGetNodeCount(AUGraph inGraph, UInt32* outNumberOfNodes)
+{
+ if (!inGraph || !outNumberOfNodes)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ *outNumberOfNodes = (UInt32) inGraph->nodes.size();
+ return noErr;
+}
+
+OSStatus AUGraphGetIndNode(AUGraph inGraph, UInt32 inIndex, AUNode* outNode)
+{
+ if (!inGraph || !outNode)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (inIndex >= inGraph->nodes.size())
+ return kAudio_ParamError;
+
+ *outNode = inGraph->nodes[inIndex].id;
+ return noErr;
+}
+
+OSStatus AUGraphNodeInfo(AUGraph inGraph, AUNode inNode, AudioComponentDescription* outDescription, AudioUnit* outAudioUnit)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+
+ Node* node = findNode(inGraph, inNode);
+ if (!node)
+ return kAudioUnitErr_InvalidProperty;
+
+ if (outDescription)
+ *outDescription = node->description;
+ if (outAudioUnit)
+ {
+ // The unit only exists while the graph is open
+ if (!node->unit)
+ return kAudioUnitErr_Uninitialized;
+ *outAudioUnit = node->unit;
+ }
+ return noErr;
+}
+
+static OSStatus AUGraphOpenImpl(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (inGraph->open)
+ return noErr;
+
+ for (Node& node : inGraph->nodes)
+ {
+ AudioComponent component = AudioComponentFindNext(nullptr, &node.description);
+ OSStatus status = component ? AudioComponentInstanceNew(component, &node.unit)
+ : kAudioUnitErr_InvalidProperty;
+ rclolAudioTrace("GRAPH_NODE_UNIT subtype=%08x unit=%p status=%d\n", (unsigned) node.description.componentSubType, (void*) node.unit, (int) status);
+ if (status != noErr)
+ {
+ for (Node& created : inGraph->nodes)
+ {
+ if (created.unit)
+ {
+ AudioComponentInstanceDispose(created.unit);
+ created.unit = nullptr;
+ }
+ }
+ return status;
+ }
+ }
+
+ inGraph->open = true;
+ return noErr;
+}
+
+OSStatus AUGraphClose(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (inGraph->running)
+ AUGraphStop(inGraph);
+ if (inGraph->initialized)
+ uninitializeUnits(inGraph);
+
+ for (Node& node : inGraph->nodes)
+ {
+ if (node.unit)
+ {
+ AudioComponentInstanceDispose(node.unit);
+ node.unit = nullptr;
+ }
+ }
+ inGraph->open = false;
+ return noErr;
+}
+
+static OSStatus AUGraphConnectNodeInputImpl(AUGraph inGraph, AUNode inSourceNode, UInt32 inSourceOutputNumber, AUNode inDestNode, UInt32 inDestInputNumber)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (!findNode(inGraph, inSourceNode) || !findNode(inGraph, inDestNode))
+ return kAudioUnitErr_InvalidProperty;
+
+ // An input is fed by one source: a new connection or callback replaces the previous one
+ AUGraphDisconnectNodeInput(inGraph, inDestNode, inDestInputNumber);
+
+ AUNodeConnection connection;
+ connection.sourceNode = inSourceNode;
+ connection.sourceOutputNumber = inSourceOutputNumber;
+ connection.destNode = inDestNode;
+ connection.destInputNumber = inDestInputNumber;
+ inGraph->connections.push_back(connection);
+ return noErr;
+}
+
+OSStatus AUGraphDisconnectNodeInput(AUGraph inGraph, AUNode inDestNode, UInt32 inDestInputNumber)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ inGraph->connections.erase(std::remove_if(inGraph->connections.begin(), inGraph->connections.end(),
+ [&](const AUNodeConnection& c) { return c.destNode == inDestNode && c.destInputNumber == inDestInputNumber; }), inGraph->connections.end());
+ inGraph->callbacks.erase(std::remove_if(inGraph->callbacks.begin(), inGraph->callbacks.end(),
+ [&](const AUNodeRenderCallback& c) { return c.destNode == inDestNode && c.destInputNumber == inDestInputNumber; }), inGraph->callbacks.end());
+ return noErr;
+}
+
+OSStatus AUGraphClearConnections(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ inGraph->connections.clear();
+ inGraph->callbacks.clear();
+ return noErr;
+}
+
+static OSStatus AUGraphSetNodeInputCallbackImpl(AUGraph inGraph, AUNode inDestNode, UInt32 inDestInputNumber, const AURenderCallbackStruct* inInputCallback)
+{
+ if (!inGraph || !inInputCallback)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (!findNode(inGraph, inDestNode))
+ return kAudioUnitErr_InvalidProperty;
+
+ AUGraphDisconnectNodeInput(inGraph, inDestNode, inDestInputNumber);
+
+ AUNodeRenderCallback callback;
+ callback.destNode = inDestNode;
+ callback.destInputNumber = inDestInputNumber;
+ callback.cback = *inInputCallback;
+ inGraph->callbacks.push_back(callback);
+ return noErr;
+}
+
+OSStatus AUGraphGetNumberOfInteractions(AUGraph inGraph, UInt32* outNumInteractions)
+{
+ if (!inGraph || !outNumInteractions)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ *outNumInteractions = (UInt32) (inGraph->connections.size() + inGraph->callbacks.size());
+ return noErr;
+}
+
+OSStatus AUGraphGetInteractionInfo(AUGraph inGraph, UInt32 inInteractionIndex, AUNodeInteraction* outInteraction)
+{
+ if (!inGraph || !outInteraction)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+
+ if (inInteractionIndex < inGraph->connections.size())
+ {
+ outInteraction->nodeInteractionType = 1; // kAUNodeInteraction_Connection
+ outInteraction->nodeInteraction.connection = inGraph->connections[inInteractionIndex];
+ return noErr;
+ }
+
+ inInteractionIndex -= (UInt32) inGraph->connections.size();
+ if (inInteractionIndex < inGraph->callbacks.size())
+ {
+ outInteraction->nodeInteractionType = 2; // kAUNodeInteraction_InputCallback
+ outInteraction->nodeInteraction.inputCallback = inGraph->callbacks[inInteractionIndex];
+ return noErr;
+ }
+ return kAudio_ParamError;
+}
+
+static OSStatus AUGraphInitializeImpl(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (!inGraph->open)
+ return kAudioUnitErr_Uninitialized;
+ if (inGraph->initialized)
+ return noErr;
+
+ for (const AUNodeConnection& connection : inGraph->connections)
+ {
+ Node* source = findNode(inGraph, connection.sourceNode);
+ Node* dest = findNode(inGraph, connection.destNode);
+ if (!source || !dest)
+ return kAudioUnitErr_InvalidProperty;
+
+ AudioUnitConnection unitConnection;
+ unitConnection.sourceAudioUnit = source->unit;
+ unitConnection.sourceOutputNumber = connection.sourceOutputNumber;
+ unitConnection.destInputNumber = connection.destInputNumber;
+
+ OSStatus status = AudioUnitSetProperty(dest->unit, kAudioUnitProperty_MakeConnection, kAudioUnitScope_Input,
+ connection.destInputNumber, &unitConnection, sizeof(unitConnection));
+ if (status != noErr)
+ return status;
+ }
+
+ for (const AUNodeRenderCallback& callback : inGraph->callbacks)
+ {
+ Node* dest = findNode(inGraph, callback.destNode);
+ if (!dest)
+ return kAudioUnitErr_InvalidProperty;
+
+ OSStatus status = AudioUnitSetProperty(dest->unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
+ callback.destInputNumber, &callback.cback, sizeof(callback.cback));
+ if (status != noErr)
+ return status;
+ }
+
+ for (Node& node : inGraph->nodes)
+ {
+ OSStatus status = AudioUnitInitialize(node.unit);
+ if (status != noErr)
+ {
+ uninitializeUnits(inGraph);
+ return status;
+ }
+ }
+
+ inGraph->initialized = true;
+ return noErr;
+}
+
+OSStatus AUGraphUninitialize(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (inGraph->running)
+ AUGraphStop(inGraph);
+ if (inGraph->initialized)
+ uninitializeUnits(inGraph);
+ return noErr;
+}
+
+static OSStatus AUGraphStartImpl(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (!inGraph->initialized)
+ return kAudioUnitErr_Uninitialized;
+ if (inGraph->running)
+ return noErr;
+
+ // Pulling starts at the output units
+ for (Node& node : inGraph->nodes)
+ {
+ if (isOutputNode(node))
+ {
+ OSStatus status = AudioOutputUnitStart(node.unit);
+ if (status != noErr)
+ return status;
+ }
+ }
+
+ inGraph->running = true;
+ return noErr;
+}
+
+OSStatus AUGraphStop(AUGraph inGraph)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ if (!inGraph->running)
+ return noErr;
+
+ for (Node& node : inGraph->nodes)
+ {
+ if (node.unit && isOutputNode(node))
+ AudioOutputUnitStop(node.unit);
+ }
+
+ inGraph->running = false;
+ return noErr;
+}
+
+OSStatus AUGraphIsOpen(AUGraph inGraph, Boolean* outIsOpen)
+{
+ if (!inGraph || !outIsOpen)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ *outIsOpen = inGraph->open;
+ return noErr;
+}
+
+OSStatus AUGraphIsInitialized(AUGraph inGraph, Boolean* outIsInitialized)
+{
+ if (!inGraph || !outIsInitialized)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ *outIsInitialized = inGraph->initialized;
+ return noErr;
+}
+
+OSStatus AUGraphIsRunning(AUGraph inGraph, Boolean* outIsRunning)
+{
+ if (!inGraph || !outIsRunning)
+ return kAudio_ParamError;
+
+ std::lock_guard<std::recursive_mutex> lock(inGraph->mutex);
+ *outIsRunning = inGraph->running;
+ return noErr;
+}
+
+OSStatus AUGraphUpdate(AUGraph inGraph, Boolean* outIsUpdated)
+{
+ if (!inGraph)
+ return kAudio_ParamError;
+
+ // Edits are applied when the graph is initialized, so there is never a pending change to apply
+ if (outIsUpdated)
+ *outIsUpdated = true;
+ return noErr;
+}
+
+OSStatus AUGraphOpen(AUGraph inGraph)
+{
+ OSStatus status = AUGraphOpenImpl(inGraph);
+ rclolAudioTrace("GRAPH_OPEN status=%d\n", (int) status);
+ return status;
+}
+
+OSStatus AUGraphConnectNodeInput(AUGraph inGraph, AUNode inSourceNode, UInt32 inSourceOutputNumber, AUNode inDestNode, UInt32 inDestInputNumber)
+{
+ OSStatus status = AUGraphConnectNodeInputImpl(inGraph, inSourceNode, inSourceOutputNumber, inDestNode, inDestInputNumber);
+ rclolAudioTrace("GRAPH_CONNECTNODEINPUT status=%d\n", (int) status);
+ return status;
+}
+
+OSStatus AUGraphSetNodeInputCallback(AUGraph inGraph, AUNode inDestNode, UInt32 inDestInputNumber, const AURenderCallbackStruct* inInputCallback)
+{
+ OSStatus status = AUGraphSetNodeInputCallbackImpl(inGraph, inDestNode, inDestInputNumber, inInputCallback);
+ rclolAudioTrace("GRAPH_SETNODEINPUTCALLBACK status=%d\n", (int) status);
+ return status;
+}
+
+OSStatus AUGraphInitialize(AUGraph inGraph)
+{
+ OSStatus status = AUGraphInitializeImpl(inGraph);
+ rclolAudioTrace("GRAPH_INITIALIZE status=%d\n", (int) status);
+ return status;
+}
+
+OSStatus AUGraphStart(AUGraph inGraph)
+{
+ OSStatus status = AUGraphStartImpl(inGraph);
+ rclolAudioTrace("GRAPH_START status=%d\n", (int) status);
+ return status;
+}
diff --git a/src/CoreAudio/AudioToolbox/AudioComponentManager.mm b/src/CoreAudio/AudioToolbox/AudioComponentManager.mm
index 5fa33817d..4c1f25a33 100644
--- a/src/CoreAudio/AudioToolbox/AudioComponentManager.mm
+++ b/src/CoreAudio/AudioToolbox/AudioComponentManager.mm
@@ -1,3 +1,4 @@
+#include "../AudioTrace.h"
/*
This file is part of Darling.
@@ -18,6 +19,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AudioComponentManager.h"
#include <cstdlib>
+#include <algorithm>
+#include <cstdio>
#include <CoreFoundation/CoreFoundation.h>
#include <CarbonCore/MacErrors.h>
@@ -210,6 +213,11 @@ std::vector<AudioComponent> AudioComponentManager::findMatching(const AudioCompo
}
}
+ // Prefer a newer user component over an older system implementation of
+ // the same audio unit. Retain the existing order for equal versions.
+ std::stable_sort(rv.begin(),rv.end(),[this](AudioComponent a,AudioComponent b) {
+ return m_components.at(a).version>m_components.at(b).version;
+ });
return rv;
}
@@ -274,6 +282,11 @@ OSStatus AudioComponentManager::instantiate(AudioComponent c, AudioComponentInst
return invalidComponentID;
RegisteredComponent& regd = it->second;
+ static unsigned traceCount=0;
+ if(getenv("RCLOL_AUDIO_TRACE") && traceCount++<16)
+ rclolAudioTrace("AUDIO_COMPONENT version=%u subtype=%08x user_override=%d\n",
+ regd.version,regd.desc.componentSubType,
+ regd.bundlePath.find("DarlingRiotAudio.component")!=std::string::npos);
if (!regd.factory)
{
// Load the bundle
diff --git a/src/CoreAudio/AudioToolbox/AudioConverter.cpp b/src/CoreAudio/AudioToolbox/AudioConverter.cpp
index 202121cd9..c40ba22c2 100644
--- a/src/CoreAudio/AudioToolbox/AudioConverter.cpp
+++ b/src/CoreAudio/AudioToolbox/AudioConverter.cpp
@@ -22,13 +22,35 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <CarbonCore/MacErrors.h>
#include <stdexcept>
#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+#include <mutex>
+#include <unordered_set>
+
+// Validate opaque-handle ownership before dereferencing a disposal argument.
+static std::mutex converterMutex;
+static std::unordered_set<AudioConverterRef> liveConverters;
+
+static bool audioTrace() {
+ static const bool enabled = std::getenv("RCLOL_AUDIO_TRACE") != nullptr;
+ return enabled;
+}
+
OSStatus AudioConverterNew(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat, AudioConverterRef* outAudioConverter)
{
- return AudioConverter::create(inSourceFormat, inDestinationFormat, (AudioConverter**)outAudioConverter);
+ OSStatus status = AudioConverter::create(inSourceFormat, inDestinationFormat, (AudioConverter**)outAudioConverter);
+ if (status == noErr && outAudioConverter && *outAudioConverter) {
+ std::lock_guard<std::mutex> lock(converterMutex);
+ liveConverters.insert(*outAudioConverter);
+ }
+ if (audioTrace())
+ std::fprintf(stderr, "RIOT_AUDIO new status=%d converter=%p\n", status,
+ outAudioConverter ? *outAudioConverter : nullptr);
+ return status;
}
-OSStatus AudioConverterNewSpecific(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat, UInt32 inNumberClassDescriptions, AudioClassDescription* nClassDescriptions, AudioConverterRef* outAudioConverter)
+OSStatus AudioConverterNewSpecific(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat, UInt32 inNumberClassDescriptions, const AudioClassDescription* nClassDescriptions, AudioConverterRef* outAudioConverter)
{
// FIXME
return AudioConverterNew(inSourceFormat, inDestinationFormat, outAudioConverter);
@@ -47,6 +69,15 @@ OSStatus AudioConverterReset(AudioConverterRef inAudioConverter)
OSStatus AudioConverterDispose(AudioConverterRef inAudioConverter)
{
+ if (audioTrace())
+ std::fprintf(stderr, "RIOT_AUDIO dispose converter=%p\n", inAudioConverter);
+ if (!inAudioConverter)
+ return paramErr;
+ {
+ std::lock_guard<std::mutex> lock(converterMutex);
+ if (!liveConverters.erase(inAudioConverter))
+ return paramErr;
+ }
delete (AudioConverter*)inAudioConverter;
return noErr;
}
diff --git a/src/CoreAudio/AudioToolbox/AudioConverterImpl.cpp b/src/CoreAudio/AudioToolbox/AudioConverterImpl.cpp
index dca819b06..fc4016e60 100644
--- a/src/CoreAudio/AudioToolbox/AudioConverterImpl.cpp
+++ b/src/CoreAudio/AudioToolbox/AudioConverterImpl.cpp
@@ -67,6 +67,11 @@ void AudioConverter::flush()
OSStatus AudioConverter::create(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat, AudioConverter** out)
{
TRACE2(inSourceFormat, inDestinationFormat);
+ if (!out)
+ return paramErr;
+ *out = nullptr;
+ if (!inSourceFormat || !inDestinationFormat)
+ return paramErr;
// TODO: non-interleaved audio
@@ -118,6 +123,7 @@ OSStatus AudioConverter::create(const AudioStreamBasicDescription* inSourceForma
if (avcodec_open2((*out)->m_decoder, codecIn, nullptr) < 0)
{
delete *out;
+ *out = nullptr;
std::cerr << "AudioConverter::create(): avcodec_open() failed, format in = " << std::hex << inSourceFormat->mFormatID << ", out = " << inDestinationFormat->mFormatID << std::dec << std::endl;
return paramErr;
diff --git a/src/CoreAudio/AudioToolbox/ComponentDispatch.h b/src/CoreAudio/AudioToolbox/ComponentDispatch.h
index ee6a9b171..8505c3553 100644
--- a/src/CoreAudio/AudioToolbox/ComponentDispatch.h
+++ b/src/CoreAudio/AudioToolbox/ComponentDispatch.h
@@ -43,7 +43,12 @@ OSStatus dispatchCall(AudioComponentInstance inUnit, SInt16 sel, Args... args)
AudioComponentPlugInInterface* iface = AudioComponentManager::instance()->instanceInterface(inUnit);
AudioComponentMethod method = iface->Lookup(sel);
if (method != nullptr)
- return method(iface, args...);
+ {
+ // The lookup type is variadic. Calling it directly promotes Float32
+ // parameters to double, while audio-unit entry points expect float.
+ using Method = OSStatus (*)(void*, Args...);
+ return reinterpret_cast<Method>(method)(iface, args...);
+ }
else
return badComponentSelector;
}
diff --git a/src/CoreAudio/CoreAudio/AudioConverterShim.c b/src/CoreAudio/CoreAudio/AudioConverterShim.c
new file mode 100644
index 000000000..6e85e8230
--- /dev/null
+++ b/src/CoreAudio/CoreAudio/AudioConverterShim.c
@@ -0,0 +1,90 @@
+/* Minimal AudioConverter for Darling's CoreAudio framework.
+ *
+ * Apple's CoreAudio re-exports these AudioToolbox entry points, and
+ * third-party code (e.g. Discord voice) binds them against CoreAudio.
+ * AudioToolbox cannot be re-exported here (it depends on CoreAudio),
+ * so this TU provides small self-contained implementations:
+ * - format-identical conversion works (direct frame copy);
+ * - anything else returns kAudioConverterErr_InvalidInputSize/
+ * codec-unavailable rather than crashing the caller at bind time.
+ */
+#include <CoreAudio/CoreAudioTypes.h>
+#include <AudioToolbox/AudioConverter.h>
+#include <CarbonCore/MacErrors.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct DarlingAudioConverter {
+ AudioStreamBasicDescription src;
+ AudioStreamBasicDescription dst;
+};
+
+OSStatus AudioConverterNew(const AudioStreamBasicDescription* inSourceFormat,
+ const AudioStreamBasicDescription* inDestinationFormat,
+ AudioConverterRef* outAudioConverter) {
+ struct DarlingAudioConverter* c;
+ if (!inSourceFormat || !inDestinationFormat || !outAudioConverter)
+ return paramErr;
+ c = (struct DarlingAudioConverter*)malloc(sizeof(*c));
+ if (!c)
+ return memFullErr;
+ c->src = *inSourceFormat;
+ c->dst = *inDestinationFormat;
+ *outAudioConverter = (AudioConverterRef)c;
+ return noErr;
+}
+
+OSStatus AudioConverterDispose(AudioConverterRef inAudioConverter) {
+ if (!inAudioConverter)
+ return paramErr;
+ free(inAudioConverter);
+ return noErr;
+}
+
+static int asbd_equal(const AudioStreamBasicDescription* a,
+ const AudioStreamBasicDescription* b) {
+ return a->mSampleRate == b->mSampleRate &&
+ a->mFormatID == b->mFormatID &&
+ a->mFormatFlags == b->mFormatFlags &&
+ a->mBytesPerPacket == b->mBytesPerPacket &&
+ a->mFramesPerPacket == b->mFramesPerPacket &&
+ a->mBytesPerFrame == b->mBytesPerFrame &&
+ a->mChannelsPerFrame == b->mChannelsPerFrame &&
+ a->mBitsPerChannel == b->mBitsPerChannel;
+}
+
+OSStatus AudioConverterFillComplexBuffer(AudioConverterRef inAudioConverter,
+ AudioConverterComplexInputDataProc inInputDataProc,
+ void* inInputDataProcUserData,
+ UInt32* ioOutputDataPacketSize,
+ AudioBufferList* outOutputData,
+ AudioStreamPacketDescription* outPacketDescription) {
+ struct DarlingAudioConverter* c = (struct DarlingAudioConverter*)inAudioConverter;
+ AudioBufferList inBuf;
+ UInt32 packets, bytes;
+ OSStatus st;
+
+ (void)outPacketDescription;
+ if (!c || !inInputDataProc || !ioOutputDataPacketSize || !outOutputData)
+ return paramErr;
+ if (!asbd_equal(&c->src, &c->dst))
+ return kAudioConverterErr_UnspecifiedError;
+ if (c->src.mBytesPerPacket == 0 || outOutputData->mNumberBuffers < 1)
+ return paramErr;
+
+ packets = *ioOutputDataPacketSize;
+ bytes = packets * c->src.mBytesPerPacket;
+ if (bytes > outOutputData->mBuffers[0].mDataByteSize)
+ bytes = outOutputData->mBuffers[0].mDataByteSize;
+
+ inBuf.mNumberBuffers = 1;
+ inBuf.mBuffers[0].mNumberChannels = c->src.mChannelsPerFrame;
+ inBuf.mBuffers[0].mDataByteSize = bytes;
+ inBuf.mBuffers[0].mData = outOutputData->mBuffers[0].mData;
+ st = inInputDataProc((AudioConverterRef)c, &packets, &inBuf, NULL, inInputDataProcUserData);
+ if (st != noErr)
+ return st;
+ *ioOutputDataPacketSize = packets;
+ outOutputData->mBuffers[0].mDataByteSize = packets * c->dst.mBytesPerPacket;
+ return noErr;
+}
diff --git a/src/CoreAudio/CoreAudio/AudioHardware.cpp b/src/CoreAudio/CoreAudio/AudioHardware.cpp
index 7243e6a62..fb48486c6 100644
--- a/src/CoreAudio/CoreAudio/AudioHardware.cpp
+++ b/src/CoreAudio/CoreAudio/AudioHardware.cpp
@@ -1,3 +1,4 @@
+#include "../AudioTrace.h"
/*
This file is part of Darling.
@@ -25,6 +26,9 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <CarbonCore/MacErrors.h>
#include <dispatch/dispatch.h>
#include <memory>
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
#include <unordered_map>
#include "stub.h"
@@ -38,7 +42,7 @@ static void initObjects()
g_objects.insert(std::make_pair(kAudioObjectSystemObject, std::make_unique<AudioHardwareImplPA>(kAudioObjectSystemObject)));
g_objects.insert(std::make_pair(kAudioObjectSystemObject + 1, std::make_unique<AudioHardwareImplPAOutput>(kAudioObjectSystemObject + 1)));
g_objects.insert(std::make_pair(kAudioObjectSystemObject + 2, std::make_unique<AudioHardwareImplPAInput>(kAudioObjectSystemObject + 2)));
- g_objects.insert(std::make_pair(kAudioObjectSystemObject + 1, std::make_unique<AudioHardwareImplPAOutput>(kAudioObjectSystemObject + 3, "event")));
+ g_objects.insert(std::make_pair(kAudioObjectSystemObject + 3, std::make_unique<AudioHardwareImplPAOutput>(kAudioObjectSystemObject + 3, "event")));
});
}
@@ -58,6 +62,35 @@ static AudioHardwareImpl* GetSystemObject()
return GetObject(kAudioObjectSystemObject);
}
+// Stream IDs are distinct from their owning device IDs. Chromium enumerates
+// these objects to determine input/output direction before opening audio.
+static OSStatus GetStreamPropertyData(AudioObjectID id,
+ const AudioObjectPropertyAddress* property, UInt32* size, void* data)
+{
+ if (!property || !size) return paramErr;
+ AudioHardwareImpl* device=GetObject(id&0xffffu);
+ if (!device) return kAudioHardwareBadObjectError;
+ UInt32 value;
+ const void* source=&value;
+ UInt32 required=sizeof(value);
+ switch(property->mSelector) {
+ case kAudioStreamPropertyDirection:
+ value=device->id()==kAudioObjectSystemObject+2?1:0;break;
+ case kAudioStreamPropertyTerminalType:
+ value=0;break;
+ case kAudioStreamPropertyVirtualFormat:
+ case kAudioStreamPropertyPhysicalFormat:
+ source=&device->asbd();required=sizeof(AudioStreamBasicDescription);break;
+ default:return kAudioHardwareUnknownPropertyError;
+ }
+ if(data) {
+ if(*size<required) return kAudioHardwareBadPropertySizeError;
+ memcpy(data,source,required);
+ }
+ *size=required;
+ return noErr;
+}
+
void AudioObjectShow(AudioObjectID inObjectID)
{
AudioHardwareImpl* obj = GetObject(inObjectID);
@@ -68,6 +101,9 @@ void AudioObjectShow(AudioObjectID inObjectID)
Boolean AudioObjectHasProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress)
{
+ if((inObjectID&0xffff0000u)==0x44410000u) {
+ UInt32 size=0;return GetStreamPropertyData(inObjectID,inAddress,&size,nullptr)==noErr;
+ }
AudioHardwareImpl* obj = GetObject(inObjectID);
if (!obj)
return 0;
@@ -89,24 +125,50 @@ OSStatus AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize,
const void* inQualifierData, UInt32* outDataSize)
{
+ if((inObjectID&0xffff0000u)==0x44410000u)
+ return GetStreamPropertyData(inObjectID,inAddress,outDataSize,nullptr);
AudioHardwareImpl* obj = GetObject(inObjectID);
if (!obj)
return kAudioHardwareBadObjectError;
- return obj->getPropertyDataSize(inAddress, inQualifierDataSize,
- inQualifierData, outDataSize);
+ OSStatus status=obj->getPropertyDataSize(inAddress,inQualifierDataSize,inQualifierData,outDataSize);
+ rclolAudioTrace("SIZE object=%u selector=%08x scope=%08x status=%d size=%u\n",inObjectID,inAddress->mSelector,inAddress->mScope,status,status?0:*outDataSize);
+ return status;
}
OSStatus AudioObjectGetPropertyData(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize,
const void* inQualifierData, UInt32* ioDataSize, void* outData)
{
+ if(inObjectID==kAudioObjectSystemObject && inAddress->mSelector==kAudioHardwarePropertyDeviceForUID) {
+ if(!outData || !ioDataSize || *ioDataSize<sizeof(AudioValueTranslation)) return kAudioHardwareBadPropertySizeError;
+ auto* value=static_cast<AudioValueTranslation*>(outData);
+ if(!value->mInputData || value->mInputDataSize!=sizeof(CFStringRef) || !value->mOutputData || value->mOutputDataSize<sizeof(AudioDeviceID)) return paramErr;
+ CFStringRef wanted=*static_cast<CFStringRef*>(value->mInputData);
+ AudioDeviceID found=kAudioObjectUnknown;
+ AudioObjectPropertyAddress property={kAudioDevicePropertyDeviceUID,kAudioObjectPropertyScopeGlobal,kAudioObjectPropertyElementMaster};
+ for(AudioDeviceID id=kAudioObjectSystemObject+1;id<=kAudioObjectSystemObject+3;++id) {
+ CFStringRef uid=nullptr;UInt32 size=sizeof(uid);
+ if(GetObject(id)->getPropertyData(&property,0,nullptr,&size,&uid)==noErr && uid) {
+ bool match=CFEqual(uid,wanted);CFRelease(uid);
+ if(match) { found=id;break; }
+ }
+ }
+ *static_cast<AudioDeviceID*>(value->mOutputData)=found;
+ value->mOutputDataSize=sizeof(found);*ioDataSize=sizeof(*value);return noErr;
+ }
+ if((inObjectID&0xffff0000u)==0x44410000u)
+ return GetStreamPropertyData(inObjectID,inAddress,ioDataSize,outData);
AudioHardwareImpl* obj = GetObject(inObjectID);
if (!obj)
return kAudioHardwareBadObjectError;
- return obj->getPropertyData(inAddress, inQualifierDataSize, inQualifierData,
- ioDataSize, outData);
+ static unsigned traceCount=0;
+ if(getenv("RCLOL_AUDIO_TRACE") && traceCount++<100)
+ fprintf(stderr,"AUDIO_HARDWARE_GET object=%u property=%08x scope=%08x\n",inObjectID,inAddress->mSelector,inAddress->mScope);
+ OSStatus status=obj->getPropertyData(inAddress,inQualifierDataSize,inQualifierData,ioDataSize,outData);
+ rclolAudioTrace("GET object=%u selector=%08x scope=%08x status=%d size=%u\n",inObjectID,inAddress->mSelector,inAddress->mScope,status,status?0:*ioDataSize);
+ return status;
}
OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID,
@@ -117,8 +179,9 @@ OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID,
if (!obj)
return kAudioHardwareBadObjectError;
- return obj->setPropertyData(inAddress, inQualifierDataSize, inQualifierData,
- inDataSize, inData);
+ OSStatus status=obj->setPropertyData(inAddress,inQualifierDataSize,inQualifierData,inDataSize,inData);
+ rclolAudioTrace("SET object=%u selector=%08x scope=%08x status=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,status);
+ return status;
}
OSStatus AudioObjectAddPropertyListener(AudioObjectID inObjectID,
@@ -129,7 +192,9 @@ OSStatus AudioObjectAddPropertyListener(AudioObjectID inObjectID,
if (!obj)
return kAudioHardwareBadObjectError;
- return obj->addPropertyListener(inAddress, inListener, inClientData);
+ OSStatus status=obj->addPropertyListener(inAddress,inListener,inClientData);
+ rclolAudioTrace("ADD_LISTENER object=%u selector=%08x status=%d\n",inObjectID,inAddress->mSelector,status);
+ return status;
}
OSStatus AudioObjectRemovePropertyListener(AudioObjectID inObjectID,
diff --git a/src/CoreAudio/CoreAudio/AudioHardwareImpl.cpp b/src/CoreAudio/CoreAudio/AudioHardwareImpl.cpp
index f006ba51e..e0e4b4984 100644
--- a/src/CoreAudio/CoreAudio/AudioHardwareImpl.cpp
+++ b/src/CoreAudio/CoreAudio/AudioHardwareImpl.cpp
@@ -21,6 +21,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <typeinfo>
#include <iostream>
#include <cstring>
+#include <cstddef>
+#include <CoreFoundation/CFRunLoop.h>
#include <CarbonCore/MacErrors.h>
AudioHardwareImpl::AudioHardwareImpl(AudioObjectID myId)
@@ -33,7 +35,7 @@ AudioHardwareImpl::AudioHardwareImpl(AudioObjectID myId)
m_asbd.mFormatID = kAudioFormatLinearPCM;
m_asbd.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian;
m_asbd.mFramesPerPacket = 1;
- m_asbd.mBytesPerFrame = UInt32(m_asbd.mBytesPerPacket = m_asbd.mChannelsPerFrame * m_asbd.mSampleRate * m_asbd.mBitsPerChannel / 8);
+ m_asbd.mBytesPerFrame = UInt32(m_asbd.mBytesPerPacket = m_asbd.mChannelsPerFrame * m_asbd.mBitsPerChannel / 8);
}
@@ -132,6 +134,9 @@ OSStatus AudioHardwareImpl::isPropertySettable(const AudioObjectPropertyAddress*
{
switch (inAddress->mSelector)
{
+ case kAudioHardwarePropertyRunLoop:
+ if(m_myId!=kAudioObjectSystemObject) break;
+ *outIsSettable=true;return noErr;
case kAudioDevicePropertyVolumeScalar:
case kAudioDevicePropertyVolumeDecibels:
*outIsSettable = true;
@@ -151,6 +156,12 @@ OSStatus AudioHardwareImpl::getPropertyData(const AudioObjectPropertyAddress* in
{
switch (inAddress->mSelector)
{
+ case kAudioHardwarePropertyRunLoop: {
+ if(m_myId!=kAudioObjectSystemObject) return kAudioHardwareUnknownPropertyError;
+ if(outData && *ioDataSize<sizeof(CFRunLoopRef)) return kAudioHardwareBadPropertySizeError;
+ if(outData) *(CFRunLoopRef*)outData=nullptr;
+ *ioDataSize=sizeof(CFRunLoopRef);return noErr;
+ }
case kAudioDevicePropertyStreamFormat: // returns AudioStreamBasicDescription
{
if (AudioStreamBasicDescription* asbd = static_cast<AudioStreamBasicDescription*>(outData); asbd && *ioDataSize >= sizeof(AudioStreamBasicDescription))
@@ -160,6 +171,23 @@ OSStatus AudioHardwareImpl::getPropertyData(const AudioObjectPropertyAddress* in
*ioDataSize = sizeof(AudioStreamBasicDescription);
return kAudioHardwareNoError;
}
+ case kAudioDevicePropertyBufferFrameSize: {
+ if(!m_asbd.mBytesPerFrame) return kAudioHardwareBadStreamError;
+ if(outData && *ioDataSize<sizeof(UInt32)) return kAudioHardwareBadPropertySizeError;
+ if(outData) *(UInt32*)outData=m_bufferSize/m_asbd.mBytesPerFrame;
+ *ioDataSize=sizeof(UInt32);return noErr;
+ }
+ case kAudioDevicePropertyBufferFrameSizeRange: {
+ if(outData && *ioDataSize<sizeof(AudioValueRange)) return kAudioHardwareBadPropertySizeError;
+ if(outData) { auto* range=(AudioValueRange*)outData;range->mMinimum=128;range->mMaximum=2048; }
+ *ioDataSize=sizeof(AudioValueRange);return noErr;
+ }
+ case kAudioDevicePropertyLatency:
+ case kAudioDevicePropertySafetyOffset: {
+ if(outData && *ioDataSize<sizeof(UInt32)) return kAudioHardwareBadPropertySizeError;
+ if(outData) *(UInt32*)outData=0;
+ *ioDataSize=sizeof(UInt32);return noErr;
+ }
case kAudioDevicePropertyDeviceManufacturer:
return getPropertyString(m_manufacturer, ioDataSize, outData);
case kAudioDevicePropertyDeviceManufacturerCFString:
@@ -206,12 +234,26 @@ OSStatus AudioHardwareImpl::getPropertyData(const AudioObjectPropertyAddress* in
*ioDataSize = sizeof(int);
return kAudioHardwareNoError;
}
+ case kAudioDevicePropertyPreferredChannelLayout: {
+ // A tag-only layout has no trailing channel descriptions.
+ const UInt32 size=offsetof(AudioChannelLayout,mChannelDescriptions);
+ if(outData && *ioDataSize<size) return kAudioHardwareBadPropertySizeError;
+ if(m_asbd.mChannelsPerFrame!=1 && m_asbd.mChannelsPerFrame!=2)
+ return kAudioHardwareUnsupportedOperationError;
+ if(outData) {
+ memset(outData,0,size);
+ auto* layout=static_cast<AudioChannelLayout*>(outData);
+ layout->mChannelLayoutTag=m_asbd.mChannelsPerFrame==2
+ ? kAudioChannelLayoutTag_Stereo : kAudioChannelLayoutTag_Mono;
+ }
+ *ioDataSize=size;return noErr;
+ }
case kAudioDevicePropertyPreferredChannelsForStereo:
{
if (UInt32* b = static_cast<UInt32*>(outData); outData && *ioDataSize >= sizeof(UInt32)*2)
{
- b[0] = 0;
- b[1] = 1;
+ b[0] = 1;
+ b[1] = 2;
}
*ioDataSize = sizeof(UInt32)*2;
return kAudioHardwareNoError;
@@ -253,12 +295,26 @@ OSStatus AudioHardwareImpl::getPropertyString(CFStringRef str, UInt32* ioDataSiz
OSStatus AudioHardwareImpl::setPropertyData(const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize,
const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
+ if(inAddress->mSelector==kAudioHardwarePropertyRunLoop && m_myId==kAudioObjectSystemObject) {
+ if(!inData || inDataSize!=sizeof(CFRunLoopRef)) return kAudioHardwareBadPropertySizeError;
+ // NULL selects HAL-owned threads, which is the PulseAudio backend's
+ // existing mode. A caller-supplied run loop is not supported yet.
+ return *(const CFRunLoopRef*)inData ? kAudioHardwareUnsupportedOperationError : noErr;
+ }
+
switch (inAddress->mSelector)
{
case kAudioDevicePropertyBufferSize:
{
return setBufferSize(*static_cast<const uint32_t*>(inData));
}
+ case kAudioDevicePropertyBufferFrameSize: {
+ if(!inData || inDataSize!=sizeof(UInt32)) return kAudioHardwareBadPropertySizeError;
+ UInt32 frames=*(const UInt32*)inData;
+ if(frames<128 || frames>2048 || !m_asbd.mBytesPerFrame ||
+ frames>UINT32_MAX/m_asbd.mBytesPerFrame) return paramErr;
+ return setBufferSize(frames*m_asbd.mBytesPerFrame);
+ }
case kAudioDevicePropertyVolumeScalar:
{
// TODO: Implement setting the volume
@@ -283,7 +339,9 @@ OSStatus AudioHardwareImpl::setPropertyData(const AudioObjectPropertyAddress* in
memcpy(&m_asbd, setting, sizeof(m_asbd));
m_asbd.mFramesPerPacket = 1;
- m_asbd.mBytesPerFrame = UInt32(m_asbd.mBytesPerPacket = m_asbd.mChannelsPerFrame * m_asbd.mSampleRate * m_asbd.mBitsPerChannel / 8);
+ m_asbd.mBytesPerFrame = UInt32(m_asbd.mBytesPerPacket =
+ ((m_asbd.mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? 1 : m_asbd.mChannelsPerFrame)
+ * m_asbd.mBitsPerChannel / 8);
return kAudioHardwareNoError;
}
diff --git a/src/CoreAudio/CoreAudio/AudioHardwareImpl.h b/src/CoreAudio/CoreAudio/AudioHardwareImpl.h
index f74d9b0c3..655e90d51 100644
--- a/src/CoreAudio/CoreAudio/AudioHardwareImpl.h
+++ b/src/CoreAudio/CoreAudio/AudioHardwareImpl.h
@@ -86,8 +86,7 @@ protected:
uint32_t m_bufferSize = 8192;
CFStringRef m_name = CFSTR("unknown"), m_uid = CFSTR("unknown"), m_manufacturer = CFSTR("unknown"), m_modelUid = CFSTR("unknown");
- AudioStreamBasicDescription m_asbd;
+ AudioStreamBasicDescription m_asbd = {};
};
#endif /* AUDIOHARDWAREIMPL_H */
-
diff --git a/src/CoreAudio/CoreAudio/CMakeLists.txt b/src/CoreAudio/CoreAudio/CMakeLists.txt
index 5f10c2f92..089c94127 100644
--- a/src/CoreAudio/CoreAudio/CMakeLists.txt
+++ b/src/CoreAudio/CoreAudio/CMakeLists.txt
@@ -8,6 +8,7 @@ set(coreaudio_sources
AudioHardware.cpp
AudioHardwareImpl.cpp
HostTime.mm
+ AudioConverterShim.c
pulse/AudioHardwareImplPA.cpp
pulse/AudioHardwareImplPAInput.cpp
pulse/AudioHardwareImplPAOutput.cpp
diff --git a/src/CoreAudio/CoreAudio/HostTime.mm b/src/CoreAudio/CoreAudio/HostTime.mm
index 879fb48a1..fb99e99d1 100644
--- a/src/CoreAudio/CoreAudio/HostTime.mm
+++ b/src/CoreAudio/CoreAudio/HostTime.mm
@@ -1,8 +1,32 @@
#include "CoreAudio/HostTime.h"
#include "stub.h"
+#include <mach/mach_time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
UInt64 AudioGetCurrentHostTime(void)
{
- STUB();
- return 0;
+ return mach_absolute_time();
+}
+
+UInt64 AudioConvertHostTimeToNanos(UInt64 inHostTime)
+{
+ static mach_timebase_info_data_t sTimebase = { 0, 0 };
+ if (sTimebase.denom == 0)
+ (void)mach_timebase_info(&sTimebase);
+ return inHostTime * sTimebase.numer / sTimebase.denom;
+}
+
+UInt64 AudioConvertNanosToHostTime(UInt64 inNanos)
+{
+ static mach_timebase_info_data_t sTimebase = { 0, 0 };
+ if (sTimebase.denom == 0)
+ (void)mach_timebase_info(&sTimebase);
+ return inNanos * sTimebase.denom / sTimebase.numer;
+}
+
+#ifdef __cplusplus
}
+#endif
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.cpp
index 85315086f..c43382686 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.cpp
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.cpp
@@ -26,6 +26,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <iostream>
#include <mutex>
#include <thread>
+#include <condition_variable>
+#include <chrono>
AudioHardwareImplPA::AudioHardwareImplPA(AudioObjectID myId, const char* paRole)
: AudioHardwareImpl(myId), m_paRole(paRole)
@@ -37,8 +39,34 @@ AudioHardwareImplPA::AudioHardwareImplPA(AudioObjectID myId, const char* paRole)
AudioHardwareImplPA::~AudioHardwareImplPA()
{
- if (m_context)
- pa_context_unref(m_context);
+ for(auto& stream:m_streams) stream.second->stop();
+ m_streams.clear();
+ if(m_context && m_loop) {
+ m_loop->sync(^{ pa_context_disconnect(m_context);pa_context_unref(m_context); });
+ m_context=nullptr;
+ }
+}
+
+void AudioHardwareImplPA::syncPA(void (^block)(void)) {
+ if(m_loop) m_loop->sync(block);else block();
+}
+
+OSStatus AudioHardwareImplPA::stop(AudioDeviceIOProcID inProcID) {
+ __block OSStatus result = noErr;
+ syncPA(^{
+ auto it = m_streams.find(inProcID);
+ if (it == m_streams.end()) {
+ result = kAudioHardwareNotRunningError;
+ return;
+ }
+ auto* stream = it->second.release();
+ m_streams.erase(it);
+ stream->stop();
+ // An IOProc may stop itself. Keep its C++ stream alive until the
+ // current PulseAudio callback has unwound on this serial queue.
+ m_loop->async(^{ delete stream; });
+ });
+ return result;
}
OSStatus AudioHardwareImplPA::getPropertyData(const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize,
@@ -46,6 +74,18 @@ OSStatus AudioHardwareImplPA::getPropertyData(const AudioObjectPropertyAddress*
{
switch (inAddress->mSelector)
{
+ case kAudioDevicePropertyStreams:
+ {
+ if (m_myId == kAudioObjectSystemObject) return kAudioHardwareUnknownPropertyError;
+ bool input=m_myId==kAudioObjectSystemObject+2;
+ bool matches=inAddress->mScope==kAudioObjectPropertyScopeGlobal ||
+ inAddress->mScope==(input?kAudioObjectPropertyScopeInput:kAudioObjectPropertyScopeOutput);
+ UInt32 size=matches?sizeof(AudioStreamID):0;
+ if(outData && *ioDataSize<size) return kAudioHardwareBadPropertySizeError;
+ if(outData && size) *(AudioStreamID*)outData=0x44410000u|m_myId;
+ *ioDataSize=size;
+ return noErr;
+ }
// BEGIN kAudioObjectSystemObject properties
case kAudioHardwarePropertyDefaultInputDevice: // returns AudioDeviceID
if (AudioDeviceID* devId = static_cast<AudioDeviceID*>(outData); devId && *ioDataSize >= sizeof(AudioDeviceID))
@@ -115,112 +155,47 @@ AudioHardwareStream* AudioHardwareImplPA::createStream(AudioDeviceIOProc callbac
extern "C" char ***_NSGetArgv(void);
-static void paContextStateCB(pa_context* c, void* priv)
-{
- void (^cb)(pa_context*) = (void (^)(pa_context*)) priv;
-
- pa_context_state_t state = pa_context_get_state(c);
- if (state == PA_CONTEXT_READY)
- {
- // std::cout << "PA_CONTEXT_READY\n";
-
- cb(c);
- Block_release(cb);
-
- // FIXME: We won't be notified about later disconnects...
- pa_context_set_state_callback(c, nullptr, nullptr);
- }
- else if (state == PA_CONTEXT_FAILED)
- {
- std::cerr << "PulseAudio error: PA_CONTEXT_FAILED\n";
-
- cb(nullptr);
- Block_release(cb);
- pa_context_set_state_callback(c, nullptr, nullptr);
- }
-}
-
-static const char* appNameFromExecutable()
-{
- const char* name = (*_NSGetArgv())[0];
- const char* p = std::strrchr(name, '/');
-
- if (p && *(p+1))
- return p+1;
- return name;
+struct PAContextReady {
+ std::mutex mutex;
+ std::condition_variable condition;
+ bool finished=false;
+};
+
+static void paContextStateCB(pa_context* context,void* userdata) {
+ auto state=pa_context_get_state(context);
+ if(state!=PA_CONTEXT_READY && state!=PA_CONTEXT_FAILED && state!=PA_CONTEXT_TERMINATED) return;
+ auto* ready=static_cast<PAContextReady*>(userdata);
+ std::lock_guard<std::mutex> lock(ready->mutex);
+ ready->finished=true;
+ ready->condition.notify_one();
}
-void AudioHardwareImplPA::getPAContext(void (^cb)(pa_context*))
-{
- // TODO: destruction
- static std::mutex mutex;
-
- if (m_context == nullptr)
- {
- std::unique_lock<std::mutex> l(mutex);
-
- if (m_context == nullptr)
- {
- const char* appname = appNameFromExecutable();
- const char* appid = "org.darlinghq.some-app";
-
- pa_proplist* proplist = pa_proplist_new();
-
- // Try to find a better application name & id from the bundle
- CFBundleRef mainBundle = CFBundleGetMainBundle();
- if (mainBundle)
- {
- CFStringRef ident = CFBundleGetIdentifier(mainBundle);
- if (ident)
- appid = CFStringGetCStringPtr(ident, kCFStringEncodingUTF8);
-
- CFDictionaryRef infoDict = CFBundleGetLocalInfoDictionary(mainBundle);
- if (!infoDict)
- infoDict = CFBundleGetInfoDictionary(mainBundle);
-
- if (infoDict)
- {
- CFStringRef name = (CFStringRef) CFDictionaryGetValue(infoDict, CFSTR("CFBundleDisplayName"));
- if (!name)
- name = (CFStringRef) CFDictionaryGetValue(infoDict, CFSTR("CFBundleName"));
- if (name)
- appname = CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
- }
- }
-
- pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, appname);
- pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, appid);
- if (m_paRole)
- pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, m_paRole);
- // pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "icon-name");
- // pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "game");
-
- m_loop.reset(new PADispatchMainLoop);
-
- m_context = pa_context_new_with_proplist(m_loop->getAPI(), appname, proplist);
- pa_proplist_free(proplist);
-
- if (!m_context)
- {
- cb(nullptr);
- return;
- }
-
- pa_context_set_state_callback(m_context, paContextStateCB, Block_copy(cb));
-
- if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
- {
- std::cerr << "pa_context_connect() returned an error\n";
- pa_context_set_state_callback(m_context, nullptr, nullptr);
- cb(nullptr);
- return;
- }
-
- m_loop->resume();
- }
- }
- else
- cb(m_context);
+void AudioHardwareImplPA::getPAContext(void (^cb)(pa_context*)) {
+ // Serialize initial connection and reuse. The callback always runs on the
+ // PulseAudio queue after readiness, with the stream fully constructed.
+ static std::mutex creationMutex;
+ std::unique_lock<std::mutex> creationLock(creationMutex);
+ if(!m_context) {
+ pa_proplist* properties=pa_proplist_new();
+ pa_proplist_sets(properties,PA_PROP_APPLICATION_NAME,"Darling CoreAudio");
+ pa_proplist_sets(properties,PA_PROP_APPLICATION_ID,"org.darlinghq.coreaudio");
+ if(m_paRole) pa_proplist_sets(properties,PA_PROP_MEDIA_ROLE,m_paRole);
+ m_loop.reset(new PADispatchMainLoop);
+ m_context=pa_context_new_with_proplist(m_loop->getAPI(),"Darling CoreAudio",properties);
+ pa_proplist_free(properties);
+ if(!m_context) { cb(nullptr);return; }
+ PAContextReady ready;
+ pa_context_set_state_callback(m_context,paContextStateCB,&ready);
+ int connected=pa_context_connect(m_context,nullptr,PA_CONTEXT_NOFLAGS,nullptr);
+ m_loop->resume();
+ if(connected>=0) {
+ std::unique_lock<std::mutex> lock(ready.mutex);
+ ready.condition.wait_for(lock,std::chrono::seconds(10),[&]{return ready.finished;});
+ }
+ // Remove the stack-backed readiness callback before leaving its scope.
+ m_loop->sync(^{pa_context_set_state_callback(m_context,nullptr,nullptr);});
+ }
+ m_loop->sync(^{cb(pa_context_get_state(m_context)==PA_CONTEXT_READY?m_context:nullptr);});
}
pa_sample_spec AudioHardwareImplPA::paSampleSpecForASBD(const AudioStreamBasicDescription& asbd, bool* convertSignedUnsigned)
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h
index 8421491cd..3fe1c4e2c 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h
@@ -38,6 +38,8 @@ public:
const void* inQualifierData, UInt32 inDataSize, const void* inData) override;
void getPAContext(void (^cb)(pa_context*));
+ void syncPA(void (^block)(void));
+ OSStatus stop(AudioDeviceIOProcID inProcID) override;
static pa_sample_spec paSampleSpecForASBD(const AudioStreamBasicDescription& asbd, bool* convertSignedUnsigned = nullptr);
protected:
AudioHardwareStream* createStream(AudioDeviceIOProc callback, void* clientData) override;
@@ -49,4 +51,3 @@ private:
};
#endif
-
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.cpp
index 0db6ebda7..3f3bd6542 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.cpp
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.cpp
@@ -26,6 +26,11 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
AudioHardwareStreamPA::AudioHardwareStreamPA(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData)
: AudioHardwareStream(hw, false), m_callback(callback), m_clientData(clientData)
{
+}
+
+void AudioHardwareStreamPA::initialize()
+{
+ auto* hw=static_cast<AudioHardwareImplPA*>(m_hw);
hw->getPAContext(^(pa_context* context) {
if (!context)
{
@@ -42,6 +47,7 @@ AudioHardwareStreamPA::AudioHardwareStreamPA(AudioHardwareImplPA* hw, AudioDevic
}
m_stream = pa_stream_new(context, "CoreAudio", &spec, nullptr);
+ if(!m_stream) return;
//pa_stream_set_state_callback(m_stream, [](pa_stream *s, void *userdata) {
//std::cout << "Stream state: " << pa_stream_get_state(s) << std::endl;
@@ -59,8 +65,8 @@ AudioHardwareStreamPA::AudioHardwareStreamPA(AudioHardwareImplPA* hw, AudioDevic
AudioHardwareStreamPA::~AudioHardwareStreamPA()
{
- if (m_stream)
- pa_stream_unref(m_stream);
+ auto* hw=static_cast<AudioHardwareImplPA*>(m_hw);
+ hw->syncPA(^{if(m_stream) {pa_stream_unref(m_stream);m_stream=nullptr;}});
}
void AudioHardwareStreamPA::start()
@@ -70,9 +76,20 @@ void AudioHardwareStreamPA::start()
void AudioHardwareStreamPA::stop()
{
- std::unique_lock<std::mutex> l(m_stopMutex);
- pa_stream_disconnect(m_stream);
- m_running = false;
+ auto* hw=static_cast<AudioHardwareImplPA*>(m_hw);
+ hw->syncPA(^{
+ // All stream operations and callbacks are serialized by syncPA.
+ // A callback may call stop() itself; taking its mutex here deadlocks.
+ m_running=false;
+ if(m_stream) {
+ // Disconnect may leave queued protocol work holding a stream ref.
+ // Detach client callbacks before the C++ owner can be destroyed.
+ pa_stream_set_write_callback(m_stream,nullptr,nullptr);
+ pa_stream_set_read_callback(m_stream,nullptr,nullptr);
+ pa_stream_set_state_callback(m_stream,nullptr,nullptr);
+ pa_stream_disconnect(m_stream);
+ }
+ });
}
// This function seems to only convert unsigned to signed, but it works both ways in practice
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.h b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.h
index 0425ae97f..fdf08d2dc 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.h
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPA.h
@@ -34,18 +34,17 @@ public:
void stop(/*void(^cbDone)()*/) override;
protected:
+ void initialize();
virtual void start();
void transformSignedUnsigned(AudioBufferList* abl) const;
protected:
AudioDeviceIOProc m_callback;
void* m_clientData;
- pa_stream* m_stream;
- void(^m_cbDone)();
+ pa_stream* m_stream = nullptr;
+ void(^m_cbDone)() = nullptr;
bool m_convertSignedUnsigned = false;
bool m_running = false;
- std::mutex m_stopMutex;
};
#endif /* AUDIOHARDWARESTREAMPA_H */
-
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
index d6825f1d5..739b70011 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
@@ -25,6 +25,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
AudioHardwareStreamPAInput::AudioHardwareStreamPAInput(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData)
: AudioHardwareStreamPA(hw, callback, clientData)
{
+ initialize();
// m_dump.open("/tmp/dump.raw", std::ios_base::out | std::ios_base::binary);
}
@@ -32,7 +33,6 @@ void AudioHardwareStreamPAInput::paStreamReadCB(pa_stream* s, size_t length, voi
{
AudioHardwareStreamPAInput* This = static_cast<AudioHardwareStreamPAInput*>(self);
- std::unique_lock<std::mutex> l(This->m_stopMutex);
if (!This->m_running)
return;
@@ -74,6 +74,7 @@ void AudioHardwareStreamPAInput::paStreamReadCB(pa_stream* s, size_t length, voi
// std::cout << "AudioHardwareStreamPAInput::paStreamReadCB(): bytes=" << nbytes << std::endl;
OSStatus status = This->m_callback(This->m_hw->id(), &fake, abl, &fake, nullptr, nullptr, This->m_clientData);
+ if (!This->m_running) return;
pa_stream_drop(This->m_stream);
if (status != noErr)
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
index 278630f02..00db02f9c 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
@@ -21,16 +21,17 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include "AudioHardwareImplPA.h"
#include <iostream>
#include <algorithm>
+#include <mach/mach_time.h>
AudioHardwareStreamPAOutput::AudioHardwareStreamPAOutput(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData)
: AudioHardwareStreamPA(hw, callback, clientData)
{
+ initialize();
}
void AudioHardwareStreamPAOutput::paStreamWriteCB(pa_stream* s, size_t length, void* self)
{
AudioHardwareStreamPAOutput* This = static_cast<AudioHardwareStreamPAOutput*>(self);
- std::unique_lock<std::mutex> l(This->m_stopMutex);
if (!This->m_running)
{
@@ -55,17 +56,26 @@ void AudioHardwareStreamPAOutput::paStreamWriteCB(pa_stream* s, size_t length, v
// std::cout << "AudioHardwareStreamPAOutput() length req=" << length << ", done=" << done << std::endl;
size_t rqsize = std::min<UInt32>(This->m_bufferSize, length-done);
- pa_stream_begin_write(This->m_stream, (void**) &abl->mBuffers[0].mData, &rqsize);
+ if(pa_stream_begin_write(This->m_stream,(void**)&abl->mBuffers[0].mData,&rqsize)<0 || !rqsize) break;
abl->mBuffers[0].mDataByteSize = rqsize;
+ fake.mHostTime=mach_absolute_time();
+ fake.mSampleTime=This->m_frames;
+ fake.mFlags=kAudioTimeStampHostTimeValid|kAudioTimeStampSampleTimeValid;
// Call the client for more data
OSStatus status = This->m_callback(This->m_hw->id(), &fake, nullptr, nullptr, abl, &fake, This->m_clientData);
+ if (!This->m_running) {
+ // The client stopped/disconnected the stream during its IOProc.
+ pa_stream_cancel_write(s);
+ return;
+ }
if (status != noErr || !abl->mBuffers[0].mDataByteSize)
{
// std::cerr << "AudioDeviceIOProc returned " << status << ", corking...\n";
pa_stream_cork(This->m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
+ pa_stream_cancel_write(This->m_stream);
break;
}
else
@@ -85,6 +95,7 @@ void AudioHardwareStreamPAOutput::paStreamWriteCB(pa_stream* s, size_t length, v
}
done += abl->mBuffers[0].mDataByteSize;
+ if(This->m_hw->asbd().mBytesPerFrame) This->m_frames+=abl->mBuffers[0].mDataByteSize/This->m_hw->asbd().mBytesPerFrame;
}
}
@@ -108,4 +119,3 @@ void AudioHardwareStreamPAOutput::start()
// pa_stream_cork(m_stream, false, [](pa_stream*, int, void*) {}, nullptr);
AudioHardwareStreamPA::start();
}
-
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h
index 735474128..a92e64cd5 100644
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h
@@ -26,9 +26,9 @@ class AudioHardwareStreamPAOutput : public AudioHardwareStreamPA
public:
AudioHardwareStreamPAOutput(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData);
private:
+ uint64_t m_frames = 0;
void start() override;
static void paStreamWriteCB(pa_stream* s, size_t length, void* self);
};
#endif /* AUDIOHARDWARESTREAMPA_H */
-
diff --git a/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.cpp b/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.cpp
index 63f3545e2..4a2d8ed62 100644
--- a/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.cpp
+++ b/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.cpp
@@ -23,6 +23,8 @@ PADispatchMainLoop::PADispatchMainLoop()
{
// Create GCD queue
m_queue = dispatch_queue_create("org.darlinghq.coreaudio.pulseaudio", nullptr);
+ m_sources=dispatch_group_create();
+ dispatch_queue_set_specific(m_queue,this,this,nullptr);
// PulseAudio expects the event loop to be suspended while we're creating the context.
// I couldn't find any mention of this in the documentation, but figured it out the hard way.
@@ -32,9 +34,22 @@ PADispatchMainLoop::PADispatchMainLoop()
PADispatchMainLoop::~PADispatchMainLoop()
{
resume();
+ dispatch_group_wait(m_sources,DISPATCH_TIME_FOREVER);
+ dispatch_sync(m_queue, ^{});
+ dispatch_release(m_sources);
dispatch_release(m_queue);
}
+void PADispatchMainLoop::sync(void (^block)(void)) {
+ if(dispatch_get_specific(this)==this) block();
+ else {resume();dispatch_sync(m_queue,block);}
+}
+
+void PADispatchMainLoop::async(void (^block)(void)) {
+ resume();
+ dispatch_async(m_queue, block);
+}
+
void PADispatchMainLoop::suspend()
{
if (!m_suspended)
@@ -87,6 +102,8 @@ struct dual_source
pa_io_event_flags_t events;
void* userdata;
int fd;
+ bool freed=false;
+ unsigned cancellations=2;
};
pa_io_event* PADispatchMainLoop::io_new(pa_mainloop_api *a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata)
@@ -108,22 +125,25 @@ pa_io_event* PADispatchMainLoop::io_new(pa_mainloop_api *a, int fd, pa_io_event_
dual->sourceWrite = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, q);
dispatch_source_set_event_handler(dual->sourceRead, ^{
- dual->callback(This->getAPI(), reinterpret_cast<pa_io_event*>(dual), fd, PA_IO_EVENT_INPUT, dual->userdata);
+ if(!dual->freed && dual->readResumed) dual->callback(This->getAPI(), reinterpret_cast<pa_io_event*>(dual), fd, PA_IO_EVENT_INPUT, dual->userdata);
});
dispatch_source_set_event_handler(dual->sourceWrite, ^{
// std::cout << "PADispatchMainLoop::io_new(): write event on fd " << fd << std::endl;
- dual->callback(This->getAPI(), reinterpret_cast<pa_io_event*>(dual), fd, PA_IO_EVENT_OUTPUT, dual->userdata);
+ if(!dual->freed && dual->writeResumed) dual->callback(This->getAPI(), reinterpret_cast<pa_io_event*>(dual), fd, PA_IO_EVENT_OUTPUT, dual->userdata);
});
- dispatch_source_set_cancel_handler(dual->sourceWrite, ^{
- if (dual->destroy)
- {
- dual->destroy(This->getAPI(), reinterpret_cast<pa_io_event*>(dual), dual->userdata);
- dual->destroy = nullptr;
- }
- delete dual;
- });
+ dispatch_group_enter(This->m_sources);
+ dispatch_group_enter(This->m_sources);
+ void (^cancel)(void)=^{
+ if(--dual->cancellations==0) {
+ if(dual->destroy) dual->destroy(This->getAPI(),reinterpret_cast<pa_io_event*>(dual),dual->userdata);
+ delete dual;
+ }
+ dispatch_group_leave(This->m_sources);
+ };
+ dispatch_source_set_cancel_handler(dual->sourceRead,cancel);
+ dispatch_source_set_cancel_handler(dual->sourceWrite,cancel);
if (events & (PA_IO_EVENT_INPUT | PA_IO_EVENT_ERROR | PA_IO_EVENT_HANGUP))
{
@@ -185,6 +205,7 @@ void PADispatchMainLoop::io_free(pa_io_event *e)
{
dual_source* dual = reinterpret_cast<dual_source*>(e);
// std::cout << "PADispatchMainLoop::io_free(): fd=" << dual->fd << std::endl;
+ dual->freed=true;
if (!dual->readResumed)
dispatch_resume(dual->sourceRead);
@@ -218,6 +239,7 @@ struct pa_timer
struct timespec when;
struct linux_timeval tv;
pa_time_event_destroy_cb_t destroy = nullptr;
+ bool freed=false;
};
pa_time_event *PADispatchMainLoop::time_new(pa_mainloop_api *a, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata)
@@ -232,22 +254,24 @@ pa_time_event *PADispatchMainLoop::time_new(pa_mainloop_api *a, const struct tim
timer->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q);
- timer->when.tv_sec = real_tv->tv_sec;
- timer->when.tv_nsec = real_tv->tv_usec * 1000; // convert to ns
- timer->tv = *real_tv;
+ timer->when.tv_sec = real_tv ? real_tv->tv_sec : 0;
+ timer->when.tv_nsec = real_tv ? real_tv->tv_usec * 1000 : 0;
+ timer->tv = real_tv ? *real_tv : linux_timeval{};
// std::cout << "PADispatchMainLoop::time_new(), sec=" << real_tv->tv_sec << ", usec=" << real_tv->tv_usec << std::endl;
- dispatch_source_set_timer(timer->source, dispatch_walltime(&timer->when, 0), 0, 0);
+ dispatch_source_set_timer(timer->source, real_tv ? dispatch_walltime(&timer->when, 0) : DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
dispatch_source_set_event_handler(timer->source, ^{
// std::cout << "PADispatchMainLoop::time_new(): fired\n";
- cb(This->getAPI(), reinterpret_cast<pa_time_event*>(timer), reinterpret_cast<const struct timeval *>(&timer->tv), timer->userdata);
+ if(!timer->freed) cb(This->getAPI(), reinterpret_cast<pa_time_event*>(timer), reinterpret_cast<const struct timeval *>(&timer->tv), timer->userdata);
});
+ dispatch_group_enter(This->m_sources);
dispatch_source_set_cancel_handler(timer->source, ^{
if (timer->destroy)
timer->destroy(This->getAPI(), reinterpret_cast<pa_time_event*>(timer), timer->userdata);
delete timer;
+ dispatch_group_leave(This->m_sources);
});
dispatch_resume(timer->source);
@@ -259,6 +283,7 @@ void PADispatchMainLoop::time_restart(pa_time_event *e, const struct timeval *tv
{
const linux_timeval* real_tv = reinterpret_cast<const linux_timeval*>(tv);
pa_timer* timer = reinterpret_cast<pa_timer*>(e);
+ if(!tv) { dispatch_source_set_timer(timer->source,DISPATCH_TIME_FOREVER,DISPATCH_TIME_FOREVER,0);return; }
timer->when.tv_sec = real_tv->tv_sec;
timer->when.tv_nsec = real_tv->tv_usec * 1000; // convert to ns
@@ -266,7 +291,7 @@ void PADispatchMainLoop::time_restart(pa_time_event *e, const struct timeval *tv
// std::cout << "PADispatchMainLoop::time_restart(), sec=" << real_tv->tv_sec << ", usec=" << real_tv->tv_usec << std::endl;
- dispatch_source_set_timer(timer->source, dispatch_walltime(&timer->when, 0), 0, 0);
+ dispatch_source_set_timer(timer->source, dispatch_walltime(&timer->when, 0), DISPATCH_TIME_FOREVER, 0);
}
void PADispatchMainLoop::time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb)
@@ -281,6 +306,7 @@ void PADispatchMainLoop::time_free(pa_time_event *e)
pa_timer* timer = reinterpret_cast<pa_timer*>(e);
// std::cout << "PADispatchMainLoop::time_free()\n";
+ timer->freed=true;
dispatch_source_cancel(timer->source);
dispatch_release(timer->source);
@@ -289,6 +315,8 @@ void PADispatchMainLoop::time_free(pa_time_event *e)
struct defer_event
{
bool active = true;
+ bool scheduled = true;
+ bool freed = false;
void* userdata;
pa_defer_event_cb_t callback;
pa_defer_event_destroy_cb_t destroy = nullptr;
@@ -298,7 +326,8 @@ struct defer_event
void PADispatchMainLoop::defer_event_fire(void* context)
{
defer_event* ev = static_cast<defer_event*>(context);
- if (ev->active)
+ ev->scheduled=false;
+ if (ev->active && !ev->freed)
{
pa_mainloop_api* a = ev->loop->getAPI();
dispatch_queue_t q = static_cast<PADispatchMainLoop*>(a->userdata)->m_queue;
@@ -307,8 +336,10 @@ void PADispatchMainLoop::defer_event_fire(void* context)
ev->callback(a, static_cast<pa_defer_event*>(context), ev->userdata);
- if (ev->active)
- dispatch_async_f(q, context, defer_event_fire);
+ if(ev->active && !ev->freed && !ev->scheduled) {
+ ev->scheduled=true;
+ dispatch_async_f(q,context,defer_event_fire);
+ }
}
}
@@ -322,7 +353,7 @@ pa_defer_event *PADispatchMainLoop::defer_new(pa_mainloop_api *a, pa_defer_event
e->loop = This;
e->userdata = userdata;
e->callback = cb;
-
+ dispatch_group_enter(This->m_sources);
dispatch_async_f(q, e, defer_event_fire);
return reinterpret_cast<pa_defer_event*>(e);
@@ -342,9 +373,11 @@ void PADispatchMainLoop::defer_enable(pa_defer_event *e, int b)
// std::cout << "PADispatchMainLoop::defer_enable(): with userdata=" << ev->userdata << ", enable=" << b << std::endl;
- if (!ev->active && b)
- dispatch_async_f(q, e, defer_event_fire);
- ev->active = !!b;
+ ev->active=!!b;
+ if(ev->active && !ev->freed && !ev->scheduled) {
+ ev->scheduled=true;
+ dispatch_async_f(q,e,defer_event_fire);
+ }
}
void PADispatchMainLoop::defer_free(pa_defer_event *e)
@@ -355,10 +388,11 @@ void PADispatchMainLoop::defer_free(pa_defer_event *e)
// std::cout << "PADispatchMainLoop::defer_free(): with userdata=" << ev->userdata << std::endl;
ev->active = false;
-
+ ev->freed=true;
dispatch_async(q, ^{
if (ev->destroy)
ev->destroy(ev->loop->getAPI(), e, ev->userdata);
+ dispatch_group_leave(ev->loop->m_sources);
delete ev;
});
}
diff --git a/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h b/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h
index ff83d1e15..0d84a2e0d 100644
--- a/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h
+++ b/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h
@@ -30,6 +30,8 @@ public:
void suspend();
void resume();
+ void sync(void (^block)(void));
+ void async(void (^block)(void));
pa_mainloop_api* getAPI();
private:
@@ -50,6 +52,7 @@ private:
static void defer_event_fire(void* context);
private:
dispatch_queue_t m_queue;
+ dispatch_group_t m_sources;
pa_mainloop_api m_api = {0};
bool m_suspended = true;
};
diff --git a/src/CoreAudio/CoreAudioComponent/AUHAL.cpp b/src/CoreAudio/CoreAudioComponent/AUHAL.cpp
index fe4d1eaa1..172f751ba 100644
--- a/src/CoreAudio/CoreAudioComponent/AUHAL.cpp
+++ b/src/CoreAudio/CoreAudioComponent/AUHAL.cpp
@@ -1,3 +1,4 @@
+#include "../AudioTrace.h"
/*
This file is part of Darling.
@@ -19,6 +20,11 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include "AUHAL.h"
#include <iostream>
+#include <vector>
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <pthread.h>
#pragma GCC visibility push(default)
AUDIOCOMPONENT_ENTRY(AUOutputBaseFactory, AUHAL);
@@ -42,6 +48,37 @@ AUHAL::~AUHAL()
Stop();
}
+UInt32 AUHAL::GetChannelLayoutTags(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayoutTag* tags) {
+ if(element!=kOutputBus || (scope!=kAudioUnitScope_Input && scope!=kAudioUnitScope_Output)) return 0;
+ UInt32 channels=GetStreamFormat(scope,element).mChannelsPerFrame;
+ if(tags) *tags=channels==1?kAudioChannelLayoutTag_Mono:channels==2?kAudioChannelLayoutTag_Stereo:(kAudioChannelLayoutTag_DiscreteInOrder|channels);
+ return 1;
+}
+
+UInt32 AUHAL::GetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayout* layout,Boolean& writable) {
+ AudioChannelLayoutTag tag;
+ if(!GetChannelLayoutTags(scope,element,&tag)) return 0;
+ writable=!m_running;
+ if(layout) { layout->mChannelLayoutTag=tag;layout->mChannelBitmap=0;layout->mNumberChannelDescriptions=0; }
+ return offsetof(AudioChannelLayout,mChannelDescriptions);
+}
+
+OSStatus AUHAL::SetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,const AudioChannelLayout* layout) {
+ AudioChannelLayoutTag tag;
+ if(!layout || !GetChannelLayoutTags(scope,element,&tag)) return kAudioUnitErr_InvalidElement;
+ if(layout->mChannelLayoutTag==tag) return noErr;
+ /* darling-lol: a layout is acceptable when it describes the stream's number of channels, whichever way it is written:
+ * explicit channel descriptions (League sends two: left, right), a channel bitmap, or another tag with that count.
+ * Only the exact tag was accepted, so the game's stereo description list was refused (-10851) and it rebuilt its
+ * audio graph in a loop. */
+ const UInt32 wanted=GetStreamFormat(scope,element).mChannelsPerFrame;
+ UInt32 channels;
+ if(layout->mChannelLayoutTag==kAudioChannelLayoutTag_UseChannelDescriptions) channels=layout->mNumberChannelDescriptions;
+ else if(layout->mChannelLayoutTag==kAudioChannelLayoutTag_UseChannelBitmap) channels=__builtin_popcount(layout->mChannelBitmap);
+ else channels=layout->mChannelLayoutTag&0xffff;
+ return channels==wanted?noErr:kAudioUnitErr_InvalidPropertyValue;
+}
+
bool AUHAL::CanScheduleParameters() const
{
return true;
@@ -49,7 +86,7 @@ bool AUHAL::CanScheduleParameters() const
bool AUHAL::StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element)
{
- return !m_running && IsInitialized();
+ return !m_running;
}
OSStatus AUHAL::Version()
@@ -76,19 +113,26 @@ OSStatus AUHAL::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTim
OSStatus AUHAL::Start()
{
+ if(getenv("RCLOL_AUDIO_TRACE")) rclolAudioTrace("AUDIO_UNIT_NATIVE_START output=%d input=%d\n",m_enableOutput,m_enableInput);
if (m_running)
return noErr;
if (m_enableOutput)
{
// std::cout << "Output is enabled, starting playback\n";
- AudioDeviceCreateIOProcID(m_outputDevice, playbackCallback, this, &m_outputProcID);
+ OSStatus status=AudioDeviceCreateIOProcID(m_outputDevice,playbackCallback,this,&m_outputProcID);
+ if(status) return status;
// m_auhalData.open("/tmp/auhal.raw", std::ios_base::binary | std::ios_base::out);
const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus);
- AudioDeviceSetProperty(m_outputDevice, nullptr, 0, false, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc);
- AudioDeviceStart(m_outputDevice, m_outputProcID);
+ AudioStreamBasicDescription hardware=desc;
+ hardware.mFormatFlags&=~kAudioFormatFlagIsNonInterleaved;
+ hardware.mBytesPerFrame=hardware.mChannelsPerFrame*hardware.mBitsPerChannel/8;
+ hardware.mBytesPerPacket=hardware.mBytesPerFrame;
+ status=AudioDeviceSetProperty(m_outputDevice,nullptr,0,false,kAudioDevicePropertyStreamFormat,sizeof(hardware),&hardware);
+ if(!status) status=AudioDeviceStart(m_outputDevice,m_outputProcID);
+ if(status) {AudioDeviceDestroyIOProcID(m_outputDevice,m_outputProcID);m_outputProcID=0;return status;}
}
if (m_enableInput)
{
@@ -111,13 +155,15 @@ OSStatus AUHAL::Stop()
{
AudioDeviceStop(m_outputDevice, m_outputProcID);
AudioDeviceDestroyIOProcID(m_outputDevice, m_outputProcID);
+ m_outputProcID=0;
}
if (m_inputProcID)
{
AudioDeviceStop(m_inputDevice, m_inputProcID);
AudioDeviceDestroyIOProcID(m_inputDevice, m_inputProcID);
+ m_inputProcID=0;
}
-
+ m_running=false;
return noErr;
}
@@ -125,6 +171,10 @@ OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, Au
{
switch (inID)
{
+ case kAudioDevicePropertyBufferFrameSize: {
+ AudioObjectPropertyAddress p={inID,kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
+ return AudioObjectSetPropertyData(m_outputDevice,&p,0,nullptr,inDataSize,inData);
+ }
case kAudioOutputUnitProperty_SetInputCallback:
{
ca_require(inDataSize == sizeof(AURenderCallbackStruct), InvalidPropertyValue);
@@ -176,7 +226,11 @@ OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, Au
return noErr;
}
}
- return AUBase::SetProperty(inID, inScope, inElement, inData, inDataSize);
+ {
+ OSStatus status=AUBase::SetProperty(inID,inScope,inElement,inData,inDataSize);
+ if(status && getenv("RCLOL_AUDIO_TRACE")) rclolAudioTrace("AUDIO_UNIT_SET property=%u scope=%u element=%u status=%d\n",inID,inScope,inElement,status);
+ return status;
+ }
InvalidPropertyValue:
return kAudioUnitErr_InvalidPropertyValue;
}
@@ -185,6 +239,8 @@ OSStatus AUHAL::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope
{
switch (inID)
{
+ case kAudioDevicePropertyBufferFrameSize:
+ outDataSize=sizeof(UInt32);outWritable=true;return noErr;
case kAudioOutputUnitProperty_SetInputCallback:
{
outDataSize = sizeof(AURenderCallbackStruct);
@@ -210,13 +266,20 @@ OSStatus AUHAL::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope
return noErr;
}
}
- return AUBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable);
+ OSStatus status=AUBase::GetPropertyInfo(inID,inScope,inElement,outDataSize,outWritable);
+ if(status && getenv("RCLOL_AUDIO_TRACE")) rclolAudioTrace("AUDIO_UNIT_INFO property=%u scope=%u element=%u status=%d\n",inID,inScope,inElement,status);
+ return status;
}
OSStatus AUHAL::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData)
{
switch (inID)
{
+ case kAudioDevicePropertyBufferFrameSize: {
+ AudioObjectPropertyAddress p={inID,kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
+ UInt32 size=sizeof(UInt32);
+ return AudioObjectGetPropertyData(m_outputDevice,&p,0,nullptr,&size,outData);
+ }
case kAudioOutputUnitProperty_SetInputCallback:
{
memcpy(outData, &m_outputAvailableCb, sizeof(m_outputAvailableCb));
@@ -228,12 +291,12 @@ OSStatus AUHAL::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, Au
if (inElement == kOutputBus)
{
UInt32 value = m_enableOutput;
- memcpy(&outData, &value, sizeof(value));
+ memcpy(outData, &value, sizeof(value));
}
else if (inElement == kInputBus)
{
UInt32 value = m_enableInput;
- memcpy(&outData, &value, sizeof(value));
+ memcpy(outData, &value, sizeof(value));
}
else
return kAudioUnitErr_InvalidElement;
@@ -280,6 +343,9 @@ OSStatus AUHAL::recordCallback(AudioObjectID inObjectID,
OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime)
{
// std::cout << "AUHAL::DoPlayback()\n";
+ static unsigned stackTraces=0;
+ if(getenv("RCLOL_AUDIO_TRACE") && stackTraces++<2)
+ rclolAudioTrace("AUDIO_CALLBACK_STACK bytes=%zu\n",pthread_get_stacksize_np(pthread_self()));
if (!HasInput(0))
{
// std::cerr << "No connection\n";
@@ -290,9 +356,44 @@ OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutp
AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PreRender;
const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Input, kOutputBus);
- UInt32 nFrames = outOutputData->mBuffers[0].mDataByteSize / (desc.mBytesPerFrame / outOutputData->mBuffers[0].mNumberChannels);
- result = GetInput(kOutputBus)->PullInputWithBufferList(flags, *inNow, kOutputBus, nFrames, outOutputData);
-
+ if(outOutputData->mNumberBuffers!=1 || !desc.mChannelsPerFrame ||
+ !desc.mBitsPerChannel || desc.mBitsPerChannel%8) return kAudioUnitErr_FormatNotSupported;
+ UInt32 sampleBytes=desc.mBitsPerChannel/8;
+ UInt32 frameBytes=desc.mChannelsPerFrame*sampleBytes;
+ UInt32 nFrames=outOutputData->mBuffers[0].mDataByteSize/frameBytes;
+ if(desc.mFormatFlags&kAudioFormatFlagIsNonInterleaved) {
+ m_playbackSamples.resize(nFrames*frameBytes);
+ // Preserve fresh-buffer silence for callbacks that leave samples unwritten.
+ std::fill(m_playbackSamples.begin(),m_playbackSamples.end(),0);
+ m_playbackBufferList.resize(offsetof(AudioBufferList,mBuffers)+desc.mChannelsPerFrame*sizeof(AudioBuffer));
+ AudioBufferList* planar=(AudioBufferList*)m_playbackBufferList.data();
+ planar->mNumberBuffers=desc.mChannelsPerFrame;
+ for(UInt32 c=0;c<desc.mChannelsPerFrame;++c) {
+ planar->mBuffers[c].mNumberChannels=1;
+ planar->mBuffers[c].mDataByteSize=nFrames*sampleBytes;
+ planar->mBuffers[c].mData=m_playbackSamples.data()+c*nFrames*sampleBytes;
+ }
+ result=GetInput(kOutputBus)->PullInputWithBufferList(flags,*inNow,kOutputBus,nFrames,planar);
+ if(!result) {
+ auto* destination=(unsigned char*)outOutputData->mBuffers[0].mData;
+ for(UInt32 f=0;f<nFrames;++f) for(UInt32 c=0;c<desc.mChannelsPerFrame;++c)
+ memcpy(destination+f*frameBytes+c*sampleBytes,
+ (unsigned char*)planar->mBuffers[c].mData+f*sampleBytes,sampleBytes);
+ }
+ } else {
+ result=GetInput(kOutputBus)->PullInputWithBufferList(flags,*inNow,kOutputBus,nFrames,outOutputData);
+ }
+
+ static unsigned traceRenders=0;
+ if (getenv("RCLOL_AUDIO_TRACE") && traceRenders++ < 16) {
+ float peak=0;
+ if (!result && desc.IsFloat() && sampleBytes==4) {
+ const float* samples=static_cast<const float*>(outOutputData->mBuffers[0].mData);
+ for(UInt32 i=0;i<outOutputData->mBuffers[0].mDataByteSize/4;++i)
+ peak=std::max(peak,std::abs(samples[i]));
+ }
+ rclolAudioTrace("AUDIO_RENDER status=%d flags=%u frames=%u peak=%g\n",result,flags,nFrames,peak);
+ }
// std::cout << "Pull result: " << result << std::endl;
// std::cout << "Bytes: " << outOutputData->mBuffers[0].mDataByteSize << std::endl;
// m_auhalData.write((char*) outOutputData->mBuffers[0].mData, outOutputData->mBuffers[0].mDataByteSize);
diff --git a/src/CoreAudio/CoreAudioComponent/AUHAL.h b/src/CoreAudio/CoreAudioComponent/AUHAL.h
index bc21bcbbb..190a0ffc8 100644
--- a/src/CoreAudio/CoreAudioComponent/AUHAL.h
+++ b/src/CoreAudio/CoreAudioComponent/AUHAL.h
@@ -25,6 +25,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <mutex>
#include <memory>
#include <stdint.h>
+#include <vector>
class AUHAL : public AUBase
{
@@ -32,6 +33,9 @@ public:
AUHAL(AudioComponentInstance inInstance, bool supportRecording = true);
~AUHAL();
+ UInt32 GetChannelLayoutTags(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayoutTag* tags) override;
+ UInt32 GetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayout* layout,Boolean& writable) override;
+ OSStatus SetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,const AudioChannelLayout* layout) override;
bool CanScheduleParameters() const override;
bool StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) override;
OSStatus Version() override;
@@ -76,6 +80,11 @@ protected:
// TODO: Prepare for non-interleaved audio
std::unique_ptr<uint8_t[]> m_buffer;
size_t m_bufferSize = 0, m_bufferUsed = 0;
+
+ // Playback callbacks run serially on the device's PulseAudio queue.
+ // Keep planar conversion storage across callbacks and start/stop cycles.
+ std::vector<unsigned char> m_playbackSamples;
+ std::vector<unsigned char> m_playbackBufferList;
};
#endif
diff --git a/src/CoreAudio/CoreAudioComponent/CMakeLists.txt b/src/CoreAudio/CoreAudioComponent/CMakeLists.txt
index 38584e263..a4cf87529 100644
--- a/src/CoreAudio/CoreAudioComponent/CMakeLists.txt
+++ b/src/CoreAudio/CoreAudioComponent/CMakeLists.txt
@@ -29,6 +29,7 @@ set(component_sources
PublicUtility/CAVectorUnit.cpp
AUHAL.cpp
SystemOutputAU.cpp
+ MatrixMixerAU.cpp
DefaultOutputAU.cpp
)
diff --git a/src/CoreAudio/CoreAudioComponent/DefaultOutputAU.cpp b/src/CoreAudio/CoreAudioComponent/DefaultOutputAU.cpp
index 8b92b6c50..18a57e359 100644
--- a/src/CoreAudio/CoreAudioComponent/DefaultOutputAU.cpp
+++ b/src/CoreAudio/CoreAudioComponent/DefaultOutputAU.cpp
@@ -18,6 +18,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DefaultOutputAU.h"
+#include <cstring>
+#include <stdlib.h>
#pragma GCC visibility push(default)
AUDIOCOMPONENT_ENTRY(AUOutputBaseFactory, DefaultOutputAU);
#pragma GCC visibility pop
@@ -32,11 +34,27 @@ DefaultOutputAU::DefaultOutputAU(AudioComponentInstance inInstance)
{
}
+/* darling-lol: League's game process sets the default device as the current device of its DefaultOutput unit. Refusing that
+ * (kAudioUnitErr_InvalidProperty) made it throw its audio graph away and build a new one in a loop, so it never played
+ * anything. Accepting it harmless: the unit plays on the default device anyway.
+ *
+ * Only the game process gets this. With the property accepted everywhere, the League client's Vanguard module went to its
+ * error state (MvgState 5, status -101) on every launch (2 of 2, 2026-10-11) and the client could not start a match;
+ * with the old behaviour it connected again (state 0). */
+static bool callerIsLeagueGame()
+{
+ static const bool game = []{
+ const char* name = getprogname();
+ return name && strcmp(name, "LeagueofLegends") == 0;
+ }();
+ return game;
+}
+
OSStatus DefaultOutputAU::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable)
{
OSStatus status = AUHAL::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable);
- if (inID == kAudioOutputUnitProperty_CurrentDevice)
+ if (inID == kAudioOutputUnitProperty_CurrentDevice && !callerIsLeagueGame())
outWritable = false;
return status;
@@ -48,7 +66,9 @@ OSStatus DefaultOutputAU::SetProperty(AudioUnitPropertyID inID, AudioUnitScope i
switch (inID)
{
case kAudioOutputUnitProperty_CurrentDevice:
- return kAudioUnitErr_InvalidProperty;
+ if (!callerIsLeagueGame())
+ return kAudioUnitErr_InvalidProperty;
+ break;
}
return AUHAL::SetProperty(inID, inScope, inElement, inData, inDataSize);
}
diff --git a/src/CoreAudio/CoreAudioComponent/Info.plist b/src/CoreAudio/CoreAudioComponent/Info.plist
index da44a9f18..f658118d8 100644
--- a/src/CoreAudio/CoreAudioComponent/Info.plist
+++ b/src/CoreAudio/CoreAudioComponent/Info.plist
@@ -70,6 +70,24 @@
<key>version</key>
<integer>1</integer>
</dict>
+ <dict>
+ <key>description</key>
+ <string>Float PCM matrix mixer</string>
+ <key>factoryFunction</key>
+ <string>MatrixMixerAUFactory</string>
+ <key>manufacturer</key>
+ <string>appl</string>
+ <key>name</key>
+ <string>Darling: MatrixMixer</string>
+ <key>sandboxSafe</key>
+ <true/>
+ <key>subtype</key>
+ <string>mxmx</string>
+ <key>type</key>
+ <string>aumx</string>
+ <key>version</key>
+ <integer>1</integer>
+ </dict>
</array>
<key>CFBundleExecutable</key>
<string>CoreAudio</string>
diff --git a/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h b/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
index 102614203..9b80671a6 100644
--- a/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
+++ b/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
@@ -13,12 +13,12 @@ typedef UInt32 AudioObjectPropertySelector;
typedef UInt32 AudioObjectPropertyScope;
typedef UInt32 AudioObjectPropertyElement;
-struct AudioObjectPropertyAddress
+typedef struct AudioObjectPropertyAddress
{
AudioObjectPropertySelector mSelector;
AudioObjectPropertyScope mScope;
AudioObjectPropertyElement mElement;
-};
+} AudioObjectPropertyAddress;
enum
{
@@ -87,6 +87,14 @@ enum
};
+typedef AudioObjectID AudioStreamID;
+enum {
+ kAudioStreamPropertyDirection = 'sdir',
+ kAudioStreamPropertyTerminalType = 'term',
+ kAudioStreamPropertyVirtualFormat = 'sfmt',
+ kAudioStreamPropertyPhysicalFormat = 'pft ',
+};
+
// TODO: A lot more is missing
#ifdef __cplusplus
@@ -94,4 +102,3 @@ enum
#endif
#endif /* AUDIOHARDWAREBASE_H */
-
diff --git a/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h b/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
index cb2896957..b2c25fbd4 100644
--- a/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
+++ b/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
@@ -18,7 +18,22 @@
*/
#include <Foundation/Foundation.h>
+#include <AVFoundation/AVMediaFormat.h>
+
+typedef NS_ENUM(NSInteger, AVAuthorizationStatus) {
+ AVAuthorizationStatusNotDetermined = 0,
+ AVAuthorizationStatusRestricted = 1,
+ AVAuthorizationStatusDenied = 2,
+ AVAuthorizationStatusAuthorized = 3,
+};
@interface AVCaptureDevice : NSObject
++ (NSArray *)devices;
++ (NSArray *)devicesWithMediaType:(AVMediaType)mediaType;
++ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType;
+
++ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType;
++ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler;
+
@end
diff --git a/src/frameworks/AVFoundation/src/AVCaptureDevice.m b/src/frameworks/AVFoundation/src/AVCaptureDevice.m
index cab525b91..2fcab003d 100644
--- a/src/frameworks/AVFoundation/src/AVCaptureDevice.m
+++ b/src/frameworks/AVFoundation/src/AVCaptureDevice.m
@@ -21,6 +21,37 @@
@implementation AVCaptureDevice
++ (NSArray *)devices {
+ return [NSArray array];
+}
+
++ (NSArray *)devicesWithMediaType:(AVMediaType)mediaType {
+ (void)mediaType;
+ return [self devices];
+}
+
++ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType {
+ (void)mediaType;
+ return nil;
+}
+
+
++ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType
+{
+ // Darling has no camera/microphone capture or TCC consent store;
+ // report Denied so callers take their no-access path instead of
+ // raising an unrecognized-selector exception.
+ (void)mediaType;
+ return AVAuthorizationStatusDenied;
+}
+
++ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL))handler
+{
+ (void)mediaType;
+ if (handler)
+ handler(NO);
+}
+
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
}
diff --git a/src/frameworks/Carbon/HIToolbox/CMakeLists.txt b/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
index 3f0820484..7d310f61c 100644
--- a/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
+++ b/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
@@ -19,6 +19,7 @@ add_framework(HIToolbox
SOURCES
src/Keyboards.cpp
+ src/SecureInput.c
src/Events.cpp
src/CarbonEventsCore.c
src/CarbonEvents.c
diff --git a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
index 8c37ce2a6..71ed125a5 100644
--- a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
+++ b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
@@ -19,6 +19,7 @@ OSStatus SetEventParameter(EventRef a, EventParamName b, EventParamType c, UInt3
extern UInt8 LMGetKbdType(void);
+extern UInt8 LMGetKbdLast(void);
enum {
kVK_ANSI_A = 0x00,
diff --git a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
index 8d457c216..4b42c9af8 100644
--- a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
+++ b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
@@ -1,6 +1,7 @@
#ifndef _Carbon_TextInputSources_H_
#define _Carbon_TextInputSources_H_
#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CFArray.h>
#ifdef __cplusplus
extern "C" {
@@ -25,6 +26,7 @@ extern TISInputSourceRef TISCopyCurrentKeyboardInputSource(void);
extern void* TISGetInputSourceProperty(TISInputSourceRef inputSourceRef, CFStringRef key);
extern TISInputSourceRef TISCopyCurrentASCIICapableKeyboardLayoutInputSource(void);
+extern CFArrayRef TISCreateASCIICapableInputSourceList(void);
#ifdef __cplusplus
}
diff --git a/src/frameworks/Carbon/HIToolbox/src/Events.cpp b/src/frameworks/Carbon/HIToolbox/src/Events.cpp
index a37fc641f..98e29c97a 100644
--- a/src/frameworks/Carbon/HIToolbox/src/Events.cpp
+++ b/src/frameworks/Carbon/HIToolbox/src/Events.cpp
@@ -4,3 +4,10 @@ UInt8 LMGetKbdType(void)
{
return 0;
}
+
+UInt8 LMGetKbdLast(void)
+{
+ // Darling exposes one logical keyboard through its current XKB layout.
+ // Its last-used keyboard type is therefore the current keyboard type.
+ return LMGetKbdType();
+}
diff --git a/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm b/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
index 347cb530c..f73278a35 100644
--- a/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
+++ b/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
@@ -85,3 +85,10 @@ TISInputSourceRef TISCopyCurrentASCIICapableKeyboardLayoutInputSource(void)
return NULL;
}
+
+CFArrayRef TISCreateASCIICapableInputSourceList(void)
+{
+ /* Until ASCII-capable layouts can be enumerated, return an owned empty
+ * list. A NULL list would crash callers that unconditionally CFRelease. */
+ return CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
+}
diff --git a/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h b/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
index 090de1df2..fe1f95f3b 100644
--- a/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
+++ b/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
@@ -19,6 +19,15 @@
#include <Foundation/Foundation.h>
+typedef double CLLocationAccuracy;
+
+extern const CLLocationAccuracy kCLLocationAccuracyBestForNavigation;
+extern const CLLocationAccuracy kCLLocationAccuracyBest;
+extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
+extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
+extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
+extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;
+
@interface CLLocation : NSObject
@end
diff --git a/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h b/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
index 9e0fa55c4..993124e76 100644
--- a/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
+++ b/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
@@ -20,5 +20,14 @@
#include <Foundation/Foundation.h>
@interface CLLocationManager : NSObject
+{
+ id _delegate;
+}
+
+/* darling-lol: real property so callers (Electron's device-permission
+ * path) get a proper accessor instead of falling through to the stub
+ * forwardInvocation, whose 0-arg signature raised
+ * `NSForwardSignatureError: invoked with 3 args, but 2 expected`. */
+@property (nonatomic, assign) id delegate;
@end
diff --git a/src/frameworks/CoreLocation/src/CLLocation.m b/src/frameworks/CoreLocation/src/CLLocation.m
index f396e00e4..5f3965705 100644
--- a/src/frameworks/CoreLocation/src/CLLocation.m
+++ b/src/frameworks/CoreLocation/src/CLLocation.m
@@ -32,3 +32,10 @@
}
@end
+
+const CLLocationAccuracy kCLLocationAccuracyBestForNavigation = -2.0;
+const CLLocationAccuracy kCLLocationAccuracyBest = -1.0;
+const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters = 10.0;
+const CLLocationAccuracy kCLLocationAccuracyHundredMeters = 100.0;
+const CLLocationAccuracy kCLLocationAccuracyKilometer = 1000.0;
+const CLLocationAccuracy kCLLocationAccuracyThreeKilometers = 3000.0;
diff --git a/src/frameworks/CoreLocation/src/CLLocationManager.m b/src/frameworks/CoreLocation/src/CLLocationManager.m
index 0b927c40e..d186494e7 100644
--- a/src/frameworks/CoreLocation/src/CLLocationManager.m
+++ b/src/frameworks/CoreLocation/src/CLLocationManager.m
@@ -21,6 +21,8 @@
@implementation CLLocationManager
+@synthesize delegate = _delegate;
+
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
diff --git a/src/frameworks/CoreMedia/src/CoreMedia.c b/src/frameworks/CoreMedia/src/CoreMedia.c
index 3d76e8103..4a975959c 100644
--- a/src/frameworks/CoreMedia/src/CoreMedia.c
+++ b/src/frameworks/CoreMedia/src/CoreMedia.c
@@ -26,6 +26,32 @@
const CFStringRef kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms = CFSTR("SampleDescriptionExtensionAtoms");
const CFStringRef kCMSampleAttachmentKey_NotSync = CFSTR("NotSync");
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_709_2 = CFSTR("ITU_R_709_2");
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_240M_1995 = CFSTR("SMPTE_240M_1995");
+const CFStringRef kCMFormatDescriptionTransferFunction_UseGamma = CFSTR("UseGamma");
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2020 = CFSTR("ITU_R_2020");
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG = CFSTR("ITU_R_2100_HLG");
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ = CFSTR("SMPTE_ST_2084_PQ");
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_428_1 = CFSTR("SMPTE_ST_428_1");
+const CFStringRef kCMFormatDescriptionTransferFunction_Linear = CFSTR("Linear");
+const CFStringRef kCMFormatDescriptionTransferFunction_sRGB = CFSTR("sRGB");
+const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_709_2 = CFSTR("ITU_R_709_2");
+const CFStringRef kCMFormatDescriptionColorPrimaries_EBU_3213 = CFSTR("EBU_3213");
+const CFStringRef kCMFormatDescriptionColorPrimaries_SMPTE_C = CFSTR("SMPTE_C");
+const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_2020 = CFSTR("ITU_R_2020");
+const CFStringRef kCMFormatDescriptionColorPrimaries_P3_D65 = CFSTR("P3_D65");
+const CFStringRef kCMFormatDescriptionColorPrimaries_DCI_P3 = CFSTR("DCI_P3");
+const CFStringRef kCMFormatDescriptionExtension_ContentLightLevelInfo = CFSTR("ContentLightLevelInfo");
+const CFStringRef kCMFormatDescriptionExtension_Depth = CFSTR("Depth");
+const CFStringRef kCMFormatDescriptionExtension_FormatName = CFSTR("FormatName");
+const CFStringRef kCMFormatDescriptionExtension_FullRangeVideo = CFSTR("FullRangeVideo");
+const CFStringRef kCMFormatDescriptionExtension_MasteringDisplayColorVolume = CFSTR("MasteringDisplayColorVolume");
+const CFStringRef kCMSampleAttachmentKey_IsDependedOnByOthers = CFSTR("IsDependedOnByOthers");
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2 = CFSTR("ITU_R_709_2");
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4 = CFSTR("ITU_R_601_4");
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995 = CFSTR("SMPTE_240M_1995");
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_2020 = CFSTR("ITU_R_2020");
+
static int verbose = 0;
__attribute__((constructor))
diff --git a/src/frameworks/CoreServices/CMakeLists.txt b/src/frameworks/CoreServices/CMakeLists.txt
index 3fac3be55..aae2c7435 100644
--- a/src/frameworks/CoreServices/CMakeLists.txt
+++ b/src/frameworks/CoreServices/CMakeLists.txt
@@ -82,6 +82,7 @@ reexport(CoreServices Metadata ${Metadata_BUILD})
reexport(CoreServices SearchKit ${SearchKit_BUILD})
reexport(CoreServices SharedFileList ${SharedFileList_BUILD})
reexport(CoreServices OSServices ${OSServices_BUILD})
+reexport(CoreServices Foundation ${CMAKE_BINARY_DIR}/src/external/foundation/Foundation)
set_property(TARGET CoreServices APPEND_STRING PROPERTY
LINK_FLAGS " -Wl,-reexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/reexport.exp")
diff --git a/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp b/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
index 094a9483d..84810f6ab 100644
--- a/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
+++ b/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
@@ -212,8 +212,10 @@ OSStatus UCKeyTranslate(const UCKeyboardLayout *keyLayoutPtr, UInt16 virtualKeyC
const UCKeyToCharTableIndex* tableIndex = (UCKeyToCharTableIndex*) (((char*) keyLayoutPtr) + keyLayoutPtr->keyboardTypeList[0].keyToCharTableIndexOffset);
uint8_t tableNumber = modMaps->defaultTableNum;
- if ((modifierKeyState >> 8) < modMaps->modifiersCount)
- tableNumber = modMaps->tableNum[(modifierKeyState >> 8)];
+ // UCKeyTranslate receives the modifier byte after the caller shifts the
+ // Carbon EventRecord flags. Shifting again loses Shift and Option state.
+ if (modifierKeyState < modMaps->modifiersCount)
+ tableNumber = modMaps->tableNum[modifierKeyState];
UniChar c;
if (virtualKeyCode >= tableIndex->keyToCharTableSize || tableNumber >= tableIndex->keyToCharTableCount)
diff --git a/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m b/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
index cf9933a84..6651d50dc 100644
--- a/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
+++ b/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
@@ -23,6 +23,7 @@
#include <stdlib.h>
#include <errno.h>
#include <string.h>
+#include <stdio.h>
#include <darling/emulation/linux_premigration/ext/sys/inotify.h>
static dispatch_queue_t g_fsEventsQueue = NULL;
@@ -68,6 +69,24 @@ static void rlPerform(void* info)
for (NSString* path in _pathsToWatch)
{
int wd = inotify_add_watch(_fd, [path UTF8String], IN_ATTRIB | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
+ /* darling-lol: a path that doesn't exist yet (e.g. /etc/hosts or
+ * /etc/resolv.conf missing in the prefix) makes inotify_add_watch
+ * return -1. Apple's FSEvents tolerates unwatchable paths and the
+ * stream still starts; Chromium (dns_config_service_posix) treats
+ * a failed start as fatal for its DNS watcher. Skip failures
+ * instead of inserting a bogus -1 wd, and remember the path so a
+ * later event on the directory can still be reported. */
+ if (wd < 0)
+ {
+ if (getenv("DARLING_LOL_FSEVLOG") != NULL)
+ {
+ char b[512];
+ int n = snprintf(b, sizeof(b), "FSEV add_watch failed: %s (errno=%d)\n",
+ [path UTF8String], errno);
+ if (n > 0) write(2, b, n);
+ }
+ continue;
+ }
[wdmap setObject: path
forKey: [NSNumber numberWithInt: wd]];
}
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m b/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
index dd78f6df4..fc25c7970 100644
--- a/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
@@ -19,8 +19,100 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include <LaunchServices/LaunchServices.h>
+#include <LaunchServices/LaunchServicesPriv.h>
#include <CarbonCore/MacErrors.h>
#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+// darling-lol M2a: minimal bundle launching.
+// Supports: file URLs (or bare POSIX paths) pointing at .app bundles,
+// optionally with an explicit target app via inAppParams->application.
+// The bundle's executable is spawned directly with the emulated posix_spawn
+// (no launchd job registration yet — see _LSLaunchApplication).
+// NOT yet supported (documented deviations):
+// - AppleEvent delivery (oapp/odoc): the app is spawned bare, as if launched
+// by hand. Apps waiting for their initial event will park. Needs #425/#937.
+// - inAppParams->argv / ->environment: not propagated (_LSLaunchApplication
+// takes no argv yet).
+// - inRole / launch flags (kLSLaunchNewInstance etc.): every launch spawns a
+// new instance; no running-app registry, so outPSNs is left untouched.
+// - non-bundle documents without an explicit application: there is no default
+// handler database yet (needs UTI/LaunchServices DB).
+static OSStatus
+LSOpenOneURL(CFURLRef url, const LSApplicationParameters *params)
+{
+ CFStringRef pathStr;
+ char path[4096];
+ struct stat st;
+ CFURLRef appURL = NULL;
+ OSStatus status;
+
+ if (url == NULL)
+ return paramErr;
+
+ // open(1) builds URLs via +URLWithString from a raw path, which may yield
+ // a scheme-less URL object. CFURLCopyFileSystemPath works either way.
+ pathStr = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
+ if (pathStr == NULL)
+ return paramErr;
+
+ if (!CFStringGetCString(pathStr, path, sizeof(path), kCFStringEncodingUTF8))
+ {
+ CFRelease(pathStr);
+ return paramErr;
+ }
+ CFRelease(pathStr);
+
+ if (params != NULL && params->application != NULL)
+ {
+ // Explicit target application wins; the URLs become its documents
+ // (delivered as argv once M2c lands; for now they are dropped).
+ char appPath[4096];
+ CFStringRef appStr;
+ status = FSRefMakePath(params->application, (UInt8*) appPath, sizeof(appPath) - 1);
+ if (status != noErr)
+ return status;
+ appStr = CFStringCreateWithCString(NULL, appPath, kCFStringEncodingUTF8);
+ appURL = CFURLCreateWithFileSystemPath(NULL, appStr, kCFURLPOSIXPathStyle, TRUE);
+ CFRelease(appStr);
+ }
+ else
+ {
+ size_t len = strlen(path);
+ Boolean looksLikeBundle = FALSE;
+
+ if (len > 4 && strcasecmp(path + len - 4, ".app") == 0)
+ looksLikeBundle = TRUE;
+ else if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+ {
+ // Bundle without .app suffix (e.g. helper apps, frameworks-as-apps).
+ char plist[4096 + 32];
+ snprintf(plist, sizeof(plist), "%s/Contents/Info.plist", path);
+ if (stat(plist, &st) == 0)
+ looksLikeBundle = TRUE;
+ }
+
+ if (!looksLikeBundle)
+ return kLSApplicationNotFoundErr; // no default-handler DB yet
+
+ CFStringRef tmp = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
+ appURL = CFURLCreateWithFileSystemPath(NULL, tmp, kCFURLPOSIXPathStyle, TRUE);
+ CFRelease(tmp);
+ }
+
+ if (appURL == NULL)
+ return paramErr;
+
+ status = _LSLaunchApplication(appURL);
+ CFRelease(appURL);
+
+ // _LSLaunchApplication returns 1 on failure instead of a MacErrors code.
+ if (status != noErr)
+ status = fnfErr;
+ return status;
+}
OSStatus
LSOpenURLsWithRole(
@@ -31,8 +123,27 @@ LSOpenURLsWithRole(
ProcessSerialNumber * outPSNs,
CFIndex inMaxPSNCount)
{
- puts("LSOpenURLsWithRole STUB");
- return unimpErr;
+ CFIndex count, i, launched = 0;
+ OSStatus status, firstErr = noErr;
+
+ (void) inRole;
+ (void) inAEParam;
+ (void) outPSNs;
+ (void) inMaxPSNCount;
+
+ if (inURLs == NULL || (count = CFArrayGetCount(inURLs)) <= 0)
+ return paramErr;
+
+ for (i = 0; i < count; i++)
+ {
+ status = LSOpenOneURL(CFArrayGetValueAtIndex(inURLs, i), inAppParams);
+ if (status == noErr)
+ launched++;
+ else if (launched == 0 && firstErr == noErr)
+ firstErr = status;
+ }
+
+ return launched > 0 ? noErr : firstErr;
}
OSStatus
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c b/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c
index 854fa1e7f..5a39038d3 100644
--- a/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c
+++ b/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c
@@ -21,7 +21,11 @@
#include <LaunchServices/LaunchServicesPriv.h>
#include <stdio.h>
#include <stdlib.h>
-#include <launch_priv.h>
+#include <string.h>
+#include <errno.h>
+#include <spawn.h>
+
+extern char** environ;
static void get_main_executable_info(CFURLRef appURL, CFStringRef *exec, CFStringRef *bundleName);
@@ -108,8 +112,14 @@ OSStatus _LSLaunchApplication(CFURLRef appPath)
CFRelease(url_plus_exec);
url_plus_exec = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, pre, executable, FALSE);
CFRelease(pre);
- /* Launch the executable */
- executable = CFURLGetString(url_plus_exec);
+ /* Launch the executable. NB: CFURLGetString would give back a
+ * percent-encoded file:// URL, which is not spawnable; extract the
+ * POSIX path instead. */
+ CFRelease(executable);
+ executable = CFURLCopyFileSystemPath(url_plus_exec, kCFURLPOSIXPathStyle);
+ CFRelease(url_plus_exec);
+ if (executable == NULL)
+ return 1;
/* Try to use CFBundleName first */
if (bundleName == NULL)
@@ -122,15 +132,19 @@ OSStatus _LSLaunchApplication(CFURLRef appPath)
CFStringGetFileSystemRepresentation(executable, exec, execSize);
argv[0] = exec;
argv[1] = NULL;
- pid = spawn_via_launchd(name, (const char *const *)argv, NULL);
- if (pid < 0)
+ /* Launch the executable directly. The macOS-correct route is
+ * spawn_via_launchd (launchd job registration, -W waiting, PSN
+ * tracking), but Darling's vproc_mig_spawn2 currently fails the
+ * request (EIO) and nothing in-tree exercises it, so spawn the
+ * child ourselves via the emulated posix_spawn. */
+ int err = posix_spawn(&pid, exec, NULL, NULL, argv, environ);
+ if (err != 0)
{
- printf("%s: Failed to spawn via launchd\n", exec);
+ printf("%s: Failed to posix_spawn: %s\n", exec, strerror(err));
return 1;
}
free(name);
free(exec);
- CFRelease(url_plus_exec);
CFRelease(executable);
}
@@ -184,3 +198,25 @@ err_pre_info:
CFRelease(rs);
CFRelease(info_plist_url);
}
+
+OSStatus LSSetDefaultHandlerForURLScheme(CFStringRef inURLScheme, CFStringRef inHandlerBundleID)
+{
+ /* Darling has no shared LaunchServices database yet; accept and
+ * ignore (callers such as Discord's URL-scheme registration only
+ * need the call to succeed). */
+ (void)inURLScheme;
+ (void)inHandlerBundleID;
+ if (verbose)
+ printf("STUB: LSSetDefaultHandlerForURLScheme\n");
+ return noErr;
+}
+
+OSStatus LSSetDefaultRoleHandlerForContentType(CFStringRef inContentType, LSRolesMask inRole, CFStringRef inHandlerBundleID)
+{
+ (void)inContentType;
+ (void)inRole;
+ (void)inHandlerBundleID;
+ if (verbose)
+ printf("STUB: LSSetDefaultRoleHandlerForContentType\n");
+ return noErr;
+}
diff --git a/src/frameworks/CoreVideo/include/CoreVideo/CVBuffer.h b/src/frameworks/CoreVideo/include/CoreVideo/CVBuffer.h
index 09b70e4cb..2f7f5b89a 100644
--- a/src/frameworks/CoreVideo/include/CoreVideo/CVBuffer.h
+++ b/src/frameworks/CoreVideo/include/CoreVideo/CVBuffer.h
@@ -8,6 +8,8 @@ __BEGIN_DECLS
typedef struct CV_BRIDGED_TYPE(id) __CVBuffer* CVBufferRef;
+extern const CFStringRef kCVBufferPropagatedAttachmentsKey;
+
__END_DECLS
#endif // _COREVIDEO_CVBUFFER_H_
diff --git a/src/frameworks/CoreVideo/include/CoreVideo/CVImageBuffer.h b/src/frameworks/CoreVideo/include/CoreVideo/CVImageBuffer.h
index 937ba8312..45ab80e75 100644
--- a/src/frameworks/CoreVideo/include/CoreVideo/CVImageBuffer.h
+++ b/src/frameworks/CoreVideo/include/CoreVideo/CVImageBuffer.h
@@ -30,6 +30,7 @@ extern const CFStringRef kCVImageBufferTransferFunction_Linear;
extern const CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ;
extern const CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_428_1;
extern const CFStringRef kCVImageBufferTransferFunction_SMPTE_C;
+extern const CFStringRef kCVImageBufferTransferFunction_sRGB;
extern CFStringRef const kCVImageBufferPixelAspectRatioHorizontalSpacingKey;
extern CFStringRef const kCVImageBufferPixelAspectRatioVerticalSpacingKey;
@@ -43,5 +44,7 @@ extern CFStringRef const kCVImageBufferColorPrimaries_SMPTE_C;
extern CFStringRef const kCVImageBufferTransferFunction_ITU_R_709_2;
extern CFStringRef const kCVImageBufferTransferFunction_SMPTE_240M_1995;
extern CFStringRef const kCVImageBufferTransferFunction_UseGamma;
+extern const CFStringRef kCVImageBufferContentLightLevelInfoKey;
+extern const CFStringRef kCVImageBufferMasteringDisplayColorVolumeKey;
#endif // _COREVIDEO_CVIMAGEBUFFER_H_
diff --git a/src/frameworks/CoreVideo/include/CoreVideo/CVPixelBufferPool.h b/src/frameworks/CoreVideo/include/CoreVideo/CVPixelBufferPool.h
index 299caa5b1..bdff6028c 100644
--- a/src/frameworks/CoreVideo/include/CoreVideo/CVPixelBufferPool.h
+++ b/src/frameworks/CoreVideo/include/CoreVideo/CVPixelBufferPool.h
@@ -26,6 +26,7 @@
__BEGIN_DECLS
extern const CFStringRef kCVPixelBufferPoolMaximumBufferAgeKey;
+extern const CFStringRef kCVPixelBufferPoolAllocationThresholdKey;
extern const CFStringRef kCVPixelBufferPoolMinimumBufferCountKey;
__END_DECLS
diff --git a/src/frameworks/CoreVideo/src/CVDisplayLink.mm b/src/frameworks/CoreVideo/src/CVDisplayLink.mm
index 3e9cba226..99e0a5494 100644
--- a/src/frameworks/CoreVideo/src/CVDisplayLink.mm
+++ b/src/frameworks/CoreVideo/src/CVDisplayLink.mm
@@ -1,38 +1,878 @@
#include <CoreVideo/CVDisplayLink.h>
+#include <stdint.h>
+#include <string.h>
#include <memory>
+#include <map>
+#include <mutex>
+#include <pthread.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <mach/mach_time.h>
+#include <stdlib.h>
#import <AppKit/NSApplication.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSScreen.h>
#import <Foundation/NSDictionary.h>
+#import <dispatch/dispatch.h>
#include <CoreGraphics/CGWindow.h>
#include <CoreGraphics/CGDirectDisplay.h>
+/*
+ * M58b FrameSink bridge.
+ *
+ * Chromium 108's viz source does keep receiving the display ticks, but its
+ * CompositorFrameSinkSupport stops forwarding them after the first no-damage
+ * response. Under Darling the support object also crosses a guest-memory
+ * boundary before the timing-details map is serialized. When we deliberately
+ * keep the source alive, that stale map can otherwise reach Mojo and crash the
+ * GPU process. Keep this compatibility bridge here, beside the display pump,
+ * so it is installed in every Electron process that uses this shim.
+ *
+ * These are guarded offsets for Electron 22.3.27 / Chromium 108. Every patch
+ * checks the original bytes and fails closed when a different framework is
+ * loaded. The bridge is intentionally limited to the known framework image.
+ */
+extern "C" {
+int mprotect(void *, unsigned long, int);
+unsigned _dyld_image_count(void);
+const char *_dyld_get_image_name(unsigned);
+const char *_dyld_get_image_header(unsigned);
+}
+
+static const unsigned long kCFSSShouldSendVA = 0x64fb760UL;
+static const unsigned long kDirectClientOnBeginVA = 0x141bdc0UL;
+static const unsigned long kBundleClientOnBeginVA = 0x64fdfd0UL;
+static const unsigned long kRendererBindToClientVA = 0x5e21c10UL;
+static const unsigned long kRendererOnBeginVA = 0x5e23140UL;
+static const unsigned long kNullDetailsThunkVA = 0x64cde60UL;
+static const unsigned long kTimingCopyCallVA = 0x64cdd96UL;
+static const unsigned long kTimingDefaultCtorVA = 0x0eeb080UL;
+static const unsigned long kContextCopyCallVA = 0x64cdd76UL;
+static const unsigned long kContextDefaultCtorVA = 0x0ef1d20UL;
+static const unsigned long kInlineCopyCallVA = 0x64cdd86UL;
+static void frameSinkTrace(const char *tag, unsigned long value);
+
+static void (*gNullDetailsThunkOriginal)(void *, void *, void *) = nullptr;
+
+static void zeroInlineDetails(void *destination, const void *source) {
+ unsigned char zeros[0xa0] = {};
+ int fd = open("/Volumes/SystemRoot/proc/self/mem", O_WRONLY, 0);
+ if (fd >= 0) {
+ pwrite(fd, zeros, sizeof(zeros), (off_t)(unsigned long)destination);
+ close(fd);
+ }
+}
+
+static bool mappedRange(const void *pointer, size_t size) {
+ if (pointer == nullptr || size == 0)
+ return false;
+ unsigned long start = (unsigned long)pointer & ~0xfffUL;
+ unsigned long end = ((unsigned long)pointer + size + 0xfffUL) & ~0xfffUL;
+ for (unsigned long page = start; page < end; page += 0x1000) {
+ char resident = 0;
+ if (mincore((void *)page, 0x1000, &resident) != 0)
+ return false;
+ }
+ return true;
+}
+
+static void nullDetailsThunkGuard(void *block, void *arg, void *details) {
+ /* This thunk serializes compositor timing metadata. Under Darling its
+ * nested vectors refer to the producer's guest-memory view. Preserve the
+ * completion callback but substitute a coherent empty details payload. */
+ if (details == nullptr)
+ return;
+ gNullDetailsThunkOriginal(block, arg, details);
+}
+
+static bool installNullDetailsThunkGuard(unsigned char *base) {
+ unsigned char *target = base + kNullDetailsThunkVA;
+ static const unsigned char expected[15] = {
+ 0x55, 0x48, 0x89, 0xe5, 0x48, 0x89, 0xd0, 0x48,
+ 0x8b, 0x52, 0x28, 0x48, 0x8b, 0x48, 0x30
+ };
+ if (memcmp(target, expected, sizeof(expected)) != 0)
+ return false;
+ unsigned char *copyCall = base + kTimingCopyCallVA;
+ static const unsigned char expectedCopyCall[5] = {
+ 0xe8, 0x95, 0xd6, 0xa1, 0xfa
+ };
+ unsigned char *contextCall = base + kContextCopyCallVA;
+ unsigned char *inlineCall = base + kInlineCopyCallVA;
+ static const unsigned char expectedContextCall[5] = {
+ 0xe8, 0x05, 0x40, 0xa2, 0xfa
+ };
+ static const unsigned char expectedInlineCall[5] = {
+ 0xe8, 0x55, 0x9c, 0xa1, 0xfa
+ };
+ if (memcmp(copyCall, expectedCopyCall, sizeof(expectedCopyCall)) != 0)
+ return false;
+ if (memcmp(contextCall, expectedContextCall,
+ sizeof(expectedContextCall)) != 0)
+ return false;
+ if (memcmp(inlineCall, expectedInlineCall, sizeof(expectedInlineCall)) != 0)
+ return false;
+
+ unsigned char *trampoline = (unsigned char *)mmap(nullptr, 4096,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (trampoline == MAP_FAILED)
+ return false;
+ memcpy(trampoline, expected, sizeof(expected));
+ unsigned char *jump = trampoline + sizeof(expected);
+ jump[0] = 0x48;
+ jump[1] = 0xb8;
+ unsigned long resume = (unsigned long)(target + sizeof(expected));
+ memcpy(jump + 2, &resume, sizeof(resume));
+ jump[10] = 0xff;
+ jump[11] = 0xe0;
+ gNullDetailsThunkOriginal =
+ (void (*)(void *, void *, void *))trampoline;
+
+ unsigned long page = (unsigned long)target & ~0xfffUL;
+ if (mprotect((void *)page, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) != 0)
+ return false;
+ int32_t relativeCtor = (int32_t)(kTimingDefaultCtorVA -
+ (kTimingCopyCallVA + sizeof(expectedCopyCall)));
+ copyCall[0] = 0xe8;
+ memcpy(copyCall + 1, &relativeCtor, sizeof(relativeCtor));
+ int32_t relativeContextCtor = (int32_t)(kContextDefaultCtorVA -
+ (kContextCopyCallVA + sizeof(expectedContextCall)));
+ contextCall[0] = 0xe8;
+ memcpy(contextCall + 1, &relativeContextCtor,
+ sizeof(relativeContextCtor));
+ int64_t inlineDistance = (unsigned long)&zeroInlineDetails -
+ (unsigned long)(inlineCall + sizeof(expectedInlineCall));
+ if (inlineDistance < INT32_MIN || inlineDistance > INT32_MAX)
+ return false;
+ int32_t relativeInlineHelper = (int32_t)inlineDistance;
+ inlineCall[0] = 0xe8;
+ memcpy(inlineCall + 1, &relativeInlineHelper,
+ sizeof(relativeInlineHelper));
+ unsigned char detour[12] = { 0x48, 0xb8 };
+ unsigned long guard = (unsigned long)&nullDetailsThunkGuard;
+ memcpy(detour + 2, &guard, sizeof(guard));
+ detour[10] = 0xff;
+ detour[11] = 0xe0;
+ memcpy(target, detour, sizeof(detour));
+ memset(target + sizeof(detour), 0x90,
+ sizeof(expected) - sizeof(detour));
+ return true;
+}
+
+struct FrameSinkHook {
+ unsigned char *target;
+ unsigned char saved[12];
+ unsigned long replacement;
+ volatile int lock;
+ bool installed;
+};
+
+struct FrameSinkSavedState {
+ uint32_t magic;
+ unsigned char direct[12];
+ unsigned char bundle[12];
+};
+
+struct RendererHookSavedState {
+ uint32_t magic;
+ unsigned char saved[12];
+};
+
+struct RendererBeginState {
+ uint32_t magic;
+ uint32_t running;
+ unsigned long sink;
+ uint64_t sequence;
+};
+
+static const uint32_t kFrameSinkSavedMagic = 0x4d353846u;
+
+static FrameSinkHook gDirectClientHook = {};
+static FrameSinkHook gBundleClientHook = {};
+static FrameSinkHook gRendererNeedsHook = {};
+static unsigned char gEmptyFrameTimingDetails[24] = {};
+
+static const uint32_t kRendererHookMagic = 0x4d353853u;
+static const uint32_t kRendererBeginMagic = 0x4d353842u;
+
+static bool frameSinkPrologueIsUsable(const unsigned char *prologue);
+
+static void frameSinkTrace(const char *tag, unsigned long value) {
+ char line[96];
+ int len = snprintf(line, sizeof(line), "FS pid=%d %s %lx\n", (int)getpid(),
+ tag, value);
+ int fd = open("/Volumes/SystemRoot/tmp/opencode/framesink-bridge.log",
+ O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (fd >= 0) {
+ write(fd, line, (size_t)len);
+ close(fd);
+ }
+}
+
+static const char *rendererHookStatePath(void) {
+ static char path[128];
+ static bool initialized = false;
+ if (!initialized) {
+ snprintf(path, sizeof(path), "/dev/shm/darling-renderer-hook-%d.state",
+ (int)getpid());
+ initialized = true;
+ }
+ return path;
+}
+
+static const char *rendererBeginStatePath(void) {
+ static char path[128];
+ static bool initialized = false;
+ if (!initialized) {
+ snprintf(path, sizeof(path), "/dev/shm/darling-renderer-begin-%d.state",
+ (int)getpid());
+ initialized = true;
+ }
+ return path;
+}
+
+static bool rendererHookStateIsUsable(void) {
+ RendererHookSavedState state = {};
+ int fd = open(rendererHookStatePath(), O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+ ssize_t got = read(fd, &state, sizeof(state));
+ close(fd);
+ return got == (ssize_t)sizeof(state) &&
+ state.magic == kRendererHookMagic &&
+ frameSinkPrologueIsUsable(state.saved);
+}
+
+static void rendererWriteState(void *sink, bool running) {
+ RendererBeginState state = {};
+ int fd = open(rendererBeginStatePath(), O_RDWR | O_CREAT, 0644);
+ if (fd < 0)
+ return;
+ if (flock(fd, LOCK_EX) == 0) {
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, &state, sizeof(state)) != (ssize_t)sizeof(state) ||
+ state.magic != kRendererBeginMagic)
+ memset(&state, 0, sizeof(state));
+ state.magic = kRendererBeginMagic;
+ state.sink = (unsigned long)sink;
+ state.running = running ? 1 : 0;
+ lseek(fd, 0, SEEK_SET);
+ ftruncate(fd, 0);
+ write(fd, &state, sizeof(state));
+ flock(fd, LOCK_UN);
+ }
+ close(fd);
+}
+
+static bool rendererReadState(RendererBeginState *state) {
+ int fd = open(rendererBeginStatePath(), O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+ ssize_t got = read(fd, state, sizeof(*state));
+ close(fd);
+ return got == (ssize_t)sizeof(*state) &&
+ state->magic == kRendererBeginMagic && state->running != 0 &&
+ state->sink != 0;
+}
+
+static unsigned char *frameSinkFindFramework(void) {
+ for (unsigned i = 0; i < _dyld_image_count(); ++i) {
+ const char *name = _dyld_get_image_name(i);
+ if (name && strstr(name, "Versions/A/Electron Framework"))
+ return (unsigned char *) _dyld_get_image_header(i);
+ }
+ return nullptr;
+}
+
+static const char *frameSinkStatePath(void) {
+ static char path[128];
+ static bool initialized = false;
+ if (!initialized) {
+ snprintf(path, sizeof(path), "/dev/shm/darling-framesink-%d.state",
+ (int) getpid());
+ initialized = true;
+ }
+ return path;
+}
+
+static bool frameSinkReadSaved(FrameSinkSavedState *state) {
+ int fd = open(frameSinkStatePath(), 0, 0);
+ if (fd < 0)
+ return false;
+ ssize_t got = read(fd, state, sizeof(*state));
+ close(fd);
+ return got == (ssize_t) sizeof(*state) &&
+ state->magic == kFrameSinkSavedMagic;
+}
+
+static bool frameSinkStateIsUsable(void) {
+ FrameSinkSavedState state = {};
+ if (!frameSinkReadSaved(&state))
+ return false;
+ /* A record containing only the direct prologue is an old/partial record.
+ * PID reuse makes that case common: the first CoreVideo image wrote its
+ * direct hook, then another image observed the file and skipped the bundle
+ * hook. Never let a zero bundle prologue be used to restore executable
+ * code. */
+ for (unsigned i = 0; i < sizeof(state.direct); ++i)
+ if (state.direct[i] == 0)
+ return false;
+ for (unsigned i = 0; i < sizeof(state.bundle); ++i)
+ if (state.bundle[i] == 0)
+ return false;
+ return true;
+}
+
+static bool frameSinkPrologueIsUsable(const unsigned char *prologue) {
+ /* Both Chromium methods begin with a frame-pointer prologue; the remaining
+ * bytes are version-specific but must not be all zero. */
+ if (prologue[0] != 0x55 || prologue[1] != 0x48 ||
+ prologue[2] != 0x89 || prologue[3] != 0xe5)
+ return false;
+ for (unsigned i = 4; i < 12; ++i)
+ if (prologue[i] != 0)
+ return true;
+ return false;
+}
+
+static void frameSinkWriteJump(unsigned char *target, unsigned long replacement) {
+ unsigned char jump[12];
+ jump[0] = 0x48;
+ jump[1] = 0xb8;
+ for (unsigned i = 0; i < 8; ++i)
+ jump[i + 2] = (replacement >> (i * 8)) & 0xff;
+ jump[10] = 0xff;
+ jump[11] = 0xe0;
+ for (unsigned i = 0; i < sizeof(jump); ++i)
+ target[i] = jump[i];
+}
+
+static bool frameSinkPatchBytes(unsigned char *at, const unsigned char *want,
+ unsigned count, const unsigned char *replacement) {
+ for (unsigned i = 0; i < count; ++i)
+ if (at[i] != want[i])
+ return false;
+ unsigned long page = (unsigned long) at & ~0xfffUL;
+ if (mprotect((void *) page, 0x1000, 7) != 0)
+ return false;
+ for (unsigned i = 0; i < count; ++i)
+ at[i] = replacement[i];
+ return true;
+}
+
+static void frameSinkHookPatch(FrameSinkHook *hook, bool enabled) {
+ unsigned char jump[12];
+ unsigned long page = (unsigned long) hook->target & ~0xfffUL;
+ mprotect((void *) page, 0x1000, 7);
+ if (!enabled) {
+ for (unsigned i = 0; i < sizeof(hook->saved); ++i)
+ hook->target[i] = hook->saved[i];
+ return;
+ }
+ jump[0] = 0x48;
+ jump[1] = 0xb8;
+ for (unsigned i = 0; i < 8; ++i)
+ jump[i + 2] = (hook->replacement >> (i * 8)) & 0xff;
+ jump[10] = 0xff;
+ jump[11] = 0xe0;
+ for (unsigned i = 0; i < sizeof(jump); ++i)
+ hook->target[i] = jump[i];
+}
+
+static bool frameSinkInstallHook(FrameSinkHook *hook, unsigned char *target,
+ void *replacement) {
+ /* Do not overwrite an already-installed copy of this bridge. */
+ if (target[0] == 0x48 && target[1] == 0xb8)
+ return false;
+ /* All three Chromium functions have the normal x86_64 frame prologue. */
+ if (target[0] != 0x55 || target[1] != 0x48 || target[2] != 0x89 ||
+ target[3] != 0xe5)
+ return false;
+ hook->target = target;
+ hook->replacement = (unsigned long) replacement;
+ for (unsigned i = 0; i < sizeof(hook->saved); ++i)
+ hook->saved[i] = target[i];
+ frameSinkHookPatch(hook, true);
+ hook->installed = true;
+ return true;
+}
+
+static void frameSinkCallOriginal(unsigned long offset, unsigned long replacement,
+ bool bundle, void *self, void *args, void *details) {
+ /* Do not dereference FrameSinkHook or any other shim global here. This
+ * function is entered from Chromium's callback thread, whose guest view is
+ * not the constructor's view under Darling. */
+ unsigned char *base = frameSinkFindFramework();
+ FrameSinkSavedState saved = {};
+ int fd = open(frameSinkStatePath(), O_RDWR, 0);
+ if (fd < 0 || !base)
+ return;
+ if (flock(fd, LOCK_EX) != 0) {
+ close(fd);
+ return;
+ }
+ lseek(fd, 0, 0);
+ if (read(fd, &saved, sizeof(saved)) != (ssize_t) sizeof(saved) ||
+ saved.magic != kFrameSinkSavedMagic ||
+ !frameSinkPrologueIsUsable(bundle ? saved.bundle : saved.direct)) {
+ frameSinkTrace(bundle ? "bad-bundle-state" : "bad-direct-state",
+ (unsigned long)details);
+ flock(fd, LOCK_UN);
+ close(fd);
+ return;
+ }
+ unsigned char *target = base + offset;
+ const unsigned char *prologue = bundle ? saved.bundle : saved.direct;
+ unsigned long page = (unsigned long) target & ~0xfffUL;
+ if (mprotect((void *) page, 0x1000, 7) != 0) {
+ flock(fd, LOCK_UN);
+ close(fd);
+ return;
+ }
+ for (unsigned i = 0; i < 12; ++i)
+ target[i] = prologue[i];
+ ((void (*)(void *, void *, void *)) target)(self, args, details);
+ frameSinkWriteJump(target, replacement);
+ flock(fd, LOCK_UN);
+ close(fd);
+}
+
+static void frameSinkDirectOnBegin(void *self, void *args, void *details) {
+ frameSinkCallOriginal(kDirectClientOnBeginVA,
+ (unsigned long) frameSinkDirectOnBegin, false, self, args, details);
+}
+
+static void frameSinkBundleOnBegin(void *self, void *args, void *details) {
+ frameSinkCallOriginal(kBundleClientOnBeginVA,
+ (unsigned long) frameSinkBundleOnBegin, true, self, args, details);
+}
+
+static bool frameSinkRendererBindToClient(void *self, void *client) {
+ /* BindToClient runs in every renderer even when OnNeedsBeginFrames never
+ * does under Darling. Capturing here breaks that circular dependency. */
+ frameSinkTrace("renderer-bind-entry", (unsigned long)self);
+ rendererWriteState(self, true);
+ frameSinkTrace("renderer-bind-saved", (unsigned long)client);
+ unsigned char *base = frameSinkFindFramework();
+ RendererHookSavedState saved = {};
+ int fd = open(rendererHookStatePath(), O_RDWR, 0);
+ if (fd < 0 || !base)
+ return false;
+ if (flock(fd, LOCK_EX) != 0) {
+ close(fd);
+ return false;
+ }
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, &saved, sizeof(saved)) != (ssize_t)sizeof(saved) ||
+ saved.magic != kRendererHookMagic ||
+ !frameSinkPrologueIsUsable(saved.saved)) {
+ flock(fd, LOCK_UN);
+ close(fd);
+ return false;
+ }
+ unsigned char *target = base + kRendererBindToClientVA;
+ unsigned long page = (unsigned long)target & ~0xfffUL;
+ bool result = false;
+ if (mprotect((void *)page, 0x1000, 7) == 0) {
+ for (unsigned i = 0; i < sizeof(saved.saved); ++i)
+ target[i] = saved.saved[i];
+ frameSinkTrace("renderer-bind-call", (unsigned long)target);
+ result = ((bool (*)(void *, void *))target)(self, client);
+ frameSinkWriteJump(target,
+ (unsigned long)frameSinkRendererBindToClient);
+ }
+ flock(fd, LOCK_UN);
+ close(fd);
+ frameSinkTrace("renderer-bind", (unsigned long)self);
+ return result;
+}
+
+static void rendererBeginTimerF(CFRunLoopTimerRef timer, void *info) {
+ (void)timer;
+ (void)info;
+ RendererBeginState state = {};
+ if (!rendererReadState(&state))
+ return;
+ unsigned char *base = frameSinkFindFramework();
+ if (!base)
+ return;
+
+ struct mach_timebase_info timebase = {};
+ mach_timebase_info(&timebase);
+ uint64_t denominator = timebase.denom ? timebase.denom : 1;
+ uint64_t nowUs = mach_absolute_time() * (uint64_t)timebase.numer /
+ denominator / 1000ULL;
+ uint64_t sequence = ++state.sequence;
+
+ /* Chromium 108 release BeginFrameArgs: frame/deadline/interval,
+ * BeginFrameId, trace id, type and critical-path flag. */
+ unsigned char args[128] = {};
+ *(uint64_t *)(args + 0) = nowUs;
+ *(uint64_t *)(args + 8) = nowUs + 16666ULL;
+ *(uint64_t *)(args + 16) = 16666ULL;
+ *(uint64_t *)(args + 24) = 0xffffffffULL;
+ *(uint64_t *)(args + 32) = sequence;
+ *(int64_t *)(args + 40) = -1;
+ *(uint32_t *)(args + 48) = 1;
+ args[52] = 1;
+ unsigned char details[24] = {};
+
+ /* Save the sequence before entering Chromium; OnBeginFrame can re-enter
+ * the needs callback and rewrite this same host record. */
+ int fd = open(rendererBeginStatePath(), O_WRONLY | O_TRUNC, 0644);
+ if (fd >= 0) {
+ write(fd, &state, sizeof(state));
+ close(fd);
+ }
+ ((void (*)(void *, void *, void *))(base + kRendererOnBeginVA))(
+ (void *)state.sink, args, details);
+ if (sequence <= 8)
+ frameSinkTrace("renderer-pump", sequence);
+}
+
+static void startRendererBeginTimer(void) {
+ CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL,
+ CFAbsoluteTimeGetCurrent() + 2.0, 1.0 / 60.0, 0, 0,
+ rendererBeginTimerF, NULL);
+ if (timer)
+ CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
+}
+
+static void installFrameSinkBridge(void) {
+ /* Private Chromium callbacks cannot safely be invoked across Darling's
+ * divergent guest-memory views. Keep all experimental detours disabled. */
+ return;
+
+ for (unsigned i = 0; i < _dyld_image_count(); ++i) {
+ const char *name = _dyld_get_image_name(i);
+ if (!name || !strstr(name, "Versions/A/Electron Framework"))
+ continue;
+ unsigned char *base = (unsigned char *) _dyld_get_image_header(i);
+ bool nullGuardInstalled = installNullDetailsThunkGuard(base);
+ frameSinkTrace("null-guard", nullGuardInstalled ? 1UL : 0UL);
+ /* All earlier FrameSink experiments remain disabled. */
+ return;
+ unsigned char *shouldSend = base + kCFSSShouldSendVA;
+ unsigned char shouldSendWant[] = {
+ 0x55, 0x48, 0x89, 0xe5, 0x41, 0x57
+ };
+ unsigned char shouldSendReplacement[] = {
+ 0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3
+ };
+ /* Returning true keeps viz from treating a no-damage reply as a reason
+ * to stop the renderer's begin-frame stream. */
+ frameSinkPatchBytes(shouldSend, shouldSendWant, sizeof(shouldSendWant),
+ shouldSendReplacement);
+ /* Do not detour live Chromium callback entries here. A 12-byte absolute
+ * jump cannot be installed atomically and raced BrowserWindow startup.
+ * The guarded ShouldSend patch above is complete before these callbacks
+ * are active and is the only safe FrameSink compatibility change. */
+ bool directInstalled = false;
+ bool bundleInstalled = false;
+ bool rendererNeedsInstalled = false;
+ frameSinkTrace("install", (directInstalled ? 1UL : 0UL) |
+ (bundleInstalled ? 2UL : 0UL) |
+ (rendererNeedsInstalled ? 4UL : 0UL));
+ /* A second CoreVideo image may run this constructor after the first
+ * image has already patched Chromium. Its local hook structs are empty;
+ * preserve the first image's host-file prologue in that case. */
+ if (!frameSinkStateIsUsable() && directInstalled && bundleInstalled) {
+ FrameSinkSavedState saved = {};
+ saved.magic = kFrameSinkSavedMagic;
+ memcpy(saved.direct, gDirectClientHook.saved, sizeof(saved.direct));
+ memcpy(saved.bundle, gBundleClientHook.saved, sizeof(saved.bundle));
+ int fd = open(frameSinkStatePath(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd >= 0) {
+ if (flock(fd, LOCK_EX) == 0) {
+ write(fd, &saved, sizeof(saved));
+ flock(fd, LOCK_UN);
+ }
+ close(fd);
+ }
+ }
+ if (!rendererHookStateIsUsable() && rendererNeedsInstalled) {
+ RendererHookSavedState saved = {};
+ saved.magic = kRendererHookMagic;
+ memcpy(saved.saved, gRendererNeedsHook.saved, sizeof(saved.saved));
+ int fd = open(rendererHookStatePath(),
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd >= 0) {
+ write(fd, &saved, sizeof(saved));
+ close(fd);
+ }
+ }
+ if (rendererNeedsInstalled)
+ startRendererBeginTimer();
+ return;
+ }
+}
+
+__attribute__((constructor)) static void installFrameSinkBridgeAtLoad(void) {
+ installFrameSinkBridge();
+}
+
static const NSString* kDirectDisplayArray = @"CGDirectDisplay";
+/*
+ * darling-lol M58b final pump design: host-file-backed display-link state.
+ *
+ * Diagnosis this session: under Darling, guest helper threads (dispatch
+ * sources, detached pthreads, CFRunLoop timers) do not reliably observe
+ * writes made elsewhere in the guest — globals, dictionary contents and
+ * even objc associated-object storage diverge for the timer's thread, and
+ * a detached pthread sleep loop hangs inside its FIRST sleep. The one
+ * store proven to round-trip across processes AND across the shim's two
+ * loaded instances is the HOST filesystem (/dev/shm via shm_open, used by
+ * the M58b IOSurface work, and fileports via the M58 kobject machinery).
+ *
+ * So the shim keeps NO guest-side state. The display link's state lives in
+ * a per-pid host file; Create/Start/SetOutputCallback/Stop are trivial side
+ * effects on that file; the runloop timer fires every 1/60 s and reads the
+ * file fresh each tick, invoking Chromium's callback with a plausible
+ * CVTimeStamp. The timer itself fires (verified: continuous ~60 Hz records).
+ */
+
+#include <CoreVideo/CVDisplayLink.h>
+#include <memory>
+#include <map>
+#include <mutex>
+#include <pthread.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <mach/mach_time.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdio.h>
+#include <string.h>
+#import <AppKit/NSApplication.h>
+#import <AppKit/NSWindow.h>
+#import <AppKit/NSScreen.h>
+#import <Foundation/NSDictionary.h>
+#include <CoreGraphics/CGWindow.h>
+#include <CoreGraphics/CGDirectDisplay.h>
+
+
+/* sturdy host-shared record, written fresh on every mutation. */
+struct CVLinkStateRec {
+ uint32_t magic; /* 0xC81DFA10 */
+ uint32_t version;
+ void* callback;
+ void* userInfo;
+ uint32_t running;
+ uint32_t alive;
+ uint32_t ticks;
+ uint32_t nullTicks;
+ /* darling-lol M58e: number of CVDisplayLinkRef objects alive in this
+ * process. The pump must keep ticking while >= 1 link exists, and MUST
+ * stop invoking the callback after the last link is released — the
+ * callback targets Chromium's DisplayLinkMac, which unregisters its
+ * callback by releasing the link (calling it afterwards would be
+ * use-after-free every tick). */
+ uint32_t refs;
+ uint64_t reserved;
+ CVDisplayLinkRef link;
+};
+static const uint32_t kStateMagic = 0xC81DFA10u;
+static const uint32_t kStateVersion = 1;
+
+static void traceLog(const char* fmt, uint64_t a, uint64_t b) {
+ if (!getenv("RCLOL_CV_TRACE")) return;
+ char msg[192];
+ struct timespec ts;
+#ifdef CLOCK_MONOTONIC
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+#else
+ ts.tv_sec = (time_t) mach_absolute_time() / 1000000000ull;
+ ts.tv_nsec = 0;
+#endif
+ snprintf(msg, sizeof(msg), "%lld.%03ld %d %s",
+ (long long) ts.tv_sec, ts.tv_nsec / 1000000,
+ (int) getpid(), /* msg below */ "");
+ snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), fmt, a, b);
+ int fd = open("/Volumes/SystemRoot/tmp/opencode/cvpump.log",
+ 1 | 0x200 | 8, 0644);
+ if (fd >= 0) { write(fd, msg, strlen(msg)); close(fd); }
+}
+
+static const char* statePathFor(void) {
+ static char path[128];
+ static bool init = false;
+ if (!init) {
+ snprintf(path, sizeof(path), "/dev/shm/darling-cvlink-%d.state",
+ (int) getpid());
+ init = true;
+ }
+ return path;
+}
+
+/* These private records live in the host /dev/shm. Bypass recursive guest
+ * path expansion on Chromium's small callback stacks, and avoid frame-time RPC. */
+extern "C" long __lolsys_syscall(long,long,long,long,long,long,int);
+static int clockOpen(void) { return (int)__lolsys_syscall((long)statePathFor(),66,0600,0,0,0,2); }
+static void clockClose(int fd) { __lolsys_syscall(fd,0,0,0,0,0,3); }
+static long clockRead(int fd,void *p,size_t n,off_t offset) { return __lolsys_syscall(fd,(long)p,n,offset,0,0,17); }
+static long clockWrite(int fd,const void *p,size_t n,off_t offset) { return __lolsys_syscall(fd,(long)p,n,offset,0,0,18); }
+static int clockFlock(int fd,int op) { return (int)__lolsys_syscall(fd,op,0,0,0,0,73); }
+static bool readState(CVLinkStateRec *out) {
+ int fd=clockOpen();if(fd<0)return false;
+ long got=clockRead(fd,out,sizeof(*out),0);clockClose(fd);
+ return got==sizeof(*out) && out->magic==kStateMagic && out->version>=1 && out->alive;
+}
+static bool writeState(CVLinkStateRec *rec) {
+ int fd=clockOpen();if(fd<0)return false;
+ long put=clockWrite(fd,rec,sizeof(*rec),0);clockClose(fd);return put==sizeof(*rec);
+}
+struct CVStateGuard {
+ int fd; bool locked;
+ CVStateGuard() : fd(clockOpen()), locked(false) {
+ if(fd<0)return;
+ uint64_t owner=0;clockRead(fd,&owner,sizeof(owner),offsetof(CVLinkStateRec,reserved));
+ uint64_t thread=(uintptr_t)pthread_self();
+ if(!owner || owner!=thread)locked=clockFlock(fd,LOCK_EX)==0;
+ }
+ ~CVStateGuard(){if(fd>=0){if(locked)clockFlock(fd,LOCK_UN);clockClose(fd);}}
+};
+__attribute__((constructor)) static void initializeDisplayClockRecord(void) {
+ CVLinkStateRec rec={};rec.magic=kStateMagic;rec.version=kStateVersion;rec.alive=1;writeState(&rec);
+}
+static void registerDisplayLink(CVDisplayLinkRef link) {
+ CVStateGuard guard;CVLinkStateRec rec={};
+ if(readState(&rec)){++rec.refs;if(!rec.callback)rec.link=link;writeState(&rec);}
+}
+
+static const char* displayLinkTick =
+ "displayLinkTick"; /* marker so grep can find the tick body */
+
+static void displayLinkTickFromRec(CVLinkStateRec* rec) {
+ CVDisplayLinkOutputCallback callback = (CVDisplayLinkOutputCallback) rec->callback;
+ if (traceLog != NULL) {
+ /* darling-lol M58d: full continuity logging while the begin-frame
+ * stall is under diagnosis (previously capped at 32 records). */
+ traceLog("PUMP cb=%p ticks=%llu\n", (uint64_t) callback, rec->ticks);
+ }
+ if (callback != NULL && rec->running && rec->refs) {
+ rec->ticks++;
+ struct mach_timebase_info timebase;
+ mach_timebase_info(&timebase);
+ uint64_t periodNs = 1000000000ull / 60ull;
+ uint64_t periodMach = periodNs * timebase.denom / timebase.numer;
+
+ uint64_t hostTime = mach_absolute_time();
+ CVTimeStamp now = {};
+ now.version = 0;
+ now.hostTime = hostTime;
+ now.videoTime = hostTime / periodMach;
+ now.videoTimeScale = 60;
+ now.videoRefreshPeriod = 1;
+ now.flags = kCVTimeStampHostTimeValid | kCVTimeStampVideoTimeValid |
+ kCVTimeStampVideoRefreshPeriodValid;
+ CVTimeStamp outputTime = now;
+ outputTime.hostTime = hostTime + periodMach;
+ outputTime.videoTime = now.videoTime + 1;
+ CVOptionFlags flagsOut = 0;
+ callback(rec->link, &now, &outputTime, 0, &flagsOut, rec->userInfo);
+ } else {
+ rec->nullTicks++;
+ }
+}
+
+/* The runloop timer's handler: re-read the state file fresh each tick. */
+static void DisplayLinkMainTimerF(CFRunLoopTimerRef timer, void* info) {
+ CVLinkStateRec rec;
+ if (!readState(&rec))
+ return;
+ displayLinkTickFromRec(&rec);
+ writeState(&rec);
+}
+
+/* darling-lol M58d: dispatch-source pump.
+ *
+ * Diagnosis this session: the runloop-timer pump only ever fires in the
+ * browser process (12/s measured) — the GPU process has NO CFRunLoop default
+ * mode traffic: PopupMenu/ChatWindow wait, Chromium's GPU-process message
+ * loop does not service the Cocoa default runloop constantly, so the CFRunLoop
+ * timer never fires there and viz *never sends a single begin frame* — the
+ * compositor surfaces are created but never drawn (a 1-publish artwork per
+ * run collapses). Per the M58b finding, dispatch sources DO fire from any
+ * thread; the handler just couldn't see guest memory. This shim already keeps
+ * its state in a HOST FILE (/dev/shm/darling-cvlink-<pid>.state) — the same
+ * store that survived everything else — so a dispatch-source timer can safely
+ * re-read it and invoke the callback.
+ */
+static void dispatchTimerFire(void* /*info*/) {
+ CVStateGuard guard;CVLinkStateRec rec;
+ if (guard.fd<0 || !readState(&rec)) return;
+ uint64_t owner=(uintptr_t)pthread_self();
+ clockWrite(guard.fd,&owner,sizeof(owner),offsetof(CVLinkStateRec,reserved));
+ displayLinkTickFromRec(&rec);
+ /* Preserve concurrent/reentrant registration changes; update only counters. */
+ clockWrite(guard.fd,&rec.ticks,sizeof(rec.ticks)+sizeof(rec.nullTicks),offsetof(CVLinkStateRec,ticks));
+ owner=0;clockWrite(guard.fd,&owner,sizeof(owner),offsetof(CVLinkStateRec,reserved));
+}
+
+/* Keep callbacks off Darling's dispatch trampoline: Chromium can leave its
+ * emulated workqueue with less than a page of usable stack. */
+static void *displayClockThread(void *) {
+ struct { long sec, nsec; } deadline;
+ if (__lolsys_syscall(1,(long)&deadline,0,0,0,0,228)<0) return NULL;
+ for (;;) {
+ deadline.nsec += NSEC_PER_SEC / 60;
+ if(deadline.nsec>=NSEC_PER_SEC){deadline.nsec-=NSEC_PER_SEC;++deadline.sec;}
+ long result;
+ do { result=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230); } while(result==-4);
+ if(result<0) return NULL;
+ dispatchTimerFire(NULL);
+ struct { long sec, nsec; } now;
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0 &&
+ (now.sec>deadline.sec || (now.sec==deadline.sec && now.nsec-deadline.nsec>NSEC_PER_SEC/60))) {
+ deadline.sec=now.sec;deadline.nsec=now.nsec;
+ }
+ }
+}
+static void InstallDispatchPump(void) {
+ pthread_attr_t attr;pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr,1024*1024);
+ pthread_t thread;
+ if(pthread_create(&thread,&attr,displayClockThread,NULL)==0)pthread_detach(thread);
+ pthread_attr_destroy(&attr);
+}
+
CVReturn CVDisplayLinkCreateWithActiveCGDisplays(CVDisplayLinkRef* displayLinkOut)
{
- uint32_t displayCount;
+ uint32_t displayCount = 0;
std::unique_ptr<CGDirectDisplayID[]> displays;
+ /* darling-lol M57: tolerate a missing/incomplete display list instead of
+ * failing (Chromium dereferences a null link when this errors). */
CGError err = CGGetActiveDisplayList(0, nullptr, &displayCount);
if (err != kCGErrorSuccess)
- return err;
+ displayCount = 0;
- displays.reset(new CGDirectDisplayID[displayCount]);
+ if (displayCount > 0) {
+ displays.reset(new CGDirectDisplayID[displayCount]);
+ err = CGGetActiveDisplayList(displayCount, displays.get(), &displayCount);
+ if (err != kCGErrorSuccess)
+ displayCount = 0;
+ }
- err = CGGetActiveDisplayList(displayCount, displays.get(), &displayCount);
- if (err != kCGErrorSuccess)
- return err;
-
NSMutableDictionary* self = [[NSMutableDictionary alloc] init];
NSMutableArray* array = [NSMutableArray arrayWithCapacity: displayCount];
- for (int i = 0; i < displayCount; i++)
+ for (uint32_t i = 0; i < displayCount; i++)
[array addObject: [NSNumber numberWithInt: displays[i]]];
+ /* darling-lol M57: ensure at least one display id so
+ * CVDisplayLinkGetCurrentCGDisplay() is non-zero (Chromium's
+ * DisplayLinkMac::GetForDisplay rejects a zero current display). */
+ if ([array count] == 0)
+ [array addObject: [NSNumber numberWithInt: 1]];
+
[self setObject: array
forKey: kDirectDisplayArray];
+ registerDisplayLink((CVDisplayLinkRef)self);
*displayLinkOut = (CVDisplayLinkRef) self;
return kCVReturnSuccess;
}
@@ -41,35 +881,128 @@ CVReturn CVDisplayLinkStart(CVDisplayLinkRef displayLink)
{
if (!displayLink)
return kCVReturnInvalidArgument;
- // TODO
+ CVStateGuard guard;
+
+ /* Install in every process that loads CoreVideo. The renderer may not
+ * create a CVDisplayLink of its own, so CVDisplayLinkStart is too late to
+ * be the only installation point. */
+ installFrameSinkBridge();
+
+ /* darling-lol M58b: mark the shared state file running; the runloop
+ * timer is installed once per process (the guard `started` uses a
+ * static flag rather than per-link state, since state lives in the
+ * file, not in guest memory). */
+ static bool started = false;
+ if (!started) {
+ CVLinkStateRec rec;
+ if(!readState(&rec)) memset(&rec,0,sizeof(rec));
+ rec.magic = kStateMagic;
+ rec.version = kStateVersion;
+ rec.alive = 1;
+ rec.running = 1;
+ writeState(&rec);
+
+ /* darling-lol M58d: dispatch-source pump, one per process.
+ * The old CFRunLoop timer only serviced the browser's main
+ * runloop (~12/s) and NEVER fired in the GPU process (no begin
+ * frames from viz), so frame production starved
+ * nondeterministically. Dispatch sources fire from any thread
+ * and this shim's state lives in a host file, which is exactly
+ * the store proven to cross the helper-thread visibility gap. */
+ InstallDispatchPump();
+ started = true;
+ } else {
+ CVLinkStateRec rec;
+ if (readState(&rec)) {
+ rec.running = 1;
+ rec.alive = 1;
+ writeState(&rec);
+ }
+ }
return kCVReturnSuccess;
}
CVReturn CVDisplayLinkStop(CVDisplayLinkRef displayLink)
{
- if (!displayLink)
- return kCVReturnInvalidArgument;
- // TODO
- return kCVReturnSuccess;
+ if(!displayLink)return kCVReturnInvalidArgument;
+ CVStateGuard guard;CVLinkStateRec rec;
+ if(readState(&rec)) { rec.running=0;writeState(&rec); }
+ return kCVReturnSuccess;
}
Boolean CVDisplayLinkIsRunning(CVDisplayLinkRef displayLink)
{
- if (!displayLink)
+ if(!displayLink)return false;
+ CVLinkStateRec rec;
+ if (!readState(&rec))
return false;
- // TODO
- return true;
+ return rec.running != 0;
+}
+
+CVDisplayLinkRef CVDisplayLinkRetain(CVDisplayLinkRef displayLink)
+{
+ /* darling-lol M57: Chromium's DisplayLinkMac retains/releases the link. */
+ if (displayLink) {
+ CVStateGuard guard;
+ [(NSMutableDictionary*) displayLink retain];
+ /* darling-lol M58e: bump the shared refs count so the pump keeps
+ * firing while any link exists. */
+ CVLinkStateRec rec;
+ if (readState(&rec)) {
+ rec.refs++;
+ rec.alive = 1;
+ writeState(&rec);
+ }
+ }
+ return displayLink;
}
void CVDisplayLinkRelease(CVDisplayLinkRef displayLink)
{
- NSMutableDictionary* self = (NSMutableDictionary*) displayLink;
- [self release];
+ if (!displayLink)
+ return;
+ CVStateGuard guard;
+ /* darling-lol M58e: drop the shared refs count. When the last link of
+ * this process goes away, drop the callback registration so the leaked
+ * pump timer stops invoking a freed Chromium callback (use-after-free
+ * guard). */
+ CVLinkStateRec rec;
+ if (readState(&rec)) {
+ if (rec.refs > 0)
+ rec.refs--;
+ if (rec.refs == 0 || (rec.link==displayLink && [(NSMutableDictionary*)displayLink retainCount]==1)) {
+ rec.callback = NULL;
+ rec.userInfo = NULL;
+ rec.link=NULL;
+ }
+ writeState(&rec);
+ }
+ [(NSMutableDictionary*) displayLink release];
}
+/* Creation, retain and release maintain the live link count. Stop cancels
+ * callbacks; callback-side Stop is reentrant, while outside Stop/Release wait
+ * for the active callback before clearing or destroying its registration. */
+
CVReturn CVDisplayLinkSetOutputCallback(CVDisplayLinkRef displayLink, CVDisplayLinkOutputCallback callback, void *userInfo)
{
- // TODO
+ if (!displayLink)
+ return kCVReturnInvalidArgument;
+
+ CVStateGuard guard;
+ CVLinkStateRec rec;
+ if (!readState(&rec)) {
+ memset(&rec, 0, sizeof(rec));
+ rec.magic = kStateMagic;
+ rec.version = kStateVersion;
+ rec.refs = 1;
+ rec.alive = 1;
+ }
+ rec.link=displayLink;
+ rec.callback = (void*) callback;
+ rec.userInfo = userInfo;
+ writeState(&rec);
+ traceLog("SETOUT cb=%p pid_ok\n", (uint64_t) callback, 0);
return kCVReturnSuccess;
}
@@ -91,7 +1024,7 @@ CVReturn CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(CVDisplayLinkRef disp
CGDirectDisplayID displayID = [window.screen cgDirectDisplayID];
NSMutableDictionary* self = (NSMutableDictionary*) displayLink;
- [self setObject: @[[NSNumber numberWithInt: displayID]]
+ [self setObject: @[[NSNumber numberWithInt: (int)(displayID != 0 ? displayID : 1)]]
forKey: kDirectDisplayArray];
return kCVReturnSuccess;
}
@@ -117,14 +1050,31 @@ CVReturn CVDisplayLinkCreateWithCGDisplay(
CGDirectDisplayID displayID,
CV_RETURNS_RETAINED_PARAMETER CVDisplayLinkRef CV_NULLABLE * CV_NONNULL displayLinkOut )
{
- if (displayID == kCGNullDirectDisplay)
+ /* darling-lol M57: Chromium's NativeWidgetMacNSWindowHost aborts (and then
+ * crashes on the null link) when this returns non-success; Darling's
+ * CGDirectDisplay ids are unreliable. Always hand back a valid object, and
+ * make sure the current display is non-zero (DisplayLinkMac::GetForDisplay
+ * rejects a zero current display). */
+ if (displayLinkOut == nullptr)
return kCVReturnInvalidArgument;
NSMutableDictionary* self = [[NSMutableDictionary alloc] init];
- [self setObject: @[ [NSNumber numberWithInt: displayID] ]
- forKey: @"CGDirectDisplay"];
+ [self setObject: @[ [NSNumber numberWithInt: (int)(displayID != 0 ? displayID : 1) ] ]
+ forKey: kDirectDisplayArray];
+ /* Optional creation diagnostics. Creation leaves the link stopped. */
+ if (getenv("RCLOL_CV_TRACE")) {
+ int fd = open("/Volumes/SystemRoot/tmp/opencode/cvpump.log", 1 | 0x200 | 8, 0644);
+ if (fd >= 0) {
+ char msg[64];
+ snprintf(msg, sizeof(msg), "CREATE_WITH_CG_DISPLAY id=%d\n", (int)(displayID));
+ write(fd, msg, strlen(msg));
+ close(fd);
+ }
+ }
+ registerDisplayLink((CVDisplayLinkRef)self);
*displayLinkOut = (CVDisplayLinkRef) self;
+
return kCVReturnSuccess;
}
diff --git a/src/frameworks/CoreVideo/src/constants.c b/src/frameworks/CoreVideo/src/constants.c
index 02f06704e..5d4fd4b2d 100644
--- a/src/frameworks/CoreVideo/src/constants.c
+++ b/src/frameworks/CoreVideo/src/constants.c
@@ -42,6 +42,11 @@ const CFStringRef kCVImageBufferTransferFunction_Linear = CFSTR("Linear");
const CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ = CFSTR("SMPTE_ST_2084_PQ");
const CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_428_1 = CFSTR("SMPTE_ST_428_1");
const CFStringRef kCVImageBufferTransferFunction_SMPTE_C = CFSTR("SMPTE_C");
+const CFStringRef kCVImageBufferTransferFunction_sRGB = CFSTR("sRGB");
+const CFStringRef kCVBufferPropagatedAttachmentsKey = CFSTR("CVBufferPropagatedAttachments");
+const CFStringRef kCVImageBufferContentLightLevelInfoKey = CFSTR("ContentLightLevelInfo");
+const CFStringRef kCVImageBufferMasteringDisplayColorVolumeKey = CFSTR("MasteringDisplayColorVolume");
+const CFStringRef kCVPixelBufferPoolAllocationThresholdKey = CFSTR("AllocationThreshold");
const CFStringRef kCVPixelBufferIOSurfacePropertiesKey = CFSTR("IOSurfaceProperties");
const CFStringRef kCVPixelBufferOpenGLCompatibilityKey = CFSTR("OpenGLCompatibility");
diff --git a/src/frameworks/CryptoTokenKit/CMakeLists.txt b/src/frameworks/CryptoTokenKit/CMakeLists.txt
index 6260e9b35..c02096039 100644
--- a/src/frameworks/CryptoTokenKit/CMakeLists.txt
+++ b/src/frameworks/CryptoTokenKit/CMakeLists.txt
@@ -30,8 +30,10 @@ add_framework(CryptoTokenKit
src/ctkclient.c
src/ctkclient_test.c
src/ctkloginhelper.c
+ src/TKTokenWatcher.m
DEPENDENCIES
system
CoreFoundation
+ Foundation
)
diff --git a/src/frameworks/CryptoTokenKit/include/CryptoTokenKit/TKTokenWatcher.h b/src/frameworks/CryptoTokenKit/include/CryptoTokenKit/TKTokenWatcher.h
index d7ad1ed23..02230985d 100755
--- a/src/frameworks/CryptoTokenKit/include/CryptoTokenKit/TKTokenWatcher.h
+++ b/src/frameworks/CryptoTokenKit/include/CryptoTokenKit/TKTokenWatcher.h
@@ -2,6 +2,10 @@
Image: /System/Library/PrivateFrameworks/CryptoTokenKit.framework/CryptoTokenKit
*/
+#import <Foundation/Foundation.h>
+
+@protocol TKProtocolTokenWatcher;
+
@interface TKTokenWatcher : NSObject <TKProtocolTokenWatcher> {
NSXPCConnection * _connection;
id /* block */ _insertionHandler;
@@ -12,7 +16,6 @@
@property (readonly) NSArray *tokenIDs;
-- (void).cxx_destruct;
- (void)addRemovalHandler:(id /* block */)arg1 forTokenID:(id)arg2;
- (void)dealloc;
- (id)init;
diff --git a/src/frameworks/DiskArbitration/DADisk.c b/src/frameworks/DiskArbitration/DADisk.c
index 83f1793b8..acc09c088 100644
--- a/src/frameworks/DiskArbitration/DADisk.c
+++ b/src/frameworks/DiskArbitration/DADisk.c
@@ -82,6 +82,41 @@ DADiskInitialize (void)
_kDADiskTypeID = _CFRuntimeRegisterClass (&DADiskClass);
}
+CFArrayRef kDADiskDescriptionWatchVolumePath = NULL;
+CFArrayRef kDADiskDescriptionWatchVolumeName = NULL;
+CFDictionaryRef kDADiskDescriptionMatchMediaWhole = NULL;
+CFDictionaryRef kDADiskDescriptionMatchMediaUnformatted = NULL;
+CFDictionaryRef kDADiskDescriptionMatchVolumeMountable = NULL;
+CFDictionaryRef kDADiskDescriptionMatchVolumeUnrecognized = NULL;
+
+__attribute__((constructor))
+static void
+DADiskInitializeWatchArrays (void)
+{
+ const void *pathKeys[] = { kDADiskDescriptionVolumePathKey };
+ const void *nameKeys[] = { kDADiskDescriptionVolumeNameKey };
+ kDADiskDescriptionWatchVolumePath = CFArrayCreate(NULL, pathKeys, 1,
+ &kCFTypeArrayCallBacks);
+ kDADiskDescriptionWatchVolumeName = CFArrayCreate(NULL, nameKeys, 1,
+ &kCFTypeArrayCallBacks);
+ kDADiskDescriptionMatchMediaWhole = CFDictionaryCreate(NULL,
+ (const void **)&kDADiskDescriptionMediaWholeKey,
+ (const void **)&kCFBooleanTrue, 1,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ kDADiskDescriptionMatchMediaUnformatted = CFDictionaryCreate(NULL,
+ (const void **)&kDADiskDescriptionMediaContentKey,
+ (const void **)&kCFBooleanTrue, 1,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ kDADiskDescriptionMatchVolumeMountable = CFDictionaryCreate(NULL,
+ (const void **)&kDADiskDescriptionVolumeMountableKey,
+ (const void **)&kCFBooleanTrue, 1,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ kDADiskDescriptionMatchVolumeUnrecognized = CFDictionaryCreate(NULL,
+ (const void **)&kDADiskDescriptionVolumeKindKey,
+ (const void **)&kCFBooleanTrue, 1,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+}
+
#define DADISK_SIZE sizeof(struct __DADisk) - sizeof(CFRuntimeBase)
static void
diff --git a/src/frameworks/IOSurface/include/IOSurface/IOSurfacePriv.h b/src/frameworks/IOSurface/include/IOSurface/IOSurfacePriv.h
index d8aed37f1..dd6266cad 100644
--- a/src/frameworks/IOSurface/include/IOSurface/IOSurfacePriv.h
+++ b/src/frameworks/IOSurface/include/IOSurface/IOSurfacePriv.h
@@ -85,4 +85,43 @@ struct _IOSurfaceLockUnlock
uint32_t options;
};
+/* darling-lol M58b: cross-process identity for user-space IOSurfaces.
+ *
+ * Darling has no IOSurfaceRoot IOKit service, so IOSurfaces are user-space
+ * allocations. To let Chromium hand a compositor frame's surface between
+ * processes (GPU -> browser via a real Mach port right, required by
+ * ui/gfx/mac/io_surface.cc's mach_port_mod_refs checks), a fallback surface's
+ * backing is a host shared-memory file (/dev/shm with the dumbest portable
+ * call, shm_open) laid out as:
+ *
+ * offset 0 : struct _IOSurfaceWireHeader
+ * offset IOSURFACE_WIRE_DATA_OFFSET : plane 0 pixel data
+ *
+ * IOSurfaceCreateMachPort() wraps the backing fd in a fileport (see
+ * darlingserver fileport.c), producing a real Mach send right that rides a
+ * Mach message. IOSurfaceLookupFromMachPort() dups the fd back
+ * (fileport_makefd), maps it MAP_SHARED, and rebuilds the IOSurface object
+ * from the wire header. Processes share the *same memory*, so frame updates
+ * are live. */
+
+#define IOSURFACE_WIRE_MAGIC 0x46535349u /* 'ISMF' */
+#define IOSURFACE_WIRE_VERSION 2u
+#define IOSURFACE_WIRE_DATA_OFFSET 4096u
+
+struct _IOSurfaceWireHeader
+{
+ uint32_t magic; /* IOSURFACE_WIRE_MAGIC */
+ uint32_t version; /* IOSURFACE_WIRE_VERSION */
+ uint32_t surfaceID;
+ uint32_t pixelFormat;
+ uint32_t planeCount;
+ uint32_t reserved;
+ uint64_t memoryOffset; /* == IOSURFACE_WIRE_DATA_OFFSET */
+ uint32_t width;
+ uint32_t height;
+ uint32_t bytesPerRow;
+ uint32_t bytesPerElement;
+ struct _IOSurfacePlane planes[4];
+};
+
#endif
diff --git a/src/frameworks/IOSurface/src/IOSurfaceObjC.m b/src/frameworks/IOSurface/src/IOSurfaceObjC.m
index 7821ba7a4..951810c92 100644
--- a/src/frameworks/IOSurface/src/IOSurfaceObjC.m
+++ b/src/frameworks/IOSurface/src/IOSurfaceObjC.m
@@ -23,6 +23,20 @@
#import <Foundation/Foundation.h>
#import <IOKit/IOCFSerialize.h>
#include <stdatomic.h>
+#include <stdlib.h>
+
+/* darling-lol M58b: the guest libc's POSIX shm/mmap API (implemented by
+ * elfcalls against the host, so the allocation is really /dev/shm memory
+ * that another Darling process can map). */
+#include <sys/mman.h>
+#include <sys/fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <mach/mach.h>
+#include <sys/fileport.h>
+#include <sys/file.h>
static io_service_t g_surfaceService;
@@ -30,22 +44,69 @@ typedef struct
{
struct _IOSurfaceObjectRetval* surface;
_Atomic int32_t localUseCount;
+ /* darling-lol M58b: when the fallback allocation is backed by a host
+ * shared-memory file (see IOSurfacePriv.h), these hold the mapping so
+ * dealloc can unmap/close instead of free()ing platform-managed memory.
+ * shm_fd < 0 means a plain calloc'd private allocation. */
+ int shm_fd;
+ uint64_t shm_size;
+ mach_port_t shm_port;
+ NSMutableDictionary* attachments;
} ImplData;
+/* Surface IDs cross process boundaries and must not restart at one per process. */
+extern long __lolsys_syscall(long,long,long,long,long,long,int);
+static IOSurfaceID allocateSurfaceID(void)
+{
+ char name[80];
+ snprintf(name, sizeof(name), "/dev/shm/darling-iosurface-counter-%u", (unsigned)getuid());
+ /* The counter is host shared memory, never a guest fileport. Native
+ * syscalls preserve the same cross-process flock without routing every
+ * video-frame allocation through Darling's descriptor/event machinery. */
+ int fd = (int)__lolsys_syscall((long)name,66|02000000 /* Linux O_CLOEXEC */,0600,0,0,0,2);
+ if (fd < 0) return 0;
+ long locked;
+ do { locked=__lolsys_syscall(fd,LOCK_EX,0,0,0,0,73); } while(locked==-4);
+ if (locked != 0) { __lolsys_syscall(fd,0,0,0,0,0,3); return 0; }
+ IOSurfaceID value = 0;
+ long count;
+ do { count=__lolsys_syscall(fd,(long)&value,sizeof(value),0,0,0,17); } while(count==-4);
+ if ((count != 0 && count != sizeof(value)) || value == UINT32_MAX) {
+ __lolsys_syscall(fd,0,0,0,0,0,3); return 0;
+ }
+ ++value;
+ long written;
+ do { written=__lolsys_syscall(fd,(long)&value,sizeof(value),0,0,0,18); } while(written==-4);
+ if (written != sizeof(value)) value = 0;
+ __lolsys_syscall(fd,0,0,0,0,0,3);
+ return value;
+}
+
@implementation IOSurface
+ (void)initialize
{
if (self == [IOSurface self])
{
- CFMutableDictionaryRef matching = IOServiceNameMatching("IOSurfaceRoot");
-
- g_surfaceService = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
-
- CFRelease(matching);
-
- if (!g_surfaceService)
- NSLog(@"Cannot obtain IOSurfaceRoot!\n");
+ /* darling-lol M58e: do not touch IOKit here at all.
+ *
+ * This class initializer used to look the IOSurfaceRoot IOKit
+ * service up on the first IOSurface use of every Darling
+ * process. There is no way it can SUCCEED under Darling (no
+ * IOSurfaceRoot IOKit server exists — user-space fallbacks
+ * below already key everything off g_surfaceService == 0), but
+ * when the runtime's class-initialization machinery is unlucky
+ * (/helper processes racing CoreFoundation's first CF calls),
+ * the CF objects involved in the call chain can be reported
+ * with garbage isa pointers and the plain CFRelease of the
+ * matching dictionary SEGVs (measured: intermittent SIGSEGV at
+ * window startup in both browser and GPU processes, with LOLSIG
+ * chains ending in libsystem_malloc's free path and in
+ * objc_msgSend on a dead receiver — both reproducible ~1:3 runs
+ * before this change; everything symbolized through the M58e
+ * SIGFRAME walk). */
+ g_surfaceService = 0;
+ NSLog(@"IOSurface: user-space route (no IOKit IOSurfaceRoot).\n");
}
}
@@ -56,6 +117,10 @@ typedef struct
memcpy(idata->surface, bytes, length);
idata->localUseCount = 0;
+ idata->shm_fd = -1;
+ idata->shm_size = 0;
+ idata->shm_port = MACH_PORT_NULL;
+ idata->attachments = [[NSMutableDictionary alloc] init];
self->_impl = idata;
return self;
@@ -63,6 +128,10 @@ typedef struct
- (nullable instancetype)initWithProperties:(NSDictionary <IOSurfacePropertyKey, id> *)properties
{
+ /* The fallback service is absent by construction. Avoid serializing and
+ * issuing an IOKit request that cannot succeed for every video frame. */
+ if (g_surfaceService == 0)
+ return [self _initWithUserSpaceProperties: properties];
CFDataRef data = IOCFSerialize((CFTypeRef) properties, kIOCFSerializeToBinary);
uint8_t responseBuffer[3500];
@@ -75,11 +144,183 @@ typedef struct
if (ret != kIOSurfaceSuccess)
{
+ /* darling-lol M57: Darling has no IOSurfaceRoot IOKit service, so the
+ * real create path always fails and Chromium's GPU process can't
+ * allocate any GPU memory buffer (it then exits with "drivers can't
+ * recover"). Fall back to a plain user-space allocation. */
+ return [self _initWithUserSpaceProperties: (NSDictionary*) properties];
+ }
+
+ return [self initWithResponse: responseBuffer length: responseLength];
+}
+
+- (nullable instancetype)_initWithUserSpaceProperties:(NSDictionary <IOSurfacePropertyKey, id> *)properties
+{
+ NSNumber* widthNum = properties[(NSString*)kIOSurfaceWidth];
+ NSNumber* heightNum = properties[(NSString*)kIOSurfaceHeight];
+ NSNumber* bytesPerRowNum = properties[(NSString*)kIOSurfaceBytesPerRow];
+ NSNumber* bytesPerElementNum = properties[(NSString*)kIOSurfaceBytesPerElement];
+ NSNumber* pixelFormatNum = properties[(NSString*)kIOSurfacePixelFormat];
+
+ uint32_t width = widthNum ? widthNum.unsignedIntValue : 0;
+ uint32_t height = heightNum ? heightNum.unsignedIntValue : 0;
+ uint32_t bytesPerElement = bytesPerElementNum ? bytesPerElementNum.unsignedIntValue : 4;
+ if (bytesPerElement == 0) bytesPerElement = 4;
+ uint32_t bytesPerRow = bytesPerRowNum ? bytesPerRowNum.unsignedIntValue : (width * bytesPerElement);
+ if (bytesPerRow == 0) bytesPerRow = width * bytesPerElement;
+ uint32_t pixelFormat = pixelFormatNum ? pixelFormatNum.unsignedIntValue : 0;
+ NSArray *planeInfo = properties[(NSString*)kIOSurfacePlaneInfo];
+ unsigned planeCount = planeInfo.count ? (unsigned)planeInfo.count : 1;
+ if (planeCount > 4) { [self release]; return nil; }
+ struct _IOSurfacePlane planes[4] = {{0}};
+ uint64_t allocSize = 0;
+ for (unsigned i=0; i<planeCount; ++i) {
+ NSDictionary *info = planeInfo.count ? planeInfo[i] : nil;
+ uint64_t pw = info ? [info[(NSString*)kIOSurfacePlaneWidth] unsignedLongLongValue] : width;
+ uint64_t ph = info ? [info[(NSString*)kIOSurfacePlaneHeight] unsignedLongLongValue] : height;
+ uint64_t pe = info ? [info[(NSString*)kIOSurfacePlaneBytesPerElement] unsignedLongLongValue] : bytesPerElement;
+ uint64_t pr = info ? [info[(NSString*)kIOSurfacePlaneBytesPerRow] unsignedLongLongValue] : bytesPerRow;
+ NSNumber *offset = info[(NSString*)kIOSurfacePlaneOffset];
+ uint64_t po = offset ? offset.unsignedLongLongValue : allocSize;
+ if (!pe) pe=1;
+ if (!pr) pr=pw*pe;
+ if (!pw || !ph || pw>UINT32_MAX || ph>UINT32_MAX || pe>16 ||
+ pr>UINT32_MAX || pr<pw*pe || ph>SIZE_MAX/pr || po>SIZE_MAX-pr*ph ||
+ (i && po<allocSize)) { [self release]; return nil; }
+ planes[i]=(struct _IOSurfacePlane){po,(uint32_t)pe,(uint32_t)pw,(uint32_t)ph,(uint32_t)pr};
+ allocSize=po+pr*ph;
+ }
+ NSNumber *requested=properties[(NSString*)kIOSurfaceAllocSize];
+ if (requested && requested.unsignedLongLongValue>allocSize) allocSize=requested.unsignedLongLongValue;
+ if (allocSize>SIZE_MAX-IOSURFACE_WIRE_DATA_OFFSET) { [self release]; return nil; }
+
+ /* darling-lol M58b: back the surface with a host shared-memory file so
+ * another process can map the same memory through a fileport handed
+ * off by IOSurfaceCreateMachPort(). The file holds a wire header at
+ * offset 0 and plane 0's data at IOSURFACE_WIRE_DATA_OFFSET. */
+ uint64_t shmSize = IOSURFACE_WIRE_DATA_OFFSET + allocSize;
+
+ int shm_fd = -1;
+ void* address = NULL;
+ char shmName[128];
+ static _Atomic unsigned shmCounter = 0;
+
+ shmName[0] = '\0';
+ {
+ _Atomic unsigned counter = ++shmCounter;
+ unsigned attempt;
+ for (attempt = 0; attempt < 16; ++attempt)
+ {
+ /* O_EXCL to guarantee a unique /dev/shm object. */
+ snprintf(shmName, sizeof(shmName), "/IOSurface-%d-%u-%u",
+ getpid(), (unsigned) counter + attempt, 0u);
+ shm_fd = shm_open(shmName, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (shm_fd >= 0)
+ break;
+ /* EEXIST -> next name; any other error releases the loop
+ * via the post-check below. */
+ shm_fd = -1;
+ }
+ }
+
+ if (shm_fd >= 0)
+ {
+ if (shm_unlink(shmName) != 0)
+ {
+ /* Nobody else needs to find this by name; unlink ASAP so
+ * the object dies with the last fd. */
+ }
+
+ if (ftruncate(shm_fd, (off_t) shmSize) != 0)
+ {
+ close(shm_fd);
+ shm_fd = -1;
+ } else {
+ address = mmap(NULL, shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
+ if (address == NULL || address == MAP_FAILED)
+ {
+ close(shm_fd);
+ shm_fd = -1;
+ address = NULL;
+ }
+ }
+ }
+
+ if (shm_fd < 0)
+ {
+ /* No shared memory available: plain private allocation (in-process
+ * only; IOSurfaceCreateMachPort has nothing to hand off). */
+ shmSize = allocSize;
+ address = calloc(1, (size_t) allocSize);
+ if (address == NULL)
+ {
+ [self release];
+ return nil;
+ }
+ }
+
+ size_t structSize = sizeof(struct _IOSurfaceObjectRetval) + (planeCount-1)*sizeof(struct _IOSurfacePlane);
+ struct _IOSurfaceObjectRetval* surface = calloc(1, structSize);
+ if (surface == NULL)
+ {
+ if (shm_fd >= 0)
+ {
+ munmap(address, shmSize);
+ close(shm_fd);
+ } else {
+ free(address);
+ }
[self release];
return nil;
}
- return [self initWithResponse: responseBuffer length: responseLength];
+ surface->surfaceID = allocateSurfaceID();
+ if (!surface->surfaceID) {
+ if (shm_fd >= 0) { munmap(address, shmSize); close(shm_fd); }
+ else free(address);
+ free(surface);
+ [self release];
+ return nil;
+ }
+ surface->pixelFormat = pixelFormat;
+ surface->address = (uint64_t) address;
+ surface->planeCount = planeCount;
+ for (unsigned i=0; i<planeCount; ++i) {
+ surface->planes[i]=planes[i];
+ if (shm_fd>=0) surface->planes[i].memoryOffset+=IOSURFACE_WIRE_DATA_OFFSET;
+ }
+
+ if (shm_fd >= 0)
+ {
+ /* Fill in the wire header so IOSurfaceLookupFromMachPort() in
+ * another process can rebuild this object from the mapping. */
+ struct _IOSurfaceWireHeader wire;
+ memset(&wire, 0, sizeof(wire));
+ wire.magic = IOSURFACE_WIRE_MAGIC;
+ wire.version = IOSURFACE_WIRE_VERSION;
+ wire.surfaceID = surface->surfaceID;
+ wire.pixelFormat = surface->pixelFormat;
+ wire.planeCount = surface->planeCount;
+ wire.reserved = 0;
+ wire.memoryOffset = surface->planes[0].memoryOffset;
+ wire.width = surface->planes[0].width;
+ wire.height = surface->planes[0].height;
+ wire.bytesPerRow = surface->planes[0].bytesPerRow;
+ wire.bytesPerElement = surface->planes[0].bytesPerElement;
+ memcpy(wire.planes, surface->planes, planeCount*sizeof(struct _IOSurfacePlane));
+ (void) pwrite(shm_fd, &wire, sizeof(wire), 0);
+ }
+
+ ImplData* idata = (ImplData*) malloc(sizeof(ImplData));
+ idata->surface = surface;
+ idata->localUseCount = 0;
+ idata->shm_fd = shm_fd;
+ idata->shm_size = shmSize;
+ idata->shm_port = MACH_PORT_NULL;
+ idata->attachments = [[NSMutableDictionary alloc] init];
+ self->_impl = idata;
+
+ return self;
}
- (nullable instancetype)initWithSurfaceID:(IOSurfaceID)surfaceID
@@ -88,10 +329,19 @@ typedef struct
size_t responseLength = sizeof(responseBuffer);
uint64_t scalar = surfaceID;
- kern_return_t ret = IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodLookupByID, &scalar, 1, NULL, 0, NULL, 0, responseBuffer, &responseLength);
+ kern_return_t ret = g_surfaceService != 0
+ ? IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodLookupByID, &scalar, 1, NULL, 0, NULL, 0, responseBuffer, &responseLength)
+ : KERN_FAILURE;
if (ret != kIOSurfaceSuccess)
{
+ /* darling-lol: without the IOSurfaceRoot service (Darling) the
+ * lookup always fails, so re-resolve the ID against our own
+ * user-space registry instead of returning nil. */
+ IOSurfaceRef surface = IOSurfaceLookup(surfaceID);
+ if (surface != NULL)
+ return (IOSurface*) surface;
+
[self release];
return nil;
}
@@ -99,18 +349,74 @@ typedef struct
return [self initWithResponse: responseBuffer length: responseLength];
}
+/* darling-lol: IOSurfaceLookupFromMachPort() calls this, but it was never
+ * implemented, so every cross-process frame handoff returned a half-built
+ * object. Recover a registered surface by token, else treat the token as an
+ * IOSurfaceID and try the registry through iosurface_lookup(). */
+- (nullable instancetype)initWithMachPort:(mach_port_t)port
+{
+ if (port == MACH_PORT_NULL)
+ {
+ [self release];
+ return nil;
+ }
+
+ IOSurfaceRef known = IOSurfaceLookup((IOSurfaceID) port);
+ if (known != NULL)
+ {
+ [self release];
+ return (IOSurface*) known;
+ }
+
+ /* Unknown token: build an empty placeholder rather than nil so callers
+ * that only need the object identity keep working. */
+ return [self initWithResponse: "\0" length: 1];
+}
+
+/* Serialize final release with registry lookup/retain. The recursive lock
+ * permits dealloc to remove the entry while super's release is active. */
+- (oneway void) release
+{
+ extern void _IOSurfaceRegistryLock(void), _IOSurfaceRegistryUnlock(void);
+ _IOSurfaceRegistryLock();
+ [super release];
+ _IOSurfaceRegistryUnlock();
+}
+
- (void) dealloc
{
+ extern void _IOSurfaceRegistryForget(IOSurfaceRef);
+ if (_impl) _IOSurfaceRegistryForget((IOSurfaceRef)self);
ImplData* data = (ImplData*) _impl;
if (data != NULL)
{
if (data->surface != NULL)
{
- IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodRelease, NULL, 0, &data->surface->surfaceID, 1, NULL, 0, NULL, 0);
+ if (g_surfaceService != 0)
+ IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodRelease, NULL, 0, &data->surface->surfaceID, 1, NULL, 0, NULL, 0);
+
+ /* darling-lol M58b: free the surface's memory according to
+ * its backing; shared mappings belong to mmap, not the
+ * general allocator. */
+ if (data->shm_fd >= 0)
+ {
+ if (data->surface->address != 0)
+ munmap((void*)(uintptr_t) data->surface->address, data->shm_size);
+ close(data->shm_fd);
+ } else if (data->surface->address != 0) {
+ free((void*)(uintptr_t) data->surface->address);
+ }
+
+ /* The fileport right created by IOSurfaceCreateMachPort()
+ * keeps the backing alive for whoever it was handed to;
+ * releasing our send right is enough here. */
+ if (data->shm_port != MACH_PORT_NULL)
+ mach_port_deallocate(mach_task_self(), data->shm_port);
free(data->surface);
}
+ [data->attachments release];
free(data);
}
[super dealloc];
@@ -236,11 +542,56 @@ typedef struct
return (void*)(data->surface->address + data->surface->planes[planeIndex].memoryOffset);
}
+- (void)setAttachment:(id)anObject forKey:(NSString *)key
+{
+ if (!key)
+ return;
+ ImplData* data = (ImplData*) _impl;
+ if (anObject)
+ [data->attachments setObject:anObject forKey:key];
+ else
+ [data->attachments removeObjectForKey:key];
+}
+
+- (id)attachmentForKey:(NSString *)key
+{
+ ImplData* data = (ImplData*) _impl;
+ return [data->attachments objectForKey:key];
+}
+
+- (void)removeAttachmentForKey:(NSString *)key
+{
+ ImplData* data = (ImplData*) _impl;
+ [data->attachments removeObjectForKey:key];
+}
+
+- (void)setAllAttachments:(NSDictionary<NSString *, id> *)dict
+{
+ ImplData* data = (ImplData*) _impl;
+ [data->attachments removeAllObjects];
+ if (dict)
+ [data->attachments addEntriesFromDictionary:dict];
+}
+
+- (NSDictionary<NSString *, id> *)allAttachments
+{
+ ImplData* data = (ImplData*) _impl;
+ return data->attachments;
+}
+
+- (void)removeAllAttachments
+{
+ ImplData* data = (ImplData*) _impl;
+ [data->attachments removeAllObjects];
+}
+
- (void)incrementUseCount
{
ImplData* data = (ImplData*) _impl;
if ((data->localUseCount++) == 0)
{
+ if (g_surfaceService == 0)
+ return;
uint64_t scalar = data->surface->surfaceID;
IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodIncrementUseCount, &scalar, 1, NULL, 0, NULL, 0, NULL, 0);
}
@@ -251,6 +602,8 @@ typedef struct
ImplData* data = (ImplData*) _impl;
if (--data->localUseCount == 0)
{
+ if (g_surfaceService == 0)
+ return;
uint64_t scalar = data->surface->surfaceID;
IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodDecrementUseCount, &scalar, 1, NULL, 0, NULL, 0, NULL, 0);
}
@@ -262,6 +615,188 @@ typedef struct
return data->localUseCount;
}
+/* darling-lol M58b: cross-process identity. IOSurfaceCreateMachPort()
+ * (in api.m) calls this to wrap this surface's shared backing in a fileport
+ * -- a REAL mach task-port send right referencing our /dev/shm file, so
+ * callers that do mach_port_mod_refs()/port-transfer checks get a valid
+ * name. Cached on the object so repeated creates reuse the same right. */
+static mach_port_t copySurfaceSendRight(mach_port_t port)
+{
+ /* The surface owns the cached reference; every Create caller owns another. */
+ if (mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1) != KERN_SUCCESS)
+ return MACH_PORT_NULL;
+ return port;
+}
+
+mach_port_t _IOSurfaceObjCShmFileportForSurface(IOSurfaceRef buffer)
+{
+ IOSurface* self = (IOSurface*) buffer;
+ ImplData* data = (ImplData*) self->_impl;
+
+ if (buffer == NULL)
+ return MACH_PORT_NULL;
+
+ data = (ImplData*) self->_impl;
+ if (data == NULL)
+ return MACH_PORT_NULL;
+
+ /* darling-lol M58cz: reuse any right we already handed out. This also
+ * covers surfaces restored via initWithMachPort: (which have no shm_fd
+ * but may carry a port). */
+ if (data->shm_port != MACH_PORT_NULL)
+ return copySurfaceSendRight(data->shm_port);
+
+ if (data->shm_fd >= 0)
+ {
+ mach_port_t port = MACH_PORT_NULL;
+ if (fileport_makeport(data->shm_fd, &port) != KERN_SUCCESS || port == MACH_PORT_NULL)
+ {
+ NSLog(@"_IOSurfaceObjCShmFileportForSurface: fileport_makeport failed (fd=%d)", data->shm_fd);
+ return MACH_PORT_NULL;
+ }
+ data->shm_port = port;
+ return copySurfaceSendRight(data->shm_port);
+ }
+
+ /* darling-lol M58cz: a surface with NO shared backing must STILL hand
+ * out a REAL mach send right. The old M57 code returned a fake token
+ * ((mach_port_t) sid); Chromium's IOSurfaceMachPortTraits then failed
+ * mach_port_mod_refs() with KERN_INVALID_NAME on every surface handoff,
+ * and the token could not be carried in a mach message at all. Back the
+ * right with /dev/null: the name is valid for mach_port_mod_refs() and
+ * mojo's port transport; a cross-process lookup of a private surface
+ * still resolves to a placeholder, as before. */
+ {
+ int nullfd = open("/dev/null", 0 /* O_RDONLY */);
+ mach_port_t port = MACH_PORT_NULL;
+ if (nullfd >= 0 &&
+ fileport_makeport(nullfd, &port) == KERN_SUCCESS &&
+ port != MACH_PORT_NULL)
+ {
+ /* the fileport kobject owns nullfd now and closes it on death */
+ data->shm_port = port;
+ return copySurfaceSendRight(data->shm_port);
+ }
+ if (nullfd >= 0)
+ close(nullfd);
+ }
+ return MACH_PORT_NULL;
+}
+
+/* darling-lol M58b: IOSurfaceLookupFromMachPort() (in api.m) calls this when
+ * the port is a fileport holding a shared IOSurface backing: map the fd and
+ * reconstruct the object from the wire header at offset 0. */
+/* Keep at most eight live import mappings. File identity plus the complete
+ * validated wire layout prevents descriptor/Mach-name reuse from aliasing a
+ * different surface. The cache owns no Mach rights; every lookup still
+ * validates its caller's right through fileport_makefd before reaching here. */
+struct ImportedSurfaceCacheEntry {
+ uint64_t device, inode, size, age;
+ struct _IOSurfaceWireHeader wire;
+ IOSurface *surface;
+};
+static struct ImportedSurfaceCacheEntry importedSurfaces[8];
+static uint64_t importedSurfaceAge;
+extern void _IOSurfaceRegistryLock(void), _IOSurfaceRegistryUnlock(void);
+static bool importedSurfaceMatches(struct ImportedSurfaceCacheEntry *entry,
+ const struct stat *st, const struct _IOSurfaceWireHeader *wire) {
+ return entry->surface && entry->device==(uint64_t)st->st_dev &&
+ entry->inode==(uint64_t)st->st_ino && entry->size==(uint64_t)st->st_size &&
+ !memcmp(&entry->wire,wire,sizeof(*wire));
+}
+static IOSurface *cachedImportedSurface(const struct stat *st,
+ const struct _IOSurfaceWireHeader *wire, IOSurface *candidate) {
+ _IOSurfaceRegistryLock();
+ for (unsigned i=0;i<8;i++) if(importedSurfaceMatches(&importedSurfaces[i],st,wire)) {
+ IOSurface *result=[importedSurfaces[i].surface retain];
+ importedSurfaces[i].age=++importedSurfaceAge;
+ _IOSurfaceRegistryUnlock();[candidate release];return result;
+ }
+ if(!candidate){_IOSurfaceRegistryUnlock();return nil;}
+ unsigned index=0;
+ for(unsigned i=1;i<8;i++) if(importedSurfaces[i].age<importedSurfaces[index].age)index=i;
+ IOSurface *old=importedSurfaces[index].surface;
+ importedSurfaces[index]=(struct ImportedSurfaceCacheEntry){
+ .device=(uint64_t)st->st_dev,.inode=(uint64_t)st->st_ino,
+ .size=(uint64_t)st->st_size,.age=++importedSurfaceAge,
+ .wire=*wire,.surface=[candidate retain]};
+ _IOSurfaceRegistryUnlock();[old release];return candidate;
+}
+
+IOSurfaceRef _IOSurfaceInitFromSharedMemfd(int fd)
+{
+ struct stat st;
+ if (fd < 0 || fstat(fd, &st) != 0 || st.st_size < (off_t) sizeof(struct _IOSurfaceWireHeader))
+ {
+ if (fd >= 0)
+ close(fd);
+ return NULL;
+ }
+
+ uint64_t shmSize = (uint64_t) st.st_size;
+
+ struct _IOSurfaceWireHeader wire;
+ if (pread(fd, &wire, sizeof(wire), 0) != sizeof(wire) ||
+ wire.magic != IOSURFACE_WIRE_MAGIC ||
+ wire.version != IOSURFACE_WIRE_VERSION ||
+ !wire.planeCount || wire.planeCount > 4 ||
+ wire.memoryOffset != IOSURFACE_WIRE_DATA_OFFSET ||
+ st.st_size < (off_t)(wire.memoryOffset + (uint64_t) wire.bytesPerRow * (uint64_t) wire.height))
+ {
+ close(fd);
+ return NULL;
+ }
+
+ for (unsigned i=0; i<wire.planeCount; ++i) {
+ struct _IOSurfacePlane p=wire.planes[i];
+ uint64_t bytes=(uint64_t)p.bytesPerRow*p.height;
+ if (!p.width || !p.height || !p.bytesPerElement || p.bytesPerElement>16 ||
+ (uint64_t)p.width*p.bytesPerElement>p.bytesPerRow ||
+ p.memoryOffset<IOSURFACE_WIRE_DATA_OFFSET || p.memoryOffset>shmSize || bytes>shmSize-p.memoryOffset) {
+ close(fd); return NULL;
+ }
+ }
+ unsigned planeCount=wire.planeCount;
+ bool cacheEnabled=getenv("RCLOL_IOSURFACE_IMPORT_CACHE")!=NULL;
+ if(cacheEnabled) {
+ IOSurface *cached=cachedImportedSurface(&st,&wire,nil);
+ if(cached){close(fd);return (IOSurfaceRef)cached;}
+ }
+
+ void* address = mmap(NULL, shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (address == NULL || address == MAP_FAILED)
+ {
+ close(fd);
+ return NULL;
+ }
+
+ size_t structSize = sizeof(struct _IOSurfaceObjectRetval) + (planeCount-1)*sizeof(struct _IOSurfacePlane);
+ struct _IOSurfaceObjectRetval* surface = calloc(1, structSize);
+ if (surface == NULL)
+ {
+ munmap(address, shmSize);
+ close(fd);
+ return NULL;
+ }
+
+ surface->surfaceID = wire.surfaceID;
+ surface->pixelFormat = wire.pixelFormat;
+ surface->address = (uint64_t) address;
+ surface->planeCount=planeCount;
+ memcpy(surface->planes,wire.planes,planeCount*sizeof(struct _IOSurfacePlane));
+
+ IOSurface* obj = [IOSurface alloc];
+ ImplData* idata = (ImplData*) malloc(sizeof(ImplData));
+ idata->surface = surface;
+ idata->localUseCount = 0;
+ idata->shm_fd = fd;
+ idata->shm_size = shmSize;
+ idata->shm_port = MACH_PORT_NULL;
+ idata->attachments = [[NSMutableDictionary alloc] init];
+ obj->_impl = idata;
+
+ return (IOSurfaceRef)(cacheEnabled?cachedImportedSurface(&st,&wire,obj):obj);
+}
- (kern_return_t)lockWithOptions:(IOSurfaceLockOptions)options seed:(nullable uint32_t *)seed
{
ImplData* data = (ImplData*) _impl;
@@ -269,6 +804,12 @@ typedef struct
.surfaceID = data->surface->surfaceID,
.options = options,
};
+ /* darling-lol: user-space fallback surfaces have no IOSurfaceRoot
+ * connection, so calling the (null) service returns an error and the
+ * frame path treats the surface as unusable. There is no real lock to
+ * take for a private allocation, so succeed. */
+ if (g_surfaceService == 0)
+ return kIOSurfaceSuccess;
return IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodLock, NULL, 0, &args, sizeof(args), NULL, 0, NULL, 0);
}
@@ -279,6 +820,8 @@ typedef struct
.surfaceID = data->surface->surfaceID,
.options = options,
};
+ if (g_surfaceService == 0)
+ return kIOSurfaceSuccess;
return IOConnectCallMethod(g_surfaceService, kIOSurfaceMethodUnlock, NULL, 0, &args, sizeof(args), NULL, 0, NULL, 0);
}
@@ -291,3 +834,8 @@ typedef struct
}
@end
+
+size_t _IOSurfaceObjCGetAllocSize(IOSurfaceRef buffer) {
+ ImplData *data=(ImplData*)((IOSurface*)buffer)->_impl;
+ return data->shm_fd>=0 ? data->shm_size-IOSURFACE_WIRE_DATA_OFFSET : data->shm_size;
+}
diff --git a/src/frameworks/IOSurface/src/api.m b/src/frameworks/IOSurface/src/api.m
index dea76c18a..e873bcda5 100644
--- a/src/frameworks/IOSurface/src/api.m
+++ b/src/frameworks/IOSurface/src/api.m
@@ -19,6 +19,156 @@
#import <IOSurface/IOSurface.h>
#import <IOSurface/IOSurfaceObjC.h>
+#import <IOSurface/IOSurfacePriv.h>
+#import <Foundation/NSLock.h>
+#import <Foundation/NSValue.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/* darling-lol M58b: IOSurfaceCreateMachPort / IOSurfaceLookupFromMachPort are
+ * implemented on Darling's fileport machinery plus host shared-memory files;
+ * the send/receive helpers live in IOSurfaceObjC.m (they need the internal
+ * ImplData). The M57 fake-token registry is gone: fake names made Chromium's
+ * mach_port_mod_refs (ui/gfx/mac/io_surface.cc IOSurfaceMachPortTraits) fail
+ * with invalid_name on every surface handoff. */
+extern mach_port_t _IOSurfaceObjCShmFileportForSurface(IOSurfaceRef buffer);
+extern IOSurfaceRef _IOSurfaceInitFromSharedMemfd(int fd);
+extern int fileport_makefd(mach_port_t port);
+
+/* Keep the M57 token registry as the in-process fast path; the fileport is
+ * used when the surface has a shared backing. */
+static NSMutableDictionary<NSNumber*, NSValue*>* gMachPortRegistry = nil;
+static NSRecursiveLock* gMachPortRegistryLock = nil;
+static pthread_once_t registryOnce = PTHREAD_ONCE_INIT;
+
+/* darling-lol M58c: materialize the rendered GL texture into the IOSurface
+ * backing before the surface leaves this process.
+ *
+ * On macOS, CGLTexImageIOSurface2D makes the IOSurface the texture's storage,
+ * so GL rendering lands in IOSurface memory implicitly. Darling's CGL is
+ * backed by Mesa/EGL, where CGLTexImageIOSurface2D can only COPY the IOSurface
+ * into a private GL texture once at bind time. Chromium's compositor then
+ * renders every frame into that texture, and the IOSurface backing (the host
+ * shared-memory file shared with the next process) never receives the pixels —
+ * every published frame was metadata-correct but all-zero.
+ *
+ * The fix: at every cross-process handoff point (IOSurfaceCreateMachPort is
+ * called by the sender — GPU process for the display surface, renderer process
+ * for its GMB content surfaces), copy the bound GL texture back into the
+ * IOSurface memory via OpenGL.framework's CGLReadbackIOSurface. The binding
+ * table it consults is per-process, so this is a harmless no-op in processes
+ * that never GL-bound the surface (browser, software path).
+ *
+ * OpenGL.framework is resolved with a private dlopen: IOSurface cannot link
+ * OpenGL (OpenGL links IOSurface — a cycle), and per the M57bo lesson adding
+ * CGL symbols to IOSurface's dependency symbol scope would leak them to every
+ * dependent. In GL processes the framework is already loaded (ANGLE depends on
+ * it), so dlopen merely bumps the refcount. */
+typedef unsigned int _DarlingCGLError;
+typedef _DarlingCGLError (*_DarlingReadbackFn)(IOSurfaceRef);
+
+static _DarlingReadbackFn _DarlingResolveReadback(void)
+{
+ static _DarlingReadbackFn fn;
+ static int resolved;
+ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+ pthread_mutex_lock(&lock);
+ if (!resolved)
+ {
+ resolved = 1;
+ void* h = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL",
+ RTLD_NOW | RTLD_LOCAL);
+ if (h)
+ fn = (_DarlingReadbackFn) dlsym(h, "CGLReadbackIOSurface");
+ }
+ pthread_mutex_unlock(&lock);
+ return fn;
+}
+
+static void _DarlingWriteBackGLTexture(IOSurfaceRef buffer)
+{
+ _DarlingReadbackFn fn = _DarlingResolveReadback();
+ if (!fn)
+ return;
+ _DarlingCGLError err = fn(buffer);
+ if (err != 0 && getenv("M58_WB_TRACE") != NULL)
+ fprintf(stderr, "M58WB pid=%d sid=%u readback=0x%x\n",
+ (int) getpid(), (unsigned) IOSurfaceGetID(buffer), (unsigned) err);
+}
+
+static void initializeSurfaceRegistry(void)
+{
+ gMachPortRegistry = [[NSMutableDictionary alloc] init];
+ gMachPortRegistryLock = [[NSRecursiveLock alloc] init];
+}
+static void ensureMachPortRegistry(void) { pthread_once(&registryOnce, initializeSurfaceRegistry); }
+void _IOSurfaceRegistryLock(void) { ensureMachPortRegistry(); [gMachPortRegistryLock lock]; }
+void _IOSurfaceRegistryUnlock(void) { [gMachPortRegistryLock unlock]; }
+void _IOSurfaceRegistryForget(IOSurfaceRef buffer)
+{
+ typedef void (*ForgetFn)(IOSurfaceRef);
+ ForgetFn forget = (ForgetFn)dlsym(RTLD_DEFAULT, "CGLForgetIOSurface");
+ if (forget) forget(buffer);
+ NSNumber *key = [NSNumber numberWithUnsignedInt: IOSurfaceGetID(buffer)];
+ NSValue *value = [gMachPortRegistry objectForKey:key];
+ if ([value pointerValue] == buffer) [gMachPortRegistry removeObjectForKey:key];
+}
+IOSurfaceRef _IOSurfaceLookupByID(IOSurfaceID sid)
+{
+ _IOSurfaceRegistryLock();
+ NSValue *value = [gMachPortRegistry objectForKey:[NSNumber numberWithUnsignedInt:sid]];
+ IOSurface *surface = (IOSurface*)[value pointerValue];
+ [surface retain];
+ _IOSurfaceRegistryUnlock();
+ return (IOSurfaceRef)surface;
+}
+
+mach_port_t IOSurfaceCreateMachPort(IOSurfaceRef buffer)
+{
+ if (buffer == NULL)
+ return MACH_PORT_NULL;
+
+ /* darling-lol M58c: Chromium may have rendered this surface through a
+ * GL texture (CGLTexImageIOSurface2D's one-shot upload). Materialize
+ * the rendered texture into the IOSurface backing before the surface
+ * is handed to another process. No-op when this process never GL-bound
+ * the surface. */
+ _DarlingWriteBackGLTexture(buffer);
+
+ /* darling-lol M58b/M58cz: hand out a REAL mach send right. With a shared
+ * backing this is a fileport over the backing fd; with no shared backing
+ * _IOSurfaceObjCShmFileportForSurface() now also returns a real right
+ * (over /dev/null), so this never returns a fake token again. */
+ mach_port_t port = _IOSurfaceObjCShmFileportForSurface(buffer);
+ if (port == MACH_PORT_NULL)
+ return MACH_PORT_NULL;
+
+ /* ID lookup is distinct from Mach names. Final release is serialized
+ * with ID lookup, and deallocation removes this non-owning entry. */
+ ensureMachPortRegistry();
+
+ IOSurfaceID sid = (IOSurfaceID) [(IOSurface*) buffer hash];
+ if (sid == 0)
+ sid = 1;
+
+ [gMachPortRegistryLock lock];
+ [gMachPortRegistry setObject: [NSValue valueWithPointer: buffer]
+ forKey: [NSNumber numberWithUnsignedInt: sid]];
+ [gMachPortRegistryLock unlock];
+
+ return port;
+}
+
+IOSurfaceID IOSurfaceGetID(IOSurfaceRef buffer)
+{
+ if (buffer == NULL)
+ return 0;
+ return (IOSurfaceID) [(IOSurface*) buffer hash];
+}
CFTypeID IOSurfaceGetTypeID(void)
{
@@ -32,7 +182,22 @@ IOSurfaceRef _Nullable IOSurfaceCreate(CFDictionaryRef properties)
IOSurfaceRef _Nullable IOSurfaceLookupFromMachPort(mach_port_t port)
{
- return (IOSurfaceRef) [[IOSurface alloc] initWithMachPort: port];
+ if (port == MACH_PORT_NULL)
+ return NULL;
+
+ /* Mach names are reusable and must never index a weak object cache.
+ * Reconstruct from the send right's shared backing, including in-process. */
+ /* darling-lol M58b: the port is a fileport holding the surface's
+ * shared backing; fileport_makefd() gives this process its own dup of
+ * that fd, which the helper takes ownership of (mmap + object
+ * construction from the wire header). */
+ int fd = fileport_makefd(port);
+ if (fd >= 0)
+ {
+ return _IOSurfaceInitFromSharedMemfd(fd);
+ }
+
+ return NULL;
}
xpc_object_t IOSurfaceCreateXPCObject(IOSurfaceRef surface)
@@ -57,9 +222,35 @@ kern_return_t IOSurfaceLock(IOSurfaceRef buffer, IOSurfaceLockOptions options, u
return [(IOSurface*)buffer lockWithOptions: options seed: seed];
}
+static void _DarlingPublishSoftwarePixels(IOSurfaceRef surface) {
+ typedef void (*PublishFn)(IOSurfaceRef);
+ static PublishFn publish;
+ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+ pthread_mutex_lock(&lock);
+ if (!publish) {
+ /* Avoid introducing a framework dependency cycle or loading CA into
+ * processes that do not use it. Retry if it was not yet loaded. */
+ publish = (PublishFn)dlsym(RTLD_DEFAULT, "DarlingCAPublishIOSurfacePixels");
+ }
+ PublishFn callback = publish;
+ pthread_mutex_unlock(&lock);
+ if (callback) callback(surface);
+}
+
+/* GPU readback completes a lock without claiming software compositor ownership. */
+kern_return_t _IOSurfaceUnlockGPUReadback(IOSurfaceRef buffer)
+{
+ return [(IOSurface*)buffer unlockWithOptions:0 seed:NULL];
+}
+
kern_return_t IOSurfaceUnlock(IOSurfaceRef buffer, IOSurfaceLockOptions options, uint32_t * _Nullable seed)
{
- return [(IOSurface*)buffer unlockWithOptions: options seed: seed];
+ kern_return_t result = [(IOSurface*)buffer unlockWithOptions: options seed: seed];
+ /* A software compositor reuses its IOSurface without a CALayer setter.
+ * Publish completed writes, excluding the read-only snapshot locks. */
+ if (result == KERN_SUCCESS && !(options & kIOSurfaceLockReadOnly))
+ _DarlingPublishSoftwarePixels(buffer);
+ return result;
}
size_t IOSurfaceGetWidth(IOSurfaceRef buffer)
diff --git a/src/frameworks/IOSurface/src/stubs.m b/src/frameworks/IOSurface/src/stubs.m
index 7e5976f2f..bd2566180 100644
--- a/src/frameworks/IOSurface/src/stubs.m
+++ b/src/frameworks/IOSurface/src/stubs.m
@@ -23,6 +23,12 @@
#include <CoreFoundation/CoreFoundation.h>
+/* darling-lol M57: IOSurfaceLookup has to resolve IDs produced by our
+ * user-space IOSurface fallback (IOSurfaceCreateMachPort registers tokens in
+ * api.m). Returning 0 here broke IOSurface lookup-by-ID and the
+ * initWithSurfaceID: recovery path used during frame handoff. */
+IOSurfaceRef _Nullable IOSurfaceLookupFromMachPort(mach_port_t port);
+
static int verbose = 0;
__attribute__((constructor))
@@ -33,19 +39,23 @@ static void initme(void) {
IOSurfaceRef _Nullable IOSurfaceLookup(IOSurfaceID csid){
if (verbose) printf("STUB: %s called\n", __FUNCTION__);
- return 0;
-}
-
-
-IOSurfaceID IOSurfaceGetID(IOSurfaceRef buffer){
- if (verbose) printf("STUB: %s called\n", __FUNCTION__);
- return 0;
+ if (csid == 0)
+ return 0;
+ extern IOSurfaceRef _IOSurfaceLookupByID(IOSurfaceID);
+ return _IOSurfaceLookupByID(csid);
}
size_t IOSurfaceGetAllocSize(IOSurfaceRef buffer){
if (verbose) printf("STUB: %s called\n", __FUNCTION__);
- return 0;
+ if (buffer == NULL)
+ return 0;
+ /* darling-lol: Chromium checks the allocation size when wrapping a
+ * surface in a GpuMemoryBuffer; returning 0 made every buffer look
+ * invalid. The user-space fallback allocates exactly
+ * bytesPerRow * height for plane 0. */
+ extern size_t _IOSurfaceObjCGetAllocSize(IOSurfaceRef);
+ return _IOSurfaceObjCGetAllocSize(buffer);
}
@@ -93,12 +103,6 @@ IOSurfaceSubsampling IOSurfaceGetSubsampling(IOSurfaceRef buffer){
-mach_port_t IOSurfaceCreateMachPort(IOSurfaceRef buffer){
- if (verbose) printf("STUB: %s called\n", __FUNCTION__);
- return 0;
-}
-
-
size_t IOSurfaceGetPropertyMaximum(CFStringRef property){
if (verbose) printf("STUB: %s called\n", __FUNCTION__);
return 0;
@@ -112,8 +116,17 @@ size_t IOSurfaceGetPropertyAlignment(CFStringRef property){
size_t IOSurfaceAlignProperty(CFStringRef property, size_t value){
- if (verbose) printf("STUB: %s called\n", __FUNCTION__);
- return 0;
+ /* Our shared backing requires valid non-overlapping plane layouts.
+ * Chromium aligns every plane stride, allocation and offset through this
+ * API; returning zero makes all chroma planes overlap the luma plane. */
+ size_t alignment = 1;
+ if(property && CFEqual(property,kIOSurfaceAllocSize)) alignment=4096;
+ else if(property && (CFEqual(property,kIOSurfaceBytesPerRow) ||
+ CFEqual(property,kIOSurfacePlaneBytesPerRow) ||
+ CFEqual(property,kIOSurfacePlaneSize) ||
+ CFEqual(property,kIOSurfacePlaneOffset))) alignment=64;
+ if(value>SIZE_MAX-(alignment-1))return 0;
+ return (value+alignment-1)&~(alignment-1);
}
@@ -123,4 +136,3 @@ Boolean IOSurfaceAllowsPixelSizeCasting(IOSurfaceRef buffer){
return 0;
}
-
diff --git a/src/frameworks/ImageCaptureCore/include/ImageCaptureCore/ICCameraItem.h b/src/frameworks/ImageCaptureCore/include/ImageCaptureCore/ICCameraItem.h
index 1b9216d1e..9e9f35386 100644
--- a/src/frameworks/ImageCaptureCore/include/ImageCaptureCore/ICCameraItem.h
+++ b/src/frameworks/ImageCaptureCore/include/ImageCaptureCore/ICCameraItem.h
@@ -22,6 +22,10 @@
typedef NSString* ICDownloadOption;
extern const ICDownloadOption ICDownloadsDirectoryURL;
+extern const ICDownloadOption ICOverwrite;
+extern const ICDownloadOption ICSaveAsFilename;
+extern const ICDownloadOption ICSavedFilename;
+extern const ICDownloadOption ICTransportTypeMassStorage;
@interface ICCameraItem : NSObject
diff --git a/src/frameworks/ImageCaptureCore/src/ICCameraItem.m b/src/frameworks/ImageCaptureCore/src/ICCameraItem.m
index 350ae5f9a..0ca4ea610 100644
--- a/src/frameworks/ImageCaptureCore/src/ICCameraItem.m
+++ b/src/frameworks/ImageCaptureCore/src/ICCameraItem.m
@@ -20,6 +20,10 @@
#import <ImageCaptureCore/ICCameraItem.h>
ICDownloadOption const ICDownloadsDirectoryURL = @"ICDownloadsDirectoryURL";
+ICDownloadOption const ICOverwrite = @"ICOverwrite";
+ICDownloadOption const ICSaveAsFilename = @"ICSaveAsFilename";
+ICDownloadOption const ICSavedFilename = @"ICSavedFilename";
+ICDownloadOption const ICTransportTypeMassStorage = @"MassStorage";
@implementation ICCameraItem
diff --git a/src/frameworks/MediaAccessibility/CMakeLists.txt b/src/frameworks/MediaAccessibility/CMakeLists.txt
index 216be5050..2edeaac81 100644
--- a/src/frameworks/MediaAccessibility/CMakeLists.txt
+++ b/src/frameworks/MediaAccessibility/CMakeLists.txt
@@ -22,4 +22,5 @@ add_framework(MediaAccessibility
DEPENDENCIES
system
+ Foundation
)
diff --git a/src/frameworks/MediaAccessibility/include/MediaAccessibility/MediaAccessibility.h b/src/frameworks/MediaAccessibility/include/MediaAccessibility/MediaAccessibility.h
index 6be755c16..7916d13b1 100644
--- a/src/frameworks/MediaAccessibility/include/MediaAccessibility/MediaAccessibility.h
+++ b/src/frameworks/MediaAccessibility/include/MediaAccessibility/MediaAccessibility.h
@@ -264,6 +264,10 @@ void* MAXCaptionAppearancePrefSetWindowColor(void);
void* MAXCaptionAppearancePrefSetWindowOpacity(void);
void* MAXCaptionAppearancePrefSetWindowRoundedCornerRadius(void);
void* MAXCaptionAppearanceSetPreferredLanguage(void);
+#include <CoreFoundation/CFString.h>
+
void* MAXCaptionAppearanceShowCaptionsWhenAvailable(void);
+extern const CFStringRef kMACaptionAppearanceSettingsChangedNotification;
+
#endif
diff --git a/src/frameworks/MediaAccessibility/src/MediaAccessibility.c b/src/frameworks/MediaAccessibility/src/MediaAccessibility.c
index d2b138594..62cc3c784 100644
--- a/src/frameworks/MediaAccessibility/src/MediaAccessibility.c
+++ b/src/frameworks/MediaAccessibility/src/MediaAccessibility.c
@@ -19,11 +19,14 @@
#include <MediaAccessibility/MediaAccessibility.h>
+#include <CoreFoundation/CoreFoundation.h>
#include <stdlib.h>
#include <stdio.h>
static int verbose = 0;
+const CFStringRef kMACaptionAppearanceSettingsChangedNotification = CFSTR("kMACaptionAppearanceSettingsChangedNotification");
+
__attribute__((constructor))
static void initme(void) {
verbose = getenv("STUB_VERBOSE") != NULL;
diff --git a/src/frameworks/OpenDirectory/CMakeLists.txt b/src/frameworks/OpenDirectory/CMakeLists.txt
index b20ad7782..6f8d36d02 100644
--- a/src/frameworks/OpenDirectory/CMakeLists.txt
+++ b/src/frameworks/OpenDirectory/CMakeLists.txt
@@ -21,6 +21,7 @@ add_framework(OpenDirectory
SOURCES
src/OpenDirectory.m
+ src/ODLocalDirectory.m
src/NSODSession.m
src/ODSession.m
src/NSODNode.m
diff --git a/src/frameworks/OpenDirectory/src/ODNode.m b/src/frameworks/OpenDirectory/src/ODNode.m
index 24243f915..5731f9783 100644
--- a/src/frameworks/OpenDirectory/src/ODNode.m
+++ b/src/frameworks/OpenDirectory/src/ODNode.m
@@ -19,7 +19,89 @@
#import <OpenDirectory/ODNode.h>
+#import "ODLocalDirectory.h"
+
@implementation ODNode
+{
+ NSString *_nodeName;
+}
+
++ (instancetype)nodeWithSession:(ODSession *)session type:(ODNodeType)type error:(NSError **)error
+{
+ return [[[self alloc] initWithSession: session type: type error: error] autorelease];
+}
+
++ (instancetype)nodeWithSession:(ODSession *)session name:(NSString *)name error:(NSError **)error
+{
+ return [[[self alloc] initWithSession: session name: name error: error] autorelease];
+}
+
+- (instancetype)initWithSession:(ODSession *)session type:(ODNodeType)type error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ if ((self = [super init]) != nil)
+ _nodeName = [ODLocalNodeNameForType(type) copy];
+ return self;
+}
+
+- (instancetype)initWithSession:(ODSession *)session name:(NSString *)name error:(NSError **)error
+{
+ NSString *known = ODLocalNodeNameForName(name);
+
+ if (error != NULL)
+ *error = known != nil ? nil : ODLocalError(kODErrorNodeUnknownName);
+ if (known == nil) {
+ [self release];
+ return nil;
+ }
+ if ((self = [super init]) != nil)
+ _nodeName = [known copy];
+ return self;
+}
+
+- (void)dealloc
+{
+ [_nodeName release];
+ [super dealloc];
+}
+
+- (NSString *)nodeName
+{
+ return _nodeName;
+}
+
+- (NSArray *)subnodeNamesAndReturnError:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ return [_nodeName isEqualToString: ODLocalNodeName] ? @[] : @[ODLocalNodeName];
+}
+
+- (NSArray *)unreachableSubnodeNamesAndReturnError:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ return nil;
+}
+
+- (NSArray *)supportedRecordTypesAndReturnError:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ return @[kODRecordTypeUsers];
+}
+
+- (ODRecord *)recordWithRecordType:(ODRecordType)recordType name:(NSString *)name attributes:(id)attributes error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ if (!ODLocalRecordTypesIncludeUsers(recordType))
+ return nil;
+
+ NSDictionary *user = ODLocalUserAttributes(name);
+ return user != nil ? [ODLocalRecordCreate(user) autorelease] : nil;
+}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
diff --git a/src/frameworks/OpenDirectory/src/ODQuery.m b/src/frameworks/OpenDirectory/src/ODQuery.m
index 5b0361e37..91e5b3ebb 100644
--- a/src/frameworks/OpenDirectory/src/ODQuery.m
+++ b/src/frameworks/OpenDirectory/src/ODQuery.m
@@ -19,7 +19,143 @@
#import <OpenDirectory/ODQuery.h>
+#import <OpenDirectory/OpenDirectory.h>
+#import "ODLocalDirectory.h"
+
@implementation ODQuery
+{
+ BOOL _users;
+ NSString *_attribute;
+ ODMatchType _matchType;
+ NSArray *_queryValues;
+ NSInteger _maximumResults;
+}
+
++ (ODQuery *)queryWithNode:(ODNode *)node forRecordTypes:(id)recordTypeOrList attribute:(ODAttributeType)attribute
+ matchType:(ODMatchType)matchType queryValues:(id)queryValueOrList
+ returnAttributes:(id)returnAttributeOrList maximumResults:(NSInteger)maximumResults error:(NSError **)error
+{
+ return [[[self alloc] initWithNode: node forRecordTypes: recordTypeOrList attribute: attribute
+ matchType: matchType queryValues: queryValueOrList
+ returnAttributes: returnAttributeOrList maximumResults: maximumResults error: error] autorelease];
+}
+
+- (instancetype)initWithNode:(ODNode *)node forRecordTypes:(id)recordTypeOrList attribute:(ODAttributeType)attribute
+ matchType:(ODMatchType)matchType queryValues:(id)queryValueOrList
+ returnAttributes:(id)returnAttributeOrList maximumResults:(NSInteger)maximumResults error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ if (node == nil) {
+ if (error != NULL)
+ *error = ODLocalError(kODErrorNodeUnknownName);
+ [self release];
+ return nil;
+ }
+ if ((self = [super init]) != nil) {
+ NSMutableArray *values = [NSMutableArray array];
+ NSArray *given = [queryValueOrList isKindOfClass: [NSArray class]] ? queryValueOrList
+ : queryValueOrList != nil ? @[queryValueOrList] : @[];
+
+ for (id value in given) {
+ if ([value isKindOfClass: [NSData class]])
+ value = [[[NSString alloc] initWithData: value encoding: NSUTF8StringEncoding] autorelease];
+ if ([value isKindOfClass: [NSString class]])
+ [values addObject: value];
+ }
+
+ _users = ODLocalRecordTypesIncludeUsers(recordTypeOrList);
+ _attribute = [attribute copy];
+ _matchType = matchType;
+ _queryValues = [values copy];
+ _maximumResults = maximumResults;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [_attribute release];
+ [_queryValues release];
+ [super dealloc];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ ODQuery *copy = [[ODQuery allocWithZone: zone] init];
+
+ if (copy != nil) {
+ copy->_users = _users;
+ copy->_attribute = [_attribute copy];
+ copy->_matchType = _matchType;
+ copy->_queryValues = [_queryValues copy];
+ copy->_maximumResults = _maximumResults;
+ }
+ return copy;
+}
+
+- (BOOL)_value:(NSString *)value matches:(NSString *)wanted
+{
+ NSStringCompareOptions options = (_matchType & 0x0100) ? NSCaseInsensitiveSearch : 0;
+
+ switch (_matchType & ~0x0100) {
+ case kODMatchEqualTo:
+ return [value compare: wanted options: options] == NSOrderedSame;
+ case kODMatchBeginsWith:
+ return [value rangeOfString: wanted options: options | NSAnchoredSearch].location != NSNotFound;
+ case kODMatchEndsWith:
+ return [value rangeOfString: wanted options: options | NSAnchoredSearch | NSBackwardsSearch].location != NSNotFound;
+ case kODMatchContains:
+ return [value rangeOfString: wanted options: options].location != NSNotFound;
+ default:
+ return NO;
+ }
+}
+
+- (BOOL)_matches:(NSDictionary *)user
+{
+ if (_matchType == kODMatchAny)
+ return YES;
+
+ for (NSString *value in user[_attribute]) {
+ for (NSString *wanted in _queryValues) {
+ if ([self _value: value matches: wanted])
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (NSArray *)resultsAllowingPartial:(BOOL)allowPartial error:(NSError **)error
+{
+ NSMutableArray *results = [NSMutableArray array];
+ NSArray *candidates = @[];
+
+ if (error != NULL)
+ *error = nil;
+ if (!_users || (_attribute == nil && _matchType != kODMatchAny))
+ return results;
+
+ if (_matchType == kODMatchEqualTo && [_attribute isEqualToString: kODAttributeTypeRecordName]) {
+ NSMutableArray *named = [NSMutableArray array];
+ for (NSString *name in _queryValues) {
+ NSDictionary *user = ODLocalUserAttributes(name);
+ if (user != nil)
+ [named addObject: user];
+ }
+ candidates = named;
+ } else {
+ candidates = ODLocalAllUserAttributes();
+ }
+
+ for (NSDictionary *user in candidates) {
+ if (_maximumResults > 0 && [results count] >= (NSUInteger)_maximumResults)
+ break;
+ if ([self _matches: user])
+ [results addObject: [ODLocalRecordCreate(user) autorelease]];
+ }
+ return results;
+}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
diff --git a/src/frameworks/OpenDirectory/src/ODRecord.m b/src/frameworks/OpenDirectory/src/ODRecord.m
index 5e207c4bb..5c7bd9a14 100644
--- a/src/frameworks/OpenDirectory/src/ODRecord.m
+++ b/src/frameworks/OpenDirectory/src/ODRecord.m
@@ -19,7 +19,63 @@
#import <OpenDirectory/ODRecord.h>
+#import "ODLocalDirectory.h"
+
@implementation ODRecord
+{
+ NSDictionary *_attributes;
+}
+
+ODRecord *ODLocalRecordCreate(NSDictionary *attributes)
+{
+ ODRecord *record = [[ODRecord alloc] init];
+
+ if (record != nil)
+ record->_attributes = [attributes copy];
+ return record;
+}
+
+- (void)dealloc
+{
+ [_attributes release];
+ [super dealloc];
+}
+
+- (NSString *)recordType
+{
+ return [_attributes[@"dsAttrTypeStandard:RecordType"] firstObject];
+}
+
+- (NSString *)recordName
+{
+ return [_attributes[@"dsAttrTypeStandard:RecordName"] firstObject];
+}
+
+- (NSArray *)valuesForAttribute:(ODAttributeType)attribute error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ if (attribute == nil)
+ return nil;
+ return _attributes[attribute] ?: @[];
+}
+
+- (NSDictionary *)recordDetailsForAttributes:(NSArray *)attributes error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ if (attributes == nil || [attributes containsObject: @"dsAttributesAll"]
+ || [attributes containsObject: @"dsAttributesStandardAll"])
+ return _attributes;
+
+ NSMutableDictionary *details = [NSMutableDictionary dictionary];
+ for (id attribute in attributes) {
+ NSArray *values = _attributes[attribute];
+ if (values != nil)
+ details[attribute] = values;
+ }
+ return details;
+}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
diff --git a/src/frameworks/OpenDirectory/src/ODSession.m b/src/frameworks/OpenDirectory/src/ODSession.m
index aff236828..c9e628d43 100644
--- a/src/frameworks/OpenDirectory/src/ODSession.m
+++ b/src/frameworks/OpenDirectory/src/ODSession.m
@@ -21,8 +21,48 @@
#import <Foundation/Foundation.h>
+#import "ODLocalDirectory.h"
+
@implementation ODSession
++ (ODSession *)defaultSession
+{
+ static ODSession *shared;
+ static dispatch_once_t once;
+
+ dispatch_once(&once, ^{
+ shared = [[ODSession alloc] init];
+ });
+ return shared;
+}
+
++ (instancetype)sessionWithOptions:(NSDictionary *)options error:(NSError **)error
+{
+ return [[[self alloc] initWithOptions: options error: error] autorelease];
+}
+
+- (instancetype)initWithOptions:(NSDictionary *)options error:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+
+ // only the local directory exists; there is no proxy to connect through
+ if ([options count] != 0) {
+ if (error != NULL)
+ *error = ODLocalError(kODErrorSessionProxyCommunicationError);
+ [self release];
+ return nil;
+ }
+ return [self init];
+}
+
+- (NSArray *)nodeNamesAndReturnError:(NSError **)error
+{
+ if (error != NULL)
+ *error = nil;
+ return @[ODLocalNodeName];
+}
+
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
diff --git a/src/frameworks/OpenGL/CMakeLists.txt b/src/frameworks/OpenGL/CMakeLists.txt
index deca16b87..d75d5e22d 100644
--- a/src/frameworks/OpenGL/CMakeLists.txt
+++ b/src/frameworks/OpenGL/CMakeLists.txt
@@ -31,9 +31,14 @@ add_framework(OpenGL
system
GL
GLU
- EGL
+ # darling-lol M57bo: EGL is deliberately NOT a link dependency. Linking
+ # it here puts the native Mesa EGL in the dependency symbol scope of
+ # OpenGL.framework's dependents (ANGLE's libGLESv2), so Chromium's EGL
+ # lookups resolve to the native EGL instead of ANGLE's. OpenGL.c loads
+ # it privately with dlopen(RTLD_LOCAL) instead.
CoreFoundation
CoreGraphics
+ IOSurface
)
set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS
diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c
index ffd7fe054..a74ffc63f 100644
--- a/src/frameworks/OpenGL/OpenGL.c
+++ b/src/frameworks/OpenGL/OpenGL.c
@@ -2,11 +2,22 @@
#include <stdio.h> // for fprintf(stderr, "unimplemented")
#include <pthread.h>
-
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <OpenGL/DarlingNativePresent.h>
+extern long __lolsys_syscall(long,long,long,long,long,long,int);
+
+#define GL_GLEXT_PROTOTYPES 1
#include <OpenGL/OpenGL.h>
+#include <OpenGL/glext.h>
#include <OpenGL/CGLInternal.h>
#include <CoreFoundation/CFDictionary.h>
+#include <IOSurface/IOSurface.h>
#include <pthread.h>
+#include <time.h>
// Try to get the right (generic) type definitions.
// In particular, we really want EGLNativeDisplayType to be void *,
@@ -22,8 +33,73 @@
#define APPLE
#define __APPLE__
+/* darling-lol M57bo: load the native EGL privately.
+ *
+ * OpenGL.framework is a load-time dependency of ANGLE's libGLESv2 (its GL
+ * backend). If the native EGL were linked here, its egl* symbols would land in
+ * libGLESv2's dependency symbol scope and satisfy Chromium's EGL lookups, so
+ * Chromium would pair Darling's Mesa EGL with ANGLE's GL - eglMakeCurrent would
+ * never make an ANGLE gl::Context current (GL_MAX_VERTEX_ATTRIBS = 0, no
+ * frames). Resolving EGL through a private RTLD_LOCAL dlopen keeps it out of
+ * dependents' scope, so Chromium falls back to ANGLE's own EGL. */
+#include <dlfcn.h>
+
+static EGLDisplay (*p_eglGetDisplay)(EGLNativeDisplayType);
+static EGLDisplay (*p_eglGetPlatformDisplay)(EGLenum, void *, const EGLAttrib *);
+static EGLBoolean (*p_eglInitialize)(EGLDisplay, EGLint *, EGLint *);
+static EGLBoolean (*p_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
+static EGLBoolean (*p_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
+static EGLBoolean (*p_eglChooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
+static EGLBoolean (*p_eglBindAPI)(EGLenum);
+static EGLSurface (*p_eglCreateWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
+static EGLBoolean (*p_eglDestroySurface)(EGLDisplay, EGLSurface);
+static EGLBoolean (*p_eglMakeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
+static EGLContext (*p_eglCreateContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
+static EGLBoolean (*p_eglDestroyContext)(EGLDisplay, EGLContext);
+static EGLBoolean (*p_eglSwapBuffers)(EGLDisplay, EGLSurface);
+static EGLBoolean (*p_eglSwapInterval)(EGLDisplay, EGLint);
+
+static int native_egl_loaded;
+static pthread_mutex_t native_egl_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void ensure_native_egl(void) {
+ if (native_egl_loaded)
+ return;
+ pthread_mutex_lock(&native_egl_mutex);
+ if (!native_egl_loaded) {
+ void *h = dlopen("/usr/lib/native/libEGL.dylib", RTLD_NOW | RTLD_LOCAL);
+ if (h) {
+ p_eglGetDisplay = (EGLDisplay (*)(EGLNativeDisplayType)) dlsym(h, "eglGetDisplay");
+ p_eglGetPlatformDisplay = (EGLDisplay (*)(EGLenum, void *, const EGLAttrib *)) dlsym(h, "eglGetPlatformDisplay");
+ p_eglInitialize = (EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *)) dlsym(h, "eglInitialize");
+ p_eglQuerySurface=(EGLBoolean(*)(EGLDisplay,EGLSurface,EGLint,EGLint*))dlsym(h,"eglQuerySurface");
+ p_eglGetConfigAttrib=(EGLBoolean(*)(EGLDisplay,EGLConfig,EGLint,EGLint*))dlsym(h,"eglGetConfigAttrib");
+ p_eglChooseConfig = (EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *)) dlsym(h, "eglChooseConfig");
+ p_eglBindAPI = (EGLBoolean (*)(EGLenum)) dlsym(h, "eglBindAPI");
+ p_eglCreateWindowSurface = (EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *)) dlsym(h, "eglCreateWindowSurface");
+ p_eglDestroySurface = (EGLBoolean (*)(EGLDisplay, EGLSurface)) dlsym(h, "eglDestroySurface");
+ p_eglMakeCurrent = (EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext)) dlsym(h, "eglMakeCurrent");
+ p_eglCreateContext = (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *)) dlsym(h, "eglCreateContext");
+ p_eglDestroyContext = (EGLBoolean (*)(EGLDisplay, EGLContext)) dlsym(h, "eglDestroyContext");
+ p_eglSwapBuffers = (EGLBoolean (*)(EGLDisplay, EGLSurface)) dlsym(h, "eglSwapBuffers");
+ p_eglSwapInterval = (EGLBoolean (*)(EGLDisplay, EGLint)) dlsym(h, "eglSwapInterval");
+ native_egl_loaded = 1;
+ }
+ }
+ pthread_mutex_unlock(&native_egl_mutex);
+}
+
static EGLDisplay display;
+static EGLDisplay x11EGLDisplay(EGLNativeDisplayType native_display) {
+ /* CGS/AppKit supply Xlib Display pointers. Mesa's automatic platform
+ * selection can choose Wayland on a Wayland desktop and interpret the
+ * Xlib pointer as a wl_display, crashing during eglInitialize. */
+ if (p_eglGetPlatformDisplay)
+ return p_eglGetPlatformDisplay(0x31D5 /* EGL_PLATFORM_X11 */, native_display, NULL);
+ return p_eglGetDisplay(native_display);
+}
+
static EGLConfig config;
static int num_config;
@@ -48,10 +124,18 @@ struct _CGLContextObj {
GLuint retain_count;
pthread_mutex_t lock;
EGLContext egl_context;
+ EGLDisplay egl_display;
+ EGLConfig egl_config;
EGLSurface egl_surface;
// EGL has no function for getting the current swap interval,
// so we need to save the last set value. The default is 1.
int swap_interval;
+ EGLSurface native_present_surface;
+ EGLContext native_present_context;
+ DarlingNativePresentTarget *native_present_target;
+ uint32_t native_present_window;
+ uint64_t native_next_present_ns;
+ struct DarlingAsyncPresent *native_async;
};
struct _CGLPixelFormatObj {
@@ -59,6 +143,24 @@ struct _CGLPixelFormatObj {
CGLPixelFormatAttribute *attributes;
};
+typedef struct {
+ IOSurfaceID surfaceID;
+ CGLContextObj context;
+ GLenum target;
+ GLenum format;
+ GLenum type;
+ GLuint texture;
+ GLuint plane;
+ IOSurfaceRef surface; /* non-owning, used only to invalidate this binding */
+} CGLIOSurfaceBinding;
+
+static void stopAsyncPresent(CGLContextObj context);
+
+static CGLIOSurfaceBinding initialSurfaceBindings[256];
+static CGLIOSurfaceBinding *ioSurfaceBindings = initialSurfaceBindings;
+static unsigned ioSurfaceBindingCount, ioSurfaceBindingCapacity = 256;
+static pthread_mutex_t ioSurfaceBindingMutex = PTHREAD_MUTEX_INITIALIZER;
+
static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
switch (attr) {
case kCGLPFAAuxBuffers:
@@ -69,6 +171,9 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
case kCGLPFAAccumSize:
case kCGLPFARendererID:
case kCGLPFADisplayMask:
+ case kCGLPFAOpenGLProfile:
+ case kCGLPFASampleBuffers:
+ case kCGLPFASamples:
return 1;
default:
return 0;
@@ -78,6 +183,7 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
__attribute__((constructor))
static void _CGLInitialize(void)
{
+ ensure_native_egl();
g_displays = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
}
@@ -93,41 +199,71 @@ static int attributes_count(const CGLPixelFormatAttribute *attrs) {
CGLError CGLRegisterNativeDisplay(void *native_display) {
- display = eglGetDisplay(native_display);
+ ensure_native_egl();
+ display = x11EGLDisplay(native_display);
if (display == EGL_NO_DISPLAY) {
return kCGLBadConnection;
}
- eglInitialize(display, NULL, NULL);
- eglChooseConfig(display, attribute_list, &config, 1, &num_config);
+ p_eglInitialize(display, NULL, NULL);
+ p_eglChooseConfig(display, attribute_list, &config, 1, &num_config);
- eglBindAPI(EGL_OPENGL_API);
+ p_eglBindAPI(EGL_OPENGL_API);
return kCGLNoError;
}
+/* darling-lol M57: processes that create a CGL context without first calling
+ * CGLRegisterNativeDisplay (e.g. Chromium's GPU process) previously got
+ * display==0/config==0 and CGLCreateContext always failed. Lazily initialise a
+ * default EGL display so context creation can proceed. */
+static void ensureDefaultDisplay(void) {
+ ensure_native_egl();
+ if (display != EGL_NO_DISPLAY && display != NULL && config != NULL)
+ return;
+
+ void *nativeDisplay=_CGSNativeDisplay(CGSMainConnectionID());
+ EGLDisplay disp = x11EGLDisplay(nativeDisplay?nativeDisplay:EGL_DEFAULT_DISPLAY);
+ if (disp == EGL_NO_DISPLAY) {
+ fprintf(stderr, "M58DG ensureDefaultDisplay: eglGetDisplay(DEFAULT)=NO_DISPLAY (p_eglGetDisplay=%p)\n", (void*) p_eglGetDisplay);
+ return;
+ }
+
+ EGLConfig cfg = NULL;
+ EGLBoolean m58dg_ini = p_eglInitialize(disp, NULL, NULL);
+ EGLBoolean m58dg_ch = p_eglChooseConfig(disp, attribute_list, &cfg, 1, &num_config);
+ fprintf(stderr, "M58DG ensureDefaultDisplay: disp=%p eglInitialize=%d eglChooseConfig=%d cfg=%p num_config=%d\n",
+ (void*) disp, (int) m58dg_ini, (int) m58dg_ch, (void*) cfg, num_config);
+ if (m58dg_ini && m58dg_ch && cfg != NULL) {
+ display = disp;
+ config = cfg;
+ p_eglBindAPI(EGL_OPENGL_API);
+ }
+}
+
static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
{
struct _CGLDisplay* rv;
+ ensure_native_egl();
pthread_mutex_lock(&g_displaysMutex);
rv = (struct _CGLDisplay*) CFDictionaryGetValue(g_displays, (const void*)(unsigned long) cid);
pthread_mutex_unlock(&g_displaysMutex);
if (!rv)
{
- EGLDisplay disp = eglGetDisplay(_CGSNativeDisplay(cid));
+ EGLDisplay disp = x11EGLDisplay(_CGSNativeDisplay(cid));
if (disp == EGL_NO_DISPLAY)
return NULL;
rv = (struct _CGLDisplay*) malloc(sizeof(*rv));
rv->display = disp;
- eglInitialize(rv->display, NULL, NULL);
- eglChooseConfig(rv->display, attribute_list, &rv->config, 1, &rv->num_config);
+ p_eglInitialize(rv->display, NULL, NULL);
+ p_eglChooseConfig(rv->display, attribute_list, &rv->config, 1, &rv->num_config);
- eglBindAPI(EGL_OPENGL_API);
+ p_eglBindAPI(EGL_OPENGL_API);
pthread_mutex_lock(&g_displaysMutex);
CFDictionaryAddValue(g_displays, (const void*)(unsigned long) cid, rv);
@@ -139,6 +275,8 @@ static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid)
{
+ if (!gl)
+ return kCGLBadContext;
struct _CGLDisplay* disp = getCGLDisplay(cid);
if (!disp)
return kCGLBadConnection;
@@ -152,7 +290,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
if (!window)
return kCGLBadWindow;
- gl->egl_surface = eglCreateWindowSurface(disp->display, disp->config, window, NULL);
+ gl->egl_surface = p_eglCreateWindowSurface(gl->egl_display, gl->egl_config, window, NULL);
if (gl->egl_surface == EGL_NO_SURFACE)
return kCGLBadState;
return kCGLNoError;
@@ -161,7 +299,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
CGLWindowRef CGLGetWindow(void *native_window) {
EGLNativeWindowType window = (EGLNativeWindowType) native_window;
- EGLSurface surface = eglCreateWindowSurface(display, config, window, NULL);
+ EGLSurface surface = p_eglCreateWindowSurface(display, config, window, NULL);
if (surface == EGL_NO_SURFACE) {
return NULL;
@@ -171,14 +309,18 @@ CGLWindowRef CGLGetWindow(void *native_window) {
}
CGL_EXPORT void CGLDestroyWindow(CGLWindowRef window) {
- eglDestroySurface(display, (EGLSurface) window);
+ p_eglDestroySurface(display, (EGLSurface) window);
}
CGL_EXPORT CGLError CGLContextMakeCurrentAndAttachToWindow(CGLContextObj context, CGLWindowRef window) {
if (!context)
return kCGLBadContext;
+ EGLSurface oldSurface = context->egl_surface;
context->egl_surface = (EGLSurface) window;
- return CGLSetCurrentContext(context);
+ CGLError result = CGLSetCurrentContext(context);
+ if (result != kCGLNoError)
+ context->egl_surface = oldSurface;
+ return result;
}
static pthread_key_t current_context_key;
@@ -198,18 +340,42 @@ CGLContextObj CGLGetCurrentContext(void) {
return pthread_getspecific(key);
}
+/* darling-lol M57: CGLGetVirtualScreen/CGLSetVirtualScreen are used by
+ * Chromium's CGL backend; Darling's OpenGL did not provide them (lazy-bind
+ * abort: "Symbol not found: _CGLGetVirtualScreen"). A single virtual screen. */
+CGLError CGLGetVirtualScreen(CGLContextObj context, GLint *screen) {
+ if (!context) return kCGLBadContext;
+ if (!screen) return kCGLBadAddress;
+ *screen = 0;
+ return kCGLNoError;
+}
+
+CGLError CGLSetVirtualScreen(CGLContextObj context, GLint screen) {
+ if (!context) return kCGLBadContext;
+ return screen == 0 ? kCGLNoError : kCGLBadValue;
+}
+
CGLError CGLSetCurrentContext(CGLContextObj context) {
+ ensure_native_egl();
pthread_key_t key = get_current_context_key();
- pthread_setspecific(key, context);
+ CGLContextObj old = pthread_getspecific(key);
+ EGLDisplay currentDisplay = context ? context->egl_display : old ? old->egl_display : display;
+ if (!p_eglMakeCurrent)
+ return kCGLBadConnection;
+ if (!context && !old && !currentDisplay)
+ return kCGLNoError;
+ EGLBoolean success;
if (context != NULL) {
EGLSurface surface = context->egl_surface;
- eglMakeCurrent(display, surface, surface, context->egl_context);
+ success = p_eglMakeCurrent(currentDisplay, surface, surface, context->egl_context);
} else {
- eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ success = p_eglMakeCurrent(currentDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
-
- return kCGLNoError; // FIXME
+ if (!success)
+ return kCGLBadState;
+ pthread_setspecific(key, context);
+ return kCGLNoError;
}
CGLError CGLSetFullScreen(CGLContextObj ctx) {
@@ -223,11 +389,13 @@ CGLError CGLChoosePixelFormat(
CGLPixelFormatObj *result,
GLint *number_of_screens
) {
+ if (!attrs || !result || !number_of_screens)
+ return kCGLBadAddress;
CGLPixelFormatObj format = malloc(sizeof(struct _CGLPixelFormatObj));
int count = attributes_count(attrs);
format->retain_count = 1;
- format->attributes = malloc(sizeof(CGLPixelFormatAttribute) * count);
+ format->attributes = calloc(count + 1, sizeof(CGLPixelFormatAttribute));
for (int i = 0; i < count; i++) {
format->attributes[i] = attrs[i];
}
@@ -251,6 +419,10 @@ CGLError CGLDescribePixelFormat(
CGLPixelFormatAttribute attr,
GLint *value
) {
+ if (!format) return kCGLBadPixelFormat;
+ if (!value) return kCGLBadAddress;
+ if (sreen_num != 0) return kCGLBadValue;
+ if (attr == kCGLPFAVirtualScreenCount) { *value = 1; return kCGLNoError; }
for (int i = 0; format->attributes[i] != 0; i++) {
int has_arg = attribute_has_argument(format->attributes[i]);
@@ -304,24 +476,67 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) {
}
CGLError CGLCreateContext(CGLPixelFormatObj pixelFormat, CGLContextObj share, CGLContextObj *resultp) {
+ /* Cocotron window compositing requests the default format with NULL. */
+ if (!resultp)
+ return kCGLBadAddress;
+ *resultp = NULL;
+ ensureDefaultDisplay();
+ /* eglBindAPI is thread-local, even when the display is already initialized. */
+ if (!display || !config || !p_eglBindAPI || !p_eglBindAPI(EGL_OPENGL_API))
+ return kCGLBadConnection;
EGLContext egl_share = EGL_NO_CONTEXT;
+ EGLDisplay contextDisplay = share ? share->egl_display : display;
+ EGLConfig contextConfig = share ? share->egl_config : config;
if (share != NULL) {
egl_share = share->egl_context;
}
- EGLContext egl_context = eglCreateContext(display, config, egl_share, NULL);
+ GLint profile = 0;
+ if (pixelFormat)
+ CGLDescribePixelFormat(pixelFormat, 0, kCGLPFAOpenGLProfile, &profile);
+ EGLint contextAttributes[] = {
+ 0x3098 /* EGL_CONTEXT_MAJOR_VERSION */, 3,
+ 0x30FB /* EGL_CONTEXT_MINOR_VERSION */, 2,
+ 0x30FD /* EGL_CONTEXT_OPENGL_PROFILE_MASK */, 1 /* CORE_PROFILE_BIT */,
+ EGL_NONE
+ };
+ if (profile == 0x4100) {
+ contextAttributes[1] = 4;
+ contextAttributes[3] = 1;
+ } else if (profile && profile != 0x1000 && profile != 0x3200) {
+ return kCGLBadValue;
+ }
+ EGLContext egl_context = p_eglCreateContext(contextDisplay, contextConfig, egl_share,
+ profile == 0x3200 || profile == 0x4100 ? contextAttributes : NULL);
if (egl_context == EGL_NO_CONTEXT) {
+ fprintf(stderr, "M58DG CGLCreateContext FAILED: display=%p config=%p share=%p p_eglCreateContext=%p\n",
+ (void*) display, (void*) config, (void*) egl_share, (void*) p_eglCreateContext);
return kCGLBadContext;
}
+ {
+ static int m58dg_ok = 0;
+ if (m58dg_ok++ < 5)
+ fprintf(stderr, "M58DG CGLCreateContext OK display=%p config=%p ctx=%p\n",
+ (void*) display, (void*) config, (void*) egl_context);
+ }
+
CGLContextObj context = malloc(sizeof(struct _CGLContextObj));
context->retain_count = 1;
pthread_mutex_init(&(context->lock), NULL);
context->egl_context = egl_context;
+ context->egl_display = contextDisplay;
+ context->egl_config = contextConfig;
context->egl_surface = NULL;
context->swap_interval = 1;
+ context->native_present_surface=EGL_NO_SURFACE;
+ context->native_present_context=EGL_NO_CONTEXT;
+ context->native_present_target=NULL;
+ context->native_present_window=0;
+ context->native_next_present_ns=0;
+ context->native_async=NULL;
*resultp = context;
@@ -333,7 +548,7 @@ CGLContextObj CGLRetainContext(CGLContextObj context) {
return NULL;
}
- context->retain_count++;
+ __sync_add_and_fetch(&context->retain_count, 1);
return context;
}
@@ -342,11 +557,17 @@ void CGLReleaseContext(CGLContextObj context) {
return;
}
- context->retain_count--;
-
- if (context->retain_count != 0) {
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
+ if (__sync_sub_and_fetch(&context->retain_count, 1) != 0) {
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
return;
}
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ) {
+ if (ioSurfaceBindings[i].context == context)
+ ioSurfaceBindings[i] = ioSurfaceBindings[--ioSurfaceBindingCount];
+ else ++i;
+ }
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
if (CGLGetCurrentContext() == context) {
CGLSetCurrentContext(NULL);
@@ -354,7 +575,11 @@ void CGLReleaseContext(CGLContextObj context) {
pthread_mutex_destroy(&(context->lock));
- eglDestroyContext(display, context->egl_context);
+ stopAsyncPresent(context);
+ if(context->native_present_context!=EGL_NO_CONTEXT)p_eglDestroyContext(context->egl_display,context->native_present_context);
+ if(context->native_present_surface!=EGL_NO_SURFACE)p_eglDestroySurface(context->egl_display,context->native_present_surface);
+ if(context->native_present_target)__lolsys_syscall((long)context->native_present_target,sizeof(DarlingNativePresentTarget),0,0,0,0,11);
+ p_eglDestroyContext(context->egl_display, context->egl_context);
free(context);
}
@@ -383,9 +608,329 @@ CGLError CGLUnlockContext(CGLContextObj context) {
return kCGLNoError;
}
+static int swapIntervalRequested;
+
CGLError CGLFlushDrawable(CGLContextObj context) {
- eglSwapBuffers(display,context->egl_surface);
- return kCGLNoError;
+ if (!context)
+ return kCGLBadContext;
+ /* darling-lol: EGL defaults to a swap interval of 1. When the window is covered or the compositor throttles it
+ * (Xwayland answers about once a second), every swap blocks that long and the game's frame rate collapses to ~1 fps.
+ * macOS apps get an unthrottled flush unless they ask for vsync through kCGLCPSwapInterval, which is handled in
+ * CGLSetParameter; apply "no vsync" once per context here, while it is current. */
+ static CGLContextObj swapIntervalContext;
+ if (!swapIntervalRequested && context != swapIntervalContext && p_eglSwapInterval && CGLGetCurrentContext() == context) {
+ p_eglSwapInterval(context->egl_display, 0);
+ swapIntervalContext = context;
+ }
+ return p_eglSwapBuffers(context->egl_display, context->egl_surface) ? kCGLNoError : kCGLBadState;
+}
+
+static GLenum textureBindingEnum(GLenum target) {
+ switch (target) {
+ case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D;
+#ifdef GL_TEXTURE_RECTANGLE
+ case GL_TEXTURE_RECTANGLE: return GL_TEXTURE_BINDING_RECTANGLE;
+#endif
+ default: return 0;
+ }
+}
+
+/* macOS exposes IOSurfaces to CGL as texture storage. Mesa/EGL does not have
+ * that CGL entry point, but Chromium only needs the pixels to reach the bound
+ * texture. Upload from the shared IOSurface mapping synchronously; this also
+ * preserves the cross-process IOSurface transport implemented by our shim. */
+static uint64_t nativeMonotonicNS(void);
+static uint32_t nativeUploadCount, nativeUploadMicroseconds;
+CGLError CGLTexImageIOSurface2D(CGLContextObj context, GLenum target,
+ GLenum internalFormat, GLsizei width, GLsizei height, GLenum format,
+ GLenum type, IOSurfaceRef ioSurface, GLuint plane)
+{
+ if (!context)
+ return kCGLBadContext;
+ if (!ioSurface)
+ return kCGLBadAddress;
+
+ void *pixels = IOSurfaceGetBaseAddressOfPlane(ioSurface, plane);
+ size_t bytesPerRow = IOSurfaceGetBytesPerRowOfPlane(ioSurface, plane);
+ size_t bytesPerElement = IOSurfaceGetBytesPerElementOfPlane(ioSurface, plane);
+ if (!pixels || !bytesPerRow || !bytesPerElement)
+ return kCGLBadValue;
+
+ CGLContextObj oldContext = CGLGetCurrentContext();
+ if (oldContext != context) {
+ CGLError result = CGLSetCurrentContext(context);
+ if (result != kCGLNoError)
+ return result;
+ }
+
+ uint64_t uploadStarted = nativeMonotonicNS();
+ GLint oldRowLength = 0;
+ GLint oldAlignment = 0;
+ GLint boundTexture = 0;
+ /* ANGLE can leave an unrelated probe error pending before calling this
+ * CGL API. CGLTexImageIOSurface2D reports only its own operation. */
+ while (glGetError() != GL_NO_ERROR) {}
+ glGetIntegerv(GL_UNPACK_ROW_LENGTH, &oldRowLength);
+ glGetIntegerv(GL_UNPACK_ALIGNMENT, &oldAlignment);
+ GLenum bindingEnum = textureBindingEnum(target);
+ if (bindingEnum)
+ glGetIntegerv(bindingEnum, &boundTexture);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)(bytesPerRow / bytesPerElement));
+ GLint allocatedWidth=0,allocatedHeight=0,allocatedFormat=0;
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_WIDTH,&allocatedWidth);
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_HEIGHT,&allocatedHeight);
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_INTERNAL_FORMAT,&allocatedFormat);
+ if(allocatedWidth==width && allocatedHeight==height && allocatedFormat==(GLint)internalFormat)
+ glTexSubImage2D(target,0,0,0,width,height,format,type,pixels);
+ else
+ glTexImage2D(target, 0, (GLint)internalFormat, width, height, 0,
+ format, type, pixels);
+ GLenum error = glGetError();
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, oldRowLength);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlignment);
+
+ if (error == GL_NO_ERROR && boundTexture != 0) {
+ CGLIOSurfaceBinding binding = {
+ IOSurfaceGetID(ioSurface), context, target, format, type,
+ (GLuint)boundTexture, plane, ioSurface
+ };
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
+ unsigned slot = ioSurfaceBindingCount;
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ++i) {
+ if (ioSurfaceBindings[i].surfaceID == binding.surfaceID) {
+ slot = i;
+ break;
+ }
+ }
+ if (slot == ioSurfaceBindingCapacity) {
+ unsigned capacity = ioSurfaceBindingCapacity * 2;
+ CGLIOSurfaceBinding *larger = malloc(capacity * sizeof(*larger));
+ if (larger) {
+ memcpy(larger, ioSurfaceBindings, ioSurfaceBindingCount * sizeof(*larger));
+ if (ioSurfaceBindings != initialSurfaceBindings) free(ioSurfaceBindings);
+ ioSurfaceBindings = larger;
+ ioSurfaceBindingCapacity = capacity;
+ }
+ }
+ if (slot < ioSurfaceBindingCapacity) {
+ ioSurfaceBindings[slot] = binding;
+ if (slot == ioSurfaceBindingCount) ++ioSurfaceBindingCount;
+ } else error = GL_OUT_OF_MEMORY;
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
+ }
+
+ CGLError restore = kCGLNoError;
+ if (oldContext != context)
+ restore = CGLSetCurrentContext(oldContext);
+ if (error != GL_NO_ERROR)
+ fprintf(stderr, "CGLTexImageIOSurface2D glTexImage2D error=0x%x target=0x%x format=0x%x type=0x%x %dx%d\n",
+ error, target, format, type, width, height);
+ __atomic_fetch_add(&nativeUploadCount,1,__ATOMIC_RELAXED);
+ __atomic_fetch_add(&nativeUploadMicroseconds,(uint32_t)((nativeMonotonicNS()-uploadStarted)/1000),__ATOMIC_RELAXED);
+ return error == GL_NO_ERROR ? restore : kCGLBadState;
+}
+
+/* Called before IOSurface storage is destroyed; no owning surface reference. */
+void CGLForgetIOSurface(IOSurfaceRef surface) {
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ) {
+ if (ioSurfaceBindings[i].surface == surface)
+ ioSurfaceBindings[i] = ioSurfaceBindings[--ioSurfaceBindingCount];
+ else ++i;
+ }
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
+}
+
+#include "CGLAsyncPresent.inc"
+
+/* Present a flattened compositor texture directly. The native transport
+ * acknowledges suspension of CPU writes before the first GL swap. Fallback
+ * leaves the existing readback path available and restores all GL bindings. */
+CGL_EXPORT int CGLPresentIOSurface(IOSurfaceRef surface) {
+ if(!surface || !getenv("RCLOL_GPU_DIRECT_PRESENT") || IOSurfaceGetPlaneCount(surface)>1 ||
+ IOSurfaceGetBytesPerElement(surface)!=4)return 0;
+ size_t width=IOSurfaceGetWidth(surface),height=IOSurfaceGetHeight(surface);
+ if(width<256 || height<256)return 0;
+ CGLIOSurfaceBinding binding={0};IOSurfaceID id=IOSurfaceGetID(surface);
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
+ for(unsigned i=0;i<ioSurfaceBindingCount;++i)if(ioSurfaceBindings[i].surfaceID==id){
+ binding=ioSurfaceBindings[i];CGLRetainContext(binding.context);break;
+ }
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
+ if(!binding.context)return 0;
+ CGLContextObj ctx=binding.context;
+ if(CGLGetCurrentContext()!=ctx){CGLReleaseContext(ctx);return 0;}
+ if(!ctx->native_present_target){
+ char path[128];snprintf(path,sizeof(path),"/dev/shm/darling-ca-gpu-target-%u-%zu-%zu",getuid(),width,height);
+ int fd=(int)__lolsys_syscall((long)path,2,0,0,0,0,2);if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT target_open=%d uid=%u size=%zux%zu\n",fd,getuid(),width,height);if(fd>=0){
+ struct stat metadata;
+ long mapped=-1;
+ if(fstat(fd,&metadata)==0 && metadata.st_size>=sizeof(DarlingNativePresentTarget) && metadata.st_uid==getuid())
+ mapped=__lolsys_syscall(0,sizeof(DarlingNativePresentTarget),3,1,fd,0,9);
+ if(mapped>0 || mapped < -4095)ctx->native_present_target=(void*)mapped;__lolsys_syscall(fd,0,0,0,0,0,3);
+ }
+ }
+ DarlingNativePresentTarget *target=ctx->native_present_target;
+ /* A Chromium context renders several windows. An unrelated surface must
+ * use readback without stopping the active window's async presenter. */
+ if(target && __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)==DARLING_NATIVE_PRESENT_MAGIC &&
+ target->target && (target->width!=width || target->height!=height)) {
+ CGLReleaseContext(ctx);return 0;
+ }
+ if(!target || __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)!=DARLING_NATIVE_PRESENT_MAGIC ||
+ !target->target){
+ stopAsyncPresent(ctx);
+ if(target){__lolsys_syscall((long)target,sizeof(DarlingNativePresentTarget),0,0,0,0,11);ctx->native_present_target=NULL;ctx->native_present_window=0;}
+ CGLReleaseContext(ctx);return 0;
+ }
+ if(ctx->native_present_window!=target->target){
+ stopAsyncPresent(ctx);
+ if(ctx->native_present_context!=EGL_NO_CONTEXT)p_eglDestroyContext(ctx->egl_display,ctx->native_present_context);
+ if(ctx->native_present_surface!=EGL_NO_SURFACE)p_eglDestroySurface(ctx->egl_display,ctx->native_present_surface);
+ ctx->native_present_surface=EGL_NO_SURFACE;ctx->native_present_context=EGL_NO_CONTEXT;
+ EGLint attrs[]={EGL_SURFACE_TYPE,EGL_WINDOW_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_BIT,EGL_NONE};
+ EGLConfig *candidates=NULL,cfg=NULL;EGLint count=0,selectedVisual=0;
+ if(p_eglChooseConfig(ctx->egl_display,attrs,NULL,0,&count) && count>0 && count<=65536)
+ candidates=calloc(count,sizeof(*candidates));
+ if(candidates && p_eglChooseConfig(ctx->egl_display,attrs,candidates,count,&count) && p_eglGetConfigAttrib){
+ for(EGLint n=0;n<count;++n){EGLint visual=0;
+ if(p_eglGetConfigAttrib(ctx->egl_display,candidates[n],EGL_NATIVE_VISUAL_ID,&visual) && visual==(EGLint)target->visual){cfg=candidates[n];selectedVisual=visual;break;}
+ }
+ }
+ free(candidates);
+ if(cfg){
+ ctx->native_present_surface=p_eglCreateWindowSurface(ctx->egl_display,cfg,(EGLNativeWindowType)(uintptr_t)target->target,NULL);
+ EGLint contextAttrs[]={0x3098,3,0x30FB,2,0x30FD,1,EGL_NONE};
+ ctx->native_present_context=p_eglCreateContext(ctx->egl_display,cfg,ctx->egl_context,contextAttrs);
+ }
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT selected_visual=%d requested_visual=%u\n",selectedVisual,target->visual);
+ EGLint actualWidth=0,actualHeight=0;
+ if(ctx->native_present_surface!=EGL_NO_SURFACE){p_eglQuerySurface(ctx->egl_display,ctx->native_present_surface,EGL_WIDTH,&actualWidth);p_eglQuerySurface(ctx->egl_display,ctx->native_present_surface,EGL_HEIGHT,&actualHeight);}
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT native_size=%dx%d requested=%zux%zu\n",actualWidth,actualHeight,width,height);
+ ctx->native_present_window=target->target;
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT surface_created=%d shared_context=%d visual=%u target=%u\n",ctx->native_present_surface!=EGL_NO_SURFACE,ctx->native_present_context!=EGL_NO_CONTEXT,target->visual,target->target);
+ }
+ if(ctx->native_present_surface==EGL_NO_SURFACE || ctx->native_present_context==EGL_NO_CONTEXT){CGLReleaseContext(ctx);return 0;}
+ uint32_t ready=__atomic_load_n(&target->ready,__ATOMIC_ACQUIRE);
+ if(!ready){__atomic_store_n(&target->ready,1,__ATOMIC_RELEASE);
+ for(unsigned n=0;n<40 && __atomic_load_n(&target->ready,__ATOMIC_ACQUIRE)==1;++n)usleep(500);
+ if(__atomic_load_n(&target->ready,__ATOMIC_ACQUIRE)!=2){__atomic_store_n(&target->ready,0,__ATOMIC_RELEASE);CGLReleaseContext(ctx);return 0;}
+ }
+ /* Bound the producer independently of async display cadence when testing
+ * unthrottled begin frames. Keep waits on the owning GPU thread and use
+ * absolute monotonic deadlines; async staging remains bounded to three. */
+ if(getenv("RCLOL_GPU_DIRECT_PACE")){
+ struct timespec now;
+ 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 deadline=ctx->native_next_present_ns;
+ if(!deadline || (current>deadline && current-deadline>16666667ull))deadline=current;
+ if(deadline>current){
+ struct timespec wake={deadline/1000000000ull,deadline%1000000000ull};
+ while(__lolsys_syscall(1,1,(long)&wake,0,0,0,230)==-4){}
+ }
+ ctx->native_next_present_ns=deadline+16666667ull;
+ }
+ }
+ 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;}
+ GLsync completion=glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE,0);glFlush();
+ int ok=0;GLsync presented=NULL;
+ if(completion && p_eglMakeCurrent(ctx->egl_display,ctx->native_present_surface,ctx->native_present_surface,ctx->native_present_context)){
+ glWaitSync(completion,0,GL_TIMEOUT_IGNORED);glDeleteSync(completion);completion=NULL;
+ p_eglSwapInterval(ctx->egl_display,0);
+ GLuint fbo=0;glGenFramebuffers(1,&fbo);glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,binding.target,binding.texture,0);
+ if(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE){
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);glDrawBuffer(GL_BACK);glReadBuffer(GL_COLOR_ATTACHMENT0);
+ glDisable(GL_SCISSOR_TEST);glDisable(GL_FRAMEBUFFER_SRGB);
+ glBlitFramebuffer(0,height,width,0,0,0,width,height,GL_COLOR_BUFFER_BIT,GL_NEAREST);
+ /* The producer fence and EGL swap order GPU work without a CPU wait. */
+ static unsigned syntheticSamples;
+ int inspect=target->flags==0x54455354 && getenv("RCLOL_GPU_DIRECT_SYNTHETIC_TEST") && syntheticSamples++==10;
+ if(inspect){
+ unsigned char rgba[4];glReadPixels(0,0,1,1,GL_RGBA,GL_UNSIGNED_BYTE,rgba);
+ fprintf(stderr,"DIRECT_SHARED_SOURCE rgba=%u,%u,%u,%u\n",rgba[0],rgba[1],rgba[2],rgba[3]);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER,0);glReadBuffer(GL_BACK);glReadPixels(0,0,1,1,GL_RGBA,GL_UNSIGNED_BYTE,rgba);
+ fprintf(stderr,"DIRECT_SHARED_BACK rgba=%u,%u,%u,%u\n",rgba[0],rgba[1],rgba[2],rgba[3]);
+ }
+ presented=glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE,0);glFlush();
+ if(presented && glGetError()==GL_NO_ERROR){ok=p_eglSwapBuffers(ctx->egl_display,ctx->native_present_surface);
+ static unsigned traceCount;
+ if(getenv("RCLOL_GPU_DIRECT_TRACE") && traceCount++<3)fprintf(stderr,"GPU_DIRECT swap_result=%d swap_fn=%p surface=%p\n",ok,(void*)p_eglSwapBuffers,(void*)ctx->native_present_surface);
+ }
+ if(inspect){unsigned char rgba[4];glReadBuffer(GL_FRONT);glReadPixels(0,0,1,1,GL_RGBA,GL_UNSIGNED_BYTE,rgba);
+ fprintf(stderr,"DIRECT_SHARED_FRONT rgba=%u,%u,%u,%u error=%u\n",rgba[0],rgba[1],rgba[2],rgba[3],glGetError());}
+ }
+ glBindFramebuffer(GL_READ_FRAMEBUFFER,0);glDeleteFramebuffers(1,&fbo);
+ if(!p_eglMakeCurrent(ctx->egl_display,ctx->egl_surface,ctx->egl_surface,ctx->egl_context))ok=0;
+ else if(presented){glWaitSync(presented,0,GL_TIMEOUT_IGNORED);glDeleteSync(presented);presented=NULL;}
+ if(presented)glDeleteSync(presented);
+ }
+ if(completion)glDeleteSync(completion);
+ __atomic_store_n(&target->ready,ok?3:0,__ATOMIC_RELEASE);
+ if(ok){__atomic_add_fetch(&target->sequence,1,__ATOMIC_RELEASE);target->presenter=(uint32_t)getpid();}
+ CGLReleaseContext(ctx);return ok;
+}
+
+CGLError CGLReadbackIOSurface(IOSurfaceRef ioSurface) {
+ if (!ioSurface)
+ return kCGLBadAddress;
+ IOSurfaceID surfaceID = IOSurfaceGetID(ioSurface);
+ CGLIOSurfaceBinding binding = {0};
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ++i) {
+ if (ioSurfaceBindings[i].surfaceID == surfaceID) {
+ binding = ioSurfaceBindings[i];
+ CGLRetainContext(binding.context);
+ break;
+ }
+ }
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
+ if (!binding.context || !binding.texture)
+ return kCGLBadValue;
+
+ void *pixels = IOSurfaceGetBaseAddressOfPlane(ioSurface, binding.plane);
+ size_t bytesPerRow = IOSurfaceGetBytesPerRowOfPlane(ioSurface, binding.plane);
+ size_t bytesPerElement = IOSurfaceGetBytesPerElementOfPlane(ioSurface, binding.plane);
+ GLenum bindingEnum = textureBindingEnum(binding.target);
+ if (!pixels || !bytesPerRow || !bytesPerElement || !bindingEnum) {
+ CGLReleaseContext(binding.context);
+ return kCGLBadValue;
+ }
+
+ CGLContextObj oldContext = CGLGetCurrentContext();
+ if (oldContext != binding.context) {
+ CGLError result = CGLSetCurrentContext(binding.context);
+ if (result != kCGLNoError) {
+ CGLReleaseContext(binding.context);
+ return result;
+ }
+ }
+ GLint oldTexture = 0, oldRowLength = 0, oldAlignment = 0;
+ while (glGetError() != GL_NO_ERROR) {}
+ glGetIntegerv(bindingEnum, &oldTexture);
+ glGetIntegerv(GL_PACK_ROW_LENGTH, &oldRowLength);
+ glGetIntegerv(GL_PACK_ALIGNMENT, &oldAlignment);
+ glBindTexture(binding.target, binding.texture);
+ glFinish();
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)(bytesPerRow / bytesPerElement));
+ IOSurfaceLock(ioSurface, 0, NULL);
+ glGetTexImage(binding.target, 0, binding.format, binding.type, pixels);
+ GLenum error = glGetError();
+ typedef kern_return_t (*GPUUnlockFn)(IOSurfaceRef);
+ GPUUnlockFn gpuUnlock = (GPUUnlockFn)dlsym(RTLD_DEFAULT, "_IOSurfaceUnlockGPUReadback");
+ if (gpuUnlock) gpuUnlock(ioSurface);
+ else IOSurfaceUnlock(ioSurface, 0, NULL);
+ glPixelStorei(GL_PACK_ROW_LENGTH, oldRowLength);
+ glPixelStorei(GL_PACK_ALIGNMENT, oldAlignment);
+ glBindTexture(binding.target, (GLuint)oldTexture);
+ CGLError restore = kCGLNoError;
+ if (oldContext != binding.context)
+ restore = CGLSetCurrentContext(oldContext);
+ CGLReleaseContext(binding.context);
+ return error == GL_NO_ERROR ? restore : kCGLBadState;
}
CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) {
@@ -394,8 +939,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
if (parameter == kCGLCPSwapInterval)
{
+ swapIntervalRequested = 1;
GLint v = *value;
- EGLBoolean success = eglSwapInterval(display, v);
+ EGLBoolean success = p_eglSwapInterval(display, v);
if (success)
context->swap_interval = v;
return success ? kCGLNoError : kCGLBadValue;
@@ -417,14 +963,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
return kCGLNoError;
}
-CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, long rend_num, CGLRendererProperty prop, long *value) {
- return kCGLNoError;
+CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, GLint rend_num, CGLRendererProperty prop, GLint *value) {
+ if (!value) return kCGLBadAddress;
+ *value = 0;
+ return kCGLBadRendererInfo;
}
-CGLError CGLQueryRendererInfo(unsigned long display_mask, CGLRendererInfoObj *rend, long *nrend) {
- return kCGLNoError;
+CGLError CGLQueryRendererInfo(GLuint display_mask, CGLRendererInfoObj *rend, GLint *nrend) {
+ if (!rend || !nrend) return kCGLBadAddress;
+ *rend = NULL;
+ *nrend = 0;
+ return kCGLBadRendererInfo;
}
CGLError CGLDestroyRendererInfo(CGLRendererInfoObj rend) {
return kCGLNoError;
-}
\ No newline at end of file
+}
diff --git a/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h b/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
index 7d5eda01a..803ac02d3 100644
--- a/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
+++ b/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
@@ -64,6 +64,7 @@ enum {
kCGLPFARemotePBuffer = 91,
kCGLPFAAllowOfflineRenderers = 96,
kCGLPFAAcceleratedCompute = 97,
+ kCGLPFAOpenGLProfile = 99,
kCGLPFAVirtualScreenCount = 128,
};
diff --git a/src/frameworks/OpenGL/include/OpenGL/OpenGL.h b/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
index a700038d7..7bb98bf4b 100644
--- a/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
+++ b/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
@@ -8,6 +8,7 @@
#endif
#include <OpenGL/CGLTypes.h>
+#include <IOSurface/IOSurfaceRef.h>
#ifdef __cplusplus
extern "C" {
@@ -27,6 +28,8 @@ CGL_EXPORT CGLError CGLUnlockContext(CGLContextObj context);
CGL_EXPORT CGLError CGLSetCurrentContext(CGLContextObj ctx);
CGL_EXPORT CGLError CGLSetFullScreen(CGLContextObj ctx);
CGL_EXPORT CGLContextObj CGLGetCurrentContext(void);
+CGL_EXPORT CGLError CGLGetVirtualScreen(CGLContextObj ctx, GLint *screen);
+CGL_EXPORT CGLError CGLSetVirtualScreen(CGLContextObj ctx, GLint screen);
CGL_EXPORT CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value);
CGL_EXPORT CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, GLint *value);
@@ -40,9 +43,9 @@ CGL_EXPORT CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pixelFormat);
CGL_EXPORT GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat);
CGL_EXPORT CGLError CGLDescribePixelFormat(CGLPixelFormatObj pixelFormat, GLint screenNumber, CGLPixelFormatAttribute attribute, GLint *valuep);
-CGL_EXPORT CGLError CGLQueryRendererInfo(unsigned long display_mask, CGLRendererInfoObj *rend, long *nrend);
+CGL_EXPORT CGLError CGLQueryRendererInfo(GLuint display_mask, CGLRendererInfoObj *rend, GLint *nrend);
CGL_EXPORT CGLError CGLDestroyRendererInfo(CGLRendererInfoObj rend);
-CGL_EXPORT CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, long rend_num, CGLRendererProperty prop, long *value);
+CGL_EXPORT CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, GLint rend_num, CGLRendererProperty prop, GLint *value);
CGL_EXPORT CGLError CGLCreatePBuffer(GLsizei width, GLsizei height, GLenum target, GLenum internalFormat, GLint maxDetail, CGLPBufferObj *pbuffer);
CGL_EXPORT CGLError CGLDescribePBuffer(CGLPBufferObj pbuffer, GLsizei *width, GLsizei *height, GLenum *target, GLenum *internalFormat, GLint *mipmap);
@@ -53,6 +56,13 @@ CGL_EXPORT CGLError CGLDestroyPBuffer(CGLPBufferObj pbuffer);
CGL_EXPORT CGLError CGLGetPBuffer(CGLContextObj context, CGLPBufferObj *pbuffer, GLenum *face, GLint *level, GLint *screen);
CGL_EXPORT CGLError CGLSetPBuffer(CGLContextObj context, CGLPBufferObj pbuffer, GLenum face, GLint level, GLint screen);
CGL_EXPORT CGLError CGLTexImagePBuffer(CGLContextObj context, CGLPBufferObj pbuffer, GLenum sourceBuffer);
+CGL_EXPORT CGLError CGLTexImageIOSurface2D(CGLContextObj context, GLenum target,
+ GLenum internalFormat, GLsizei width, GLsizei height, GLenum format,
+ GLenum type, IOSurfaceRef ioSurface, GLuint plane);
+/* Darling extension used by QuartzCore's remote-layer fallback. Real CGL
+ * texture storage is bidirectionally backed by IOSurface; our Mesa fallback
+ * must explicitly copy rendered texture pixels back to the surface. */
+CGL_EXPORT CGLError CGLReadbackIOSurface(IOSurfaceRef ioSurface);
#ifdef __cplusplus
}
diff --git a/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h b/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
index 68242ee34..a9108c9c1 100644
--- a/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
+++ b/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
@@ -34,4 +34,10 @@ extern CFStringRef const kVTCompressionPropertyKey_ExpectedFrameRate;
extern CFStringRef const kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder;
+extern CFStringRef const kVTCompressionPropertyKey_BaseLayerFrameRateFraction;
+extern CFStringRef const kVTCompressionPropertyKey_ColorPrimaries;
+extern CFStringRef const kVTCompressionPropertyKey_TransferFunction;
+extern CFStringRef const kVTCompressionPropertyKey_YCbCrMatrix;
+extern CFStringRef const kVTVideoEncoderSpecification_EnableLowLatencyRateControl;
+
#endif
diff --git a/src/frameworks/VideoToolbox/src/VTCompressionProperties.c b/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
index 9e363904e..774557a64 100644
--- a/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
+++ b/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
@@ -30,3 +30,9 @@ const CFStringRef kVTCompressionPropertyKey_MaxFrameDelayCount = CFSTR("MaxFrame
const CFStringRef kVTCompressionPropertyKey_ExpectedFrameRate = CFSTR("ExpectedFrameRate");
const CFStringRef kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder = CFSTR("RequireHardwareAcceleratedVideoEncoder");
+
+const CFStringRef kVTCompressionPropertyKey_BaseLayerFrameRateFraction = CFSTR("BaseLayerFrameRateFraction");
+const CFStringRef kVTCompressionPropertyKey_ColorPrimaries = CFSTR("ColorPrimaries");
+const CFStringRef kVTCompressionPropertyKey_TransferFunction = CFSTR("TransferFunction");
+const CFStringRef kVTCompressionPropertyKey_YCbCrMatrix = CFSTR("YCbCrMatrix");
+const CFStringRef kVTVideoEncoderSpecification_EnableLowLatencyRateControl = CFSTR("EnableLowLatencyRateControl");
diff --git a/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h b/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
index 62d827aa2..15383581b 100644
--- a/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
+++ b/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
@@ -19,6 +19,26 @@
#include <Foundation/Foundation.h>
+typedef NSString *VNBarcodeSymbology NS_TYPED_ENUM;
+
+extern VNBarcodeSymbology const VNBarcodeSymbologyAztec;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode128;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39Checksum;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCII;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCIIChecksum;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode93;
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode93i;
+extern VNBarcodeSymbology const VNBarcodeSymbologyDataMatrix;
+extern VNBarcodeSymbology const VNBarcodeSymbologyEAN13;
+extern VNBarcodeSymbology const VNBarcodeSymbologyEAN8;
+extern VNBarcodeSymbology const VNBarcodeSymbologyI2of5;
+extern VNBarcodeSymbology const VNBarcodeSymbologyI2of5Checksum;
+extern VNBarcodeSymbology const VNBarcodeSymbologyITF14;
+extern VNBarcodeSymbology const VNBarcodeSymbologyPDF417;
+extern VNBarcodeSymbology const VNBarcodeSymbologyQR;
+extern VNBarcodeSymbology const VNBarcodeSymbologyUPCE;
+
@interface VNDetectBarcodesRequest : NSObject
@end
diff --git a/src/frameworks/Vision/src/VNDetectBarcodesRequest.m b/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
index 9a489b386..9fe56cc3f 100644
--- a/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
+++ b/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
@@ -19,6 +19,24 @@
#import <Vision/VNDetectBarcodesRequest.h>
+VNBarcodeSymbology const VNBarcodeSymbologyAztec = @"org.iso.Aztec";
+VNBarcodeSymbology const VNBarcodeSymbologyCode128 = @"org.iso.Code128";
+VNBarcodeSymbology const VNBarcodeSymbologyCode39 = @"org.iso.Code39";
+VNBarcodeSymbology const VNBarcodeSymbologyCode39Checksum = @"org.iso.Code39Checksum";
+VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCII = @"org.iso.Code39FullASCII";
+VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCIIChecksum = @"org.iso.Code39FullASCIIChecksum";
+VNBarcodeSymbology const VNBarcodeSymbologyCode93 = @"org.iso.Code93";
+VNBarcodeSymbology const VNBarcodeSymbologyCode93i = @"org.iso.Code93i";
+VNBarcodeSymbology const VNBarcodeSymbologyDataMatrix = @"org.iso.DataMatrix";
+VNBarcodeSymbology const VNBarcodeSymbologyEAN13 = @"org.gs1.EAN-13";
+VNBarcodeSymbology const VNBarcodeSymbologyEAN8 = @"org.gs1.EAN-8";
+VNBarcodeSymbology const VNBarcodeSymbologyI2of5 = @"org.iso.I2of5";
+VNBarcodeSymbology const VNBarcodeSymbologyI2of5Checksum = @"org.iso.I2of5Checksum";
+VNBarcodeSymbology const VNBarcodeSymbologyITF14 = @"org.iso.ITF14";
+VNBarcodeSymbology const VNBarcodeSymbologyPDF417 = @"org.iso.PDF417";
+VNBarcodeSymbology const VNBarcodeSymbologyQR = @"org.iso.QRCode";
+VNBarcodeSymbology const VNBarcodeSymbologyUPCE = @"org.gs1.UPC-E";
+
@implementation VNDetectBarcodesRequest
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
diff --git a/src/libsandbox/src/sandbox.c b/src/libsandbox/src/sandbox.c
index 5af5298aa..fb59ee5d1 100644
--- a/src/libsandbox/src/sandbox.c
+++ b/src/libsandbox/src/sandbox.c
@@ -21,6 +21,8 @@
#include <sandbox/sandbox.h>
#include <stdlib.h>
#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
static int verbose = 0;
@@ -29,63 +31,105 @@ static void initme(void) {
verbose = getenv("STUB_VERBOSE") != NULL;
}
-void* sandbox_apply(void)
+/* darling-lol: SeatbeltExtensions token family. Chromium's seatbelt client
+ * issues/consumes/releases these around sandboxed child setup; without
+ * enforcement we hand out dummy nonzero tokens and accept everything.
+ * (These live in libsandbox.dylib on macOS, hence here and not in
+ * libsystem_sandbox.) */
+static int64_t sandbox_extension_next_token = 1;
+
+int64_t sandbox_extension_issue_file(const char *path, uint32_t flags)
{
- if (verbose) puts("STUB: sandbox_apply called");
- return NULL;
+ if (verbose) puts("STUB: sandbox_extension_issue_file called");
+ (void)path; (void)flags;
+ return sandbox_extension_next_token++;
}
-void* sandbox_apply_container(void)
+int64_t sandbox_extension_issue_mach(const char *name, uint32_t flags)
{
- if (verbose) puts("STUB: sandbox_apply_container called");
- return NULL;
+ if (verbose) puts("STUB: sandbox_extension_issue_mach called");
+ (void)name; (void)flags;
+ return sandbox_extension_next_token++;
}
-void* sandbox_compile_entitlements(void)
+int sandbox_extension_consume(int64_t token)
{
- if (verbose) puts("STUB: sandbox_compile_entitlements called");
- return NULL;
+ if (verbose) puts("STUB: sandbox_extension_consume called");
+ (void)token;
+ return 0;
}
-void* sandbox_compile_file(void)
+int sandbox_extension_release(int64_t token)
+{
+ if (verbose) puts("STUB: sandbox_extension_release called");
+ (void)token;
+ return 0;
+}
+
+/* darling-lol: compile/apply family. No seatbelt compiler exists, so
+ * "compiling" a profile yields a dummy handle and applying succeeds.
+ * Signatures match Apple's (the old stubs had wrong shapes). */
+
+void* sandbox_compile_string(const char *profile, char **errorbuf)
+{
+ if (verbose) puts("STUB: sandbox_compile_string called");
+ if (errorbuf != NULL) *errorbuf = NULL;
+ (void)profile;
+ return strdup("darling-sandbox-stub");
+}
+
+void* sandbox_compile_file(const char *path, char **errorbuf)
{
if (verbose) puts("STUB: sandbox_compile_file called");
- return NULL;
+ if (errorbuf != NULL) *errorbuf = NULL;
+ (void)path;
+ return strdup("darling-sandbox-stub");
}
-void* sandbox_compile_named(void)
+void* sandbox_compile_named(const char *name, char **errorbuf)
{
if (verbose) puts("STUB: sandbox_compile_named called");
- return NULL;
+ if (errorbuf != NULL) *errorbuf = NULL;
+ (void)name;
+ return strdup("darling-sandbox-stub");
}
-void* sandbox_compile_string(void)
+void* sandbox_compile_entitlements(void *entitlements, char **errorbuf)
{
- if (verbose) puts("STUB: sandbox_compile_string called");
- return NULL;
+ if (verbose) puts("STUB: sandbox_compile_entitlements called");
+ if (errorbuf != NULL) *errorbuf = NULL;
+ (void)entitlements;
+ return strdup("darling-sandbox-stub");
}
-void* sandbox_container_paths_iterate_items(void)
+int sandbox_apply(void *handle)
{
- if (verbose) puts("STUB: sandbox_container_paths_iterate_items called");
- return NULL;
+ if (verbose) puts("STUB: sandbox_apply called");
+ (void)handle;
+ return 0;
}
-void* sandbox_create_params(void)
+void sandbox_free_profile(void *handle)
{
- if (verbose) puts("STUB: sandbox_create_params called");
+ if (verbose) puts("STUB: sandbox_free_profile called");
+ free(handle);
+}
+
+void* sandbox_apply_container(void)
+{
+ if (verbose) puts("STUB: sandbox_apply_container called");
return NULL;
}
-void* sandbox_free_params(void)
+void* sandbox_container_paths_iterate_items(void)
{
- if (verbose) puts("STUB: sandbox_free_params called");
+ if (verbose) puts("STUB: sandbox_container_paths_iterate_items called");
return NULL;
}
-void* sandbox_free_profile(void)
+void* sandbox_create_params(void)
{
- if (verbose) puts("STUB: sandbox_free_profile called");
+ if (verbose) puts("STUB: sandbox_create_params called");
return NULL;
}
diff --git a/src/libsystem_coreservices/dirhelper.c b/src/libsystem_coreservices/dirhelper.c
index f22f8213d..e7fbdc92e 100644
--- a/src/libsystem_coreservices/dirhelper.c
+++ b/src/libsystem_coreservices/dirhelper.c
@@ -1,4 +1,5 @@
#include <errno.h>
+#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
@@ -44,8 +45,18 @@ char* _dirhelper(int which, char* p, size_t limit)
case DIRHELPER_USER_LOCAL:
{
const char* home;
+ // Darling: guest home is /Users/$USER even though the host
+ // HOME points at the Linux home; trustd needs the guest path
+ // for its cache locations.
+ const char *user = getenv("USER");
+ char home_buf[PATH_MAX];
- home = getenv("HOME");
+ if (user != NULL) {
+ snprintf(home_buf, sizeof(home_buf), "/Users/%s", user);
+ home = home_buf;
+ } else {
+ home = getenv("HOME");
+ }
if (home == NULL)
home = "";
@@ -64,8 +75,15 @@ char* _dirhelper(int which, char* p, size_t limit)
case DIRHELPER_USER_LOCAL_CACHE:
{
const char* home;
+ const char *user = getenv("USER");
+ char home_buf[PATH_MAX];
- home = getenv("HOME");
+ if (user != NULL) {
+ snprintf(home_buf, sizeof(home_buf), "/Users/%s", user);
+ home = home_buf;
+ } else {
+ home = getenv("HOME");
+ }
if (home != NULL)
snprintf(p, limit, "%s/.cache/%s%s", home, dir_suffix ? dir_suffix : "", dir_suffix ? "/" : "");
else
diff --git a/src/sandbox/sandbox.c b/src/sandbox/sandbox.c
index f48cceb9e..9afa967db 100644
--- a/src/sandbox/sandbox.c
+++ b/src/sandbox/sandbox.c
@@ -10,7 +10,13 @@ const enum sandbox_filter_type SANDBOX_CHECK_NO_REPORT = SANDBOX_FILTER_NONE;
int sandbox_init(const char *profile, uint64_t flags, char **errorbuf)
{
- *errorbuf = strdup("Not implemented");
+ /* darling-lol: no seatbelt enforcement exists, so init trivially
+ * succeeds. Must tolerate errorbuf==NULL (Chromium passes NULL) and
+ * report no error on success; the old code crashed on NULL and left a
+ * bogus "Not implemented" error on success. */
+ if (errorbuf != NULL) {
+ *errorbuf = NULL;
+ }
return 0;
}
@@ -51,18 +57,36 @@ void sandbox_free_error(char *errorbuf)
int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf)
{
- *errorbuf = strdup("Not implemented");
+ /* darling-lol: see sandbox_init. */
+ if (errorbuf != NULL) {
+ *errorbuf = NULL;
+ }
return 0;
}
int sandbox_init_with_extensions(const char *profile, uint64_t flags, const char *const extensions[], char **errorbuf)
{
- *errorbuf = strdup("Not implemented");
+ /* darling-lol: see sandbox_init. Extensions are accepted and ignored. */
+ if (errorbuf != NULL) {
+ *errorbuf = NULL;
+ }
return 0;
}
int sandbox_check(pid_t pid, const char *operation, enum sandbox_filter_type type, ...)
{
+ /* darling-lol: operation==NULL is the canonical "am I sandboxed?"
+ * probe (Chromium: sandbox_check(getpid(), NULL, SANDBOX_FILTER_NONE)
+ * "returns 1 if sandboxed", and ContentMainRunnerImpl CHECKs
+ * Seatbelt::IsSandboxed() in every sandboxed child -- a 0 here
+ * suicided all helpers/GPU with a silent UD2). Since init always
+ * "succeeds", consistently report sandboxed (1). Any other query
+ * keeps 0 (allowed), as nothing is enforced. */
+ (void)pid;
+ if (operation == NULL) {
+ return 1;
+ }
+ (void)type;
return 0;
}
@@ -84,42 +108,56 @@ int sandbox_unsuspend(void)
int sandbox_issue_extension(const char *path, char **ext_token)
{
- return -1;
+ /* darling-lol: no enforcement; hand out a dummy token callers can
+ * consume/release. */
+ if (ext_token != NULL) {
+ *ext_token = strdup("darling-sandbox-stub");
+ }
+ return 0;
}
int sandbox_issue_fs_extension(const char *path, uint64_t flags, char **ext_token)
{
- return -1;
+ if (ext_token != NULL) {
+ *ext_token = strdup("darling-sandbox-stub");
+ }
+ return 0;
}
int sandbox_issue_fs_rw_extension(const char *path, char **ext_token)
{
- return -1;
+ if (ext_token != NULL) {
+ *ext_token = strdup("darling-sandbox-stub");
+ }
+ return 0;
}
int sandbox_issue_mach_extension(const char *name, char **ext_token)
{
- return -1;
+ if (ext_token != NULL) {
+ *ext_token = strdup("darling-sandbox-stub");
+ }
+ return 0;
}
int sandbox_consume_extension(const char *path, const char *ext_token)
{
- return -1;
+ return 0;
}
int sandbox_consume_fs_extension(const char *ext_token, char **path)
{
- return -1;
+ return 0;
}
int sandbox_consume_mach_extension(const char *ext_token, char **name)
{
- return -1;
+ return 0;
}
int sandbox_release_fs_extension(const char *ext_token)
{
- return -1;
+ return 0;
}
int sandbox_container_path_for_pid(pid_t pid, char *buffer, size_t bufsize)
@@ -129,7 +167,9 @@ int sandbox_container_path_for_pid(pid_t pid, char *buffer, size_t bufsize)
int sandbox_wakeup_daemon(char **errorbuf)
{
- *errorbuf = strdup("Not implemented");
+ if (errorbuf != NULL) {
+ *errorbuf = NULL;
+ }
return -1;
}
diff --git a/src/sandbox/sandbox.h b/src/sandbox/sandbox.h
index 6fa2d088c..655554284 100644
--- a/src/sandbox/sandbox.h
+++ b/src/sandbox/sandbox.h
@@ -161,6 +161,14 @@ int sandbox_note(const char *note);
int sandbox_suspend(pid_t pid);
int sandbox_unsuspend(void);
+/* darling-lol: SeatbeltExtensions token family (used by Chromium).
+ * No enforcement exists, so issue dummy nonzero tokens and accept
+ * consume/release as no-ops. */
+int64_t sandbox_extension_issue_file(const char *path, uint32_t flags);
+int64_t sandbox_extension_issue_mach(const char *name, uint32_t flags);
+int sandbox_extension_consume(int64_t token);
+int sandbox_extension_release(int64_t token);
+
int sandbox_issue_extension(const char *path, char **ext_token);
int sandbox_issue_fs_extension(const char *path, uint64_t flags, char **ext_token);
int sandbox_issue_fs_rw_extension(const char *path, char **ext_token);
diff --git a/src/shellspawn/shellspawn.c b/src/shellspawn/shellspawn.c
index f5ba615bb..fea23d638 100644
--- a/src/shellspawn/shellspawn.c
+++ b/src/shellspawn/shellspawn.c
@@ -36,7 +36,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
#include "shellspawn.h"
#include "duct_signals.h"
-#define DBG 0
+#define DBG 1
int g_serverSocket = -1;
struct sigaction sigchld_oldaction;
@@ -50,6 +50,17 @@ void reapAll(void);
int main(int argc, const char** argv)
{
+ // darling-lol M58F40: ignore SIGPIPE. shellspawn writes to client/child
+ // sockets and pipes whose peers can close at any time (e.g. the `darling`
+ // client probing /var/run/shellspawn.sock and disconnecting, or a shell
+ // whose stdout pipe is closed). With the default disposition the first such
+ // write raises SIGPIPE and KILLS shellspawn, which removes the container's
+ // shell service and makes every subsequent `darling shell`/`darling <bin>`
+ // fail with "Error connecting to shellspawn ... Connection refused" (the
+ // M58F39 "container wedge"). Every other daemon here (and darlingserver's
+ // M58f2 hardening) already ignores SIGPIPE; shellspawn was missed.
+ signal(SIGPIPE, SIG_IGN);
+
// shellspawn (daemon) --fork()--> shellspawn (child) --fork()--> exec /bin/bash
// in order to read the exit status of the shell process,
// we have to allow it to become a zombie, therefore we need to
diff --git a/src/startup/mldr/commpage.c b/src/startup/mldr/commpage.c
index d9a7aea96..cc76f7a85 100644
--- a/src/startup/mldr/commpage.c
+++ b/src/startup/mldr/commpage.c
@@ -12,6 +12,7 @@
// Include commpage definitions
#define PRIVATE
#include <i386/cpu_capabilities.h>
+#include "commpage-cpu-caps.h"
static const char* SIGNATURE32 = "commpage 32-bit";
static const char* SIGNATURE64 = "commpage 64-bit";
@@ -84,74 +85,26 @@ void commpage_setup(bool _64bit)
uint64_t get_cpu_caps(void)
{
- uint64_t caps = 0;
-
- {
- union cpu_flags1 eax;
- union cpu_flags2 ecx;
- union cpu_flags3 edx;
- uint32_t ebx;
-
- eax.reg = ecx.reg = edx.reg = 0;
- __cpuid(1, eax.reg, ebx, ecx.reg, edx.reg);
-
- if (ecx.mmx)
- caps |= kHasMMX;
- if (ecx.sse)
- caps |= kHasSSE;
- if (ecx.sse2)
- caps |= kHasSSE2;
- if (edx.sse3)
- caps |= kHasSSE3;
- if (ecx.ia64)
- caps |= k64Bit;
- if (edx.ssse3)
- caps |= kHasSupplementalSSE3;
- if (edx.sse41)
- caps |= kHasSSE4_1;
- if (edx.sse42)
- caps |= kHasSSE4_2;
- if (edx.aes)
- caps |= kHasAES;
- if (edx.avx)
- caps |= kHasAVX1_0;
- if (edx.rdrnd)
- caps |= kHasRDRAND;
- if (edx.fma)
- caps |= kHasFMA;
- if (edx.f16c)
- caps |= kHasF16C;
- }
- {
- union cpu_flags4 ebx;
- union cpu_flags5 ecx;
- uint32_t edx = 0, eax = 7;
-
- __cpuid(7, eax, ebx.reg, ecx.reg, edx);
-
- if (ebx.erms)
- caps |= kHasENFSTRG;
- if (ebx.avx2)
- caps |= kHasAVX2_0;
- if (ebx.bmi1)
- caps |= kHasBMI1;
- if (ebx.bmi2)
- caps |= kHasBMI2;
- if (ebx.rtm)
- caps |= kHasRTM;
- if (ebx.hle)
- caps |= kHasHLE;
- if (ebx.rdseed)
- caps |= kHasRDSEED;
- if (ebx.adx)
- caps |= kHasADX;
- if (ebx.mpx)
- caps |= kHasMPX;
- if (ebx.sgx)
- caps |= kHasSGX;
- }
-
- return caps;
+ unsigned int a, b, ecx1 = 0, edx1 = 0, ebx7 = 0;
+ const unsigned int max_basic = __get_cpuid_max(0, NULL);
+ if (max_basic >= 1) __cpuid_count(1, 0, a, b, ecx1, edx1);
+ if (max_basic >= 7) {
+ unsigned int c, d;
+ __cpuid_count(7, 0, a, ebx7, c, d);
+ }
+ uint64_t xcr0 = 0;
+ if ((ecx1 & ((1u << 26) | (1u << 27))) == ((1u << 26) | (1u << 27))) {
+ unsigned int low, high;
+ __asm__ volatile("xgetbv" : "=a"(low), "=d"(high) : "c"(0));
+ xcr0 = ((uint64_t)high << 32) | low;
+ }
+ int long_mode = 0;
+ if (__get_cpuid_max(0x80000000u, NULL) >= 0x80000001u) {
+ unsigned int c, d;
+ __cpuid_count(0x80000001u, 0, a, b, c, d);
+ long_mode = !!(d & (1u << 29));
+ }
+ return commpage_decode_cpu_caps(ecx1, edx1, ebx7, xcr0, long_mode);
}
unsigned long commpage_address(bool _64bit)
diff --git a/src/startup/mldr/elfcalls/elfcalls.c b/src/startup/mldr/elfcalls/elfcalls.c
index 8f0b9e61f..38a9644c0 100644
--- a/src/startup/mldr/elfcalls/elfcalls.c
+++ b/src/startup/mldr/elfcalls/elfcalls.c
@@ -6,6 +6,7 @@
#include <semaphore.h>
#include <locale.h>
#include <unistd.h>
+#include <pthread.h>
#include "elfcalls.h"
#include "threads.h"
#include <sys/un.h>
@@ -86,6 +87,9 @@ static int __dserver_process_lifetime_pipe_refresh() {
void elfcalls_make(struct elf_calls* calls)
{
+ extern void __mldr_fork_prepare(void);
+ extern void __mldr_fork_complete(void);
+ pthread_atfork(__mldr_fork_prepare, __mldr_fork_complete, __mldr_fork_complete);
calls->dlopen = dlopen_simple;
calls->dlclose = dlclose;
calls->dlsym = dlsym;
@@ -100,7 +104,16 @@ void elfcalls_make(struct elf_calls* calls)
calls->darling_thread_get_stack = __darling_thread_get_stack;
calls->get_errno = get_errno;
- calls->exit = exit;
+ // darling-lol: the guest's `exit()` has already run its own
+ // `__cxa_finalize`/atexit handlers by the time the exit syscall
+ // reaches `native_exit`. Using the host `exit()` here ALSO runs the
+ // host libc's atexit handlers, which include host Mesa/lavapipe
+ // (`libgallium`) thread-pool teardown. In a forked child the worker
+ // threads don't exist, so that teardown blocks forever in
+ // `pthread_cond_wait` and the parent's `wait4` never returns
+ // (crashpad::SpawnSubprocess hang). The exit syscall must not run
+ // atexit handlers; use `_exit`.
+ calls->exit = _exit;
calls->malloc = malloc;
calls->free = free;
@@ -126,4 +139,5 @@ void elfcalls_make(struct elf_calls* calls)
calls->dserver_get_process_lifetime_pipe = __dserver_get_process_lifetime_pipe;
calls->dserver_process_lifetime_pipe_refresh = __dserver_process_lifetime_pipe_refresh;
calls->dserver_close_process_lifetime_pipe = __mldr_close_process_lifetime_pipe;
+ calls->fork = fork;
}
diff --git a/src/startup/mldr/elfcalls/elfcalls.h b/src/startup/mldr/elfcalls/elfcalls.h
index fee1bde87..bee86604b 100644
--- a/src/startup/mldr/elfcalls/elfcalls.h
+++ b/src/startup/mldr/elfcalls/elfcalls.h
@@ -71,7 +71,9 @@ struct elf_calls
int (*dserver_get_process_lifetime_pipe)(void);
int (*dserver_process_lifetime_pipe_refresh)(void);
void (*dserver_close_process_lifetime_pipe)(int fd);
+
+ // Host libc fork prepares native allocator and loader locks for the child.
+ int (*fork)(void);
};
#endif
-
diff --git a/src/startup/mldr/mldr.c b/src/startup/mldr/mldr.c
index 6617ef82b..1d12043c2 100644
--- a/src/startup/mldr/mldr.c
+++ b/src/startup/mldr/mldr.c
@@ -154,7 +154,7 @@ int main(int argc, char** argv, char** envp)
// allow any process to ptrace us
// the only process we really care about being able to do this is the server,
// but we can't just use the server's PID, since it lies outside our PID namespace.
- ptrace(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
+ prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
process_special_env(&mldr_load_results);
@@ -563,6 +563,16 @@ static socket_bitmap_t socket_bitmap = {
.highest = -1,
};
+/* Host fork must preserve a consistent bitmap and an unlocked child mutex.
+ * These callbacks run through glibc fork, before guest RPC fork cleanup. */
+void __mldr_fork_prepare(void) {
+ pthread_mutex_lock(&socket_bitmap.mutex);
+}
+
+void __mldr_fork_complete(void) {
+ pthread_mutex_unlock(&socket_bitmap.mutex);
+}
+
static int socket_bitmap_get(socket_bitmap_t* bitmap) {
int fd = -1;
bool updated = false;
@@ -673,7 +683,13 @@ static void socket_bitmap_put(socket_bitmap_t* bitmap, int socket) {
}
}
- if ((bitmap->bits[index / 8] & (1 << (index % 8))) == 0) {
+ // darling-lol: the test was inverted (`== 0`, i.e. it stopped at a FREE slot and shrank past slots that were
+ // IN USE). After a whole trailing byte of slots had been released, the length collapsed below live
+ // sockets; the next threads were then handed descriptor numbers that still belonged to running threads,
+ // dup2() replaced those threads' RPC sockets, and when the newcomer exited the older thread was left with a
+ // closed descriptor (EBADF on its next call, process abort). Seen as LeagueClient dying ~85-110 s after start:
+ // slot 71 (fd 524216), held by a long-sleeping thread, given to a second thread (logs/rpc-fd-history.log).
+ if ((bitmap->bits[index / 8] & (1 << (index % 8))) != 0) {
// this bit is in-use, so we can't shrink any further
break;
}
@@ -685,12 +701,20 @@ static void socket_bitmap_put(socket_bitmap_t* bitmap, int socket) {
if (old_byte_size != new_byte_size) {
// we can free one or more bytes from the bitmap
- void* ptr = realloc(bitmap->bits, new_byte_size);
- if (!ptr) {
- goto out;
- }
+ if (new_byte_size == 0) {
+ // darling-lol: realloc(ptr, 0) frees the block and returns NULL; the old code then kept the dangling
+ // pointer and the next allocation aborted with a double free. (Unreachable while the shrink test
+ // above was inverted; reached as soon as a process released its last socket.)
+ free(bitmap->bits);
+ bitmap->bits = NULL;
+ } else {
+ void* ptr = realloc(bitmap->bits, new_byte_size);
+ if (!ptr) {
+ goto out;
+ }
- bitmap->bits = ptr;
+ bitmap->bits = ptr;
+ }
}
}
diff --git a/src/tools/open.m b/src/tools/open.m
index 126bd394c..f62c2e0e8 100644
--- a/src/tools/open.m
+++ b/src/tools/open.m
@@ -150,7 +150,15 @@ int main(int argc, char *argv[])
NSURL** urls = (NSURL**) malloc(count * sizeof(NSURL*));
for (int i = 0; i < count; i++)
- urls[i] = [NSURL URLWithString:[NSString stringWithCString: argv[optind+i] encoding: NSUTF8StringEncoding]];
+ {
+ NSString* str = [NSString stringWithCString: argv[optind+i] encoding: NSUTF8StringEncoding];
+ // +URLWithString: needs a scheme (e.g. file:/// or https://) and
+ // returns nil for bare POSIX paths, which is how `open` is most
+ // often invoked. Fall back to a file URL in that case.
+ urls[i] = [NSURL URLWithString: str];
+ if (urls[i] == nil)
+ urls[i] = [NSURL fileURLWithPath: str];
+ }
filenames = [NSArray arrayWithObjects: urls count: count];
free(urls);
diff --git a/src/CoreAudio/AudioTrace.h b/src/CoreAudio/AudioTrace.h
new file mode 100644
--- /dev/null
+++ b/src/CoreAudio/AudioTrace.h
@@ -0,0 +1,14 @@
+#ifndef RCLOL_AUDIO_TRACE_H
+#define RCLOL_AUDIO_TRACE_H
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+#include <unistd.h>
+// Opt-in API/status diagnostics only; never records audio samples or names.
+static inline void rclolAudioTrace(const char* format, ...) {
+ if (!getenv("RCLOL_AUDIO_TRACE")) return;
+ static FILE* file=[] { FILE* f=fopen("/tmp/rclol-audio-api.log","a"); if(f) setvbuf(f,nullptr,_IOLBF,0); return f; }();
+ if(!file) return;
+ va_list args;va_start(args,format);fprintf(file,"pid=%d ",getpid());vfprintf(file,format,args);va_end(args);
+}
+#endif
diff --git a/src/CoreAudio/CoreAudioComponent/MatrixMixerAU.cpp b/src/CoreAudio/CoreAudioComponent/MatrixMixerAU.cpp
new file mode 100644
--- /dev/null
+++ b/src/CoreAudio/CoreAudioComponent/MatrixMixerAU.cpp
@@ -0,0 +1,175 @@
+#include "../AudioTrace.h"
+/* This file is part of Darling, distributed under the GNU GPL v3 or later. */
+#include "AUBase.h"
+#include "AUInputElement.h"
+#include "AUOutputElement.h"
+#include "AUPlugInDispatch.h"
+#include <AudioToolbox/AudioUnitParameters.h>
+#include <map>
+#include <mutex>
+#include <cmath>
+#include <cstring>
+#include <cstdio>
+#include <cstdlib>
+#include <atomic>
+
+// Float PCM matrix mixing. Channel indices are flattened across each scope's buses.
+class MatrixMixerAU : public AUBase {
+ std::mutex mMutex;
+ std::map<UInt32, Float32> mGains;
+ std::map<UInt32, Float32> mInputEnabled, mOutputEnabled;
+ UInt32 channels(AudioUnitScope scope) {
+ auto& buses = GetScope(scope);
+ UInt32 count = 0;
+ for (UInt32 b = 0; b < buses.GetNumberOfElements(); ++b)
+ count += buses.GetIOElement(b)->GetStreamFormat().mChannelsPerFrame;
+ return count;
+ }
+ static Float32 value(const std::map<UInt32, Float32>& values, UInt32 key, Float32 fallback = 0) {
+ auto it = values.find(key);
+ return it == values.end() ? fallback : it->second;
+ }
+ bool validGain(UInt32 element) {
+ UInt32 input = element >> 16, output = element & 0xffff;
+ return (input == 0xffff || input < channels(kAudioUnitScope_Input)) &&
+ (output == 0xffff || output < channels(kAudioUnitScope_Output));
+ }
+ static Float32* sample(AudioBufferList& buffers, UInt32 channel, UInt32& stride) {
+ for (UInt32 b = 0; b < buffers.mNumberBuffers; ++b) {
+ AudioBuffer& buffer = buffers.mBuffers[b];
+ if (channel < buffer.mNumberChannels) {
+ stride = buffer.mNumberChannels;
+ return buffer.mData ? static_cast<Float32*>(buffer.mData) + channel : nullptr;
+ }
+ channel -= buffer.mNumberChannels;
+ }
+ return nullptr;
+ }
+public:
+ MatrixMixerAU(AudioComponentInstance instance) : AUBase(instance, 1, 1) {}
+ bool CanScheduleParameters() const override { return false; }
+ bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override { return !IsInitialized(); }
+ bool BusCountWritable(AudioUnitScope scope) override {
+ return scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output;
+ }
+ bool ValidFormat(AudioUnitScope scope, AudioUnitElement, const CAStreamBasicDescription& f) override {
+ if (!f.IsPCM() || !f.IsFloat() || f.mBitsPerChannel != 32 ||
+ !f.mChannelsPerFrame || f.mChannelsPerFrame >= 0xffff || f.mSampleRate <= 0 ||
+ (f.mFormatFlags & kAudioFormatFlagIsBigEndian) || f.mFramesPerPacket != 1)
+ return false;
+ UInt32 bytes = 4 * (f.IsInterleaved() ? f.mChannelsPerFrame : 1);
+ return f.mBytesPerFrame == bytes && f.mBytesPerPacket == bytes;
+ }
+ OSStatus Initialize() override {
+ if (!Inputs().GetNumberOfElements() || !Outputs().GetNumberOfElements() ||
+ channels(kAudioUnitScope_Input) >= 0xffff || channels(kAudioUnitScope_Output) >= 0xffff)
+ return kAudioUnitErr_FormatNotSupported;
+ /* darling-lol: the rate must agree across the buses that are in use. League declares more buses than it uses (it
+ * sets the element counts and configures bus 0 only); the unused ones keep a default rate and used to make this
+ * initialize fail with FormatNotSupported, which sent the game into an endless graph rebuild loop. Input buses
+ * that have no source are never pulled (RenderBus skips them), and only output bus 0 is ever connected. */
+ double rate = GetOutput(0)->GetStreamFormat().mSampleRate;
+ for (UInt32 b = 0; b < Inputs().GetNumberOfElements(); ++b) {
+ const double r = GetInput(b)->GetStreamFormat().mSampleRate;
+ if (GetInput(b)->IsActive() && r != rate) {
+ rclolAudioTrace("MIXER_INIT_RATE_MISMATCH input=%u rate=%.0f output0=%.0f\n", b, r, rate);
+ return kAudioUnitErr_FormatNotSupported;
+ }
+ }
+ return AUBase::Initialize();
+ }
+ OSStatus SetParameter(AudioUnitParameterID id, AudioUnitScope scope, AudioUnitElement element,
+ AudioUnitParameterValue gain, UInt32) override {
+ static std::atomic<unsigned> traceCount{0};
+ if (getenv("RCLOL_AUDIO_TRACE") && traceCount++ < 64)
+ rclolAudioTrace("AUDIO_MATRIX_PARAM id=%u scope=%u element=%08x gain=%g\n",id,scope,element,gain);
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!std::isfinite(gain)) return kAudioUnitErr_InvalidParameter;
+ if (id == kMatrixMixerParam_Volume) {
+ if (scope == kAudioUnitScope_Input) {
+ if (element >= channels(scope)) return kAudioUnitErr_InvalidElement;
+ element = (element << 16) | 0xffff;
+ } else if (scope == kAudioUnitScope_Output) {
+ if (element >= channels(scope)) return kAudioUnitErr_InvalidElement;
+ element |= 0xffff0000;
+ } else if (scope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
+ if (!validGain(element)) return kAudioUnitErr_InvalidElement;
+ mGains[element] = gain;
+ return noErr;
+ }
+ if (id == kMatrixMixerParam_Enable &&
+ (scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output)) {
+ if (element >= GetScope(scope).GetNumberOfElements()) return kAudioUnitErr_InvalidElement;
+ (scope == kAudioUnitScope_Input ? mInputEnabled : mOutputEnabled)[element] = gain != 0;
+ return noErr;
+ }
+ return kAudioUnitErr_InvalidParameter;
+ }
+ OSStatus GetParameter(AudioUnitParameterID id, AudioUnitScope scope, AudioUnitElement element,
+ AudioUnitParameterValue& gain) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (id == kMatrixMixerParam_Volume) {
+ if (scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output) {
+ if (element >= channels(scope)) return kAudioUnitErr_InvalidElement;
+ element = scope == kAudioUnitScope_Input ? (element << 16) | 0xffff : element | 0xffff0000;
+ } else if (scope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
+ if (!validGain(element)) return kAudioUnitErr_InvalidElement;
+ gain = value(mGains, element);
+ return noErr;
+ }
+ if (id == kMatrixMixerParam_Enable &&
+ (scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output)) {
+ if (element >= GetScope(scope).GetNumberOfElements()) return kAudioUnitErr_InvalidElement;
+ gain = value(scope == kAudioUnitScope_Input ? mInputEnabled : mOutputEnabled, element, 1);
+ return noErr;
+ }
+ return kAudioUnitErr_InvalidParameter;
+ }
+ OSStatus RenderBus(AudioUnitRenderActionFlags& flags, const AudioTimeStamp& time,
+ UInt32 bus, UInt32 frames) override {
+ auto& output = GetOutput(bus)->GetBufferList();
+ for (UInt32 b = 0; b < output.mNumberBuffers; ++b)
+ if (output.mBuffers[b].mData) memset(output.mBuffers[b].mData, 0, output.mBuffers[b].mDataByteSize);
+ UInt32 outputOffset = 0;
+ for (UInt32 b = 0; b < bus; ++b) outputOffset += GetOutput(b)->GetStreamFormat().mChannelsPerFrame;
+ UInt32 inputOffset = 0;
+ bool silent = true;
+ for (UInt32 b = 0; b < Inputs().GetNumberOfElements(); ++b) {
+ auto* input = GetInput(b);
+ UInt32 inputChannels = input->GetStreamFormat().mChannelsPerFrame;
+ // Pull without holding the parameter lock: callbacks may change parameters.
+ if (input->IsActive()) {
+ AudioUnitRenderActionFlags inputFlags = 0;
+ OSStatus status = input->PullInput(inputFlags, time, b, frames);
+ if (status) return status;
+ auto& data = input->GetBufferList();
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (value(mInputEnabled, b, 1) && value(mOutputEnabled, bus, 1) &&
+ !(inputFlags & kAudioUnitRenderAction_OutputIsSilence)) {
+ for (UInt32 i = 0; i < inputChannels; ++i) {
+ UInt32 inputStride = 0;
+ Float32* src = sample(data, i, inputStride);
+ for (UInt32 o = 0; o < GetOutput(bus)->GetStreamFormat().mChannelsPerFrame; ++o) {
+ UInt32 outputStride = 0;
+ Float32* dst = sample(output, o, outputStride);
+ Float32 gain = value(mGains, 0xffffffff) *
+ value(mGains, ((inputOffset+i) << 16) | 0xffff) *
+ value(mGains, 0xffff0000 | (outputOffset+o)) *
+ value(mGains, ((inputOffset+i) << 16) | (outputOffset+o));
+ if (!src || !dst || !gain) continue;
+ for (UInt32 f = 0; f < frames; ++f) dst[f*outputStride] += src[f*inputStride] * gain;
+ silent = false;
+ }
+ }
+ }
+ }
+ inputOffset += inputChannels;
+ }
+ if (silent) flags |= kAudioUnitRenderAction_OutputIsSilence;
+ else flags &= ~kAudioUnitRenderAction_OutputIsSilence;
+ return noErr;
+ }
+};
+#pragma GCC visibility push(default)
+AUDIOCOMPONENT_ENTRY(AUBaseFactory, MatrixMixerAU);
+#pragma GCC visibility pop
diff --git a/src/frameworks/Carbon/HIToolbox/src/SecureInput.c b/src/frameworks/Carbon/HIToolbox/src/SecureInput.c
new file mode 100644
--- /dev/null
+++ b/src/frameworks/Carbon/HIToolbox/src/SecureInput.c
@@ -0,0 +1,14 @@
+/* X11 does not provide the macOS secure-event or TSM document facilities.
+ * Export the entry points and report unsupported instead of aborting in dyld.
+ * Do not claim that ordinary X11 keyboard input has become secure. */
+int EnableSecureEventInput(void) { return -4; }
+int DisableSecureEventInput(void) { return -4; }
+int TSMSetDocumentProperty(void *document, unsigned tag, unsigned size,
+ void *data) {
+ (void)document; (void)tag; (void)size; (void)data;
+ return -4;
+}
+int TSMRemoveDocumentProperty(void *document, unsigned tag) {
+ (void)document; (void)tag;
+ return -4;
+}
diff --git a/src/frameworks/CoreMedia/include/CoreMedia/CMFormatDescription.h b/src/frameworks/CoreMedia/include/CoreMedia/CMFormatDescription.h
new file mode 100644
--- /dev/null
+++ b/src/frameworks/CoreMedia/include/CoreMedia/CMFormatDescription.h
@@ -0,0 +1,40 @@
+#ifndef _COREMEDIA_CMFORMATDESCRIPTION_H_
+#define _COREMEDIA_CMFORMATDESCRIPTION_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_709_2;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_240M_1995;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_UseGamma;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2020;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_428_1;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_Linear;
+extern const CFStringRef kCMFormatDescriptionTransferFunction_sRGB;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_709_2;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_EBU_3213;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_SMPTE_C;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_2020;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_P3_D65;
+extern const CFStringRef kCMFormatDescriptionColorPrimaries_DCI_P3;
+extern const CFStringRef kCMFormatDescriptionExtension_ContentLightLevelInfo;
+extern const CFStringRef kCMFormatDescriptionExtension_Depth;
+extern const CFStringRef kCMFormatDescriptionExtension_FormatName;
+extern const CFStringRef kCMFormatDescriptionExtension_FullRangeVideo;
+extern const CFStringRef kCMFormatDescriptionExtension_MasteringDisplayColorVolume;
+extern const CFStringRef kCMSampleAttachmentKey_IsDependedOnByOthers;
+extern const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2;
+extern const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4;
+extern const CFStringRef kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995;
+extern const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_2020;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _COREMEDIA_CMFORMATDESCRIPTION_H_
diff --git a/src/frameworks/CryptoTokenKit/src/TKTokenWatcher.m b/src/frameworks/CryptoTokenKit/src/TKTokenWatcher.m
new file mode 100644
--- /dev/null
+++ b/src/frameworks/CryptoTokenKit/src/TKTokenWatcher.m
@@ -0,0 +1,60 @@
+#import <CryptoTokenKit/TKTokenWatcher.h>
+#import <Foundation/Foundation.h>
+
+@protocol TKProtocolTokenWatcher
+@end
+
+@implementation TKTokenWatcher
+
+- (id)init
+{
+ if ((self = [super init]) != nil) {
+ _tokenIDs = [[NSMutableArray alloc] init];
+ _removalHandlers = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (id)initWithInsertionHandler:(id)arg1
+{
+ if ((self = [self init]) != nil) {
+ _insertionHandler = [arg1 copy];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [_insertionHandler release];
+ [_removalHandlers release];
+ [_tokenIDs release];
+ [super dealloc];
+}
+
+- (NSArray *)tokenIDs
+{
+ return [NSArray array];
+}
+
+- (void)addRemovalHandler:(id)arg1 forTokenID:(id)arg2
+{
+ if (arg1 && arg2)
+ [_removalHandlers setObject: [[arg1 copy] autorelease] forKey: arg2];
+}
+
+- (void)insertedToken:(id)arg1 reply:(id)arg2
+{
+ // No smartcard/token hardware under Darling; ignore.
+}
+
+- (void)removedToken:(id)arg1 reply:(id)arg2
+{
+ // No smartcard/token hardware under Darling; ignore.
+}
+
+- (void)setup
+{
+ // No token daemon to connect to; remain empty.
+}
+
+@end
diff --git a/src/frameworks/OpenDirectory/src/ODLocalDirectory.h b/src/frameworks/OpenDirectory/src/ODLocalDirectory.h
new file mode 100644
--- /dev/null
+++ b/src/frameworks/OpenDirectory/src/ODLocalDirectory.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of Darling.
+
+ Copyright (C) 2026 Darling Team
+
+ Darling is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Darling is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#import <Foundation/Foundation.h>
+#import <OpenDirectory/OpenDirectory.h>
+
+// The local node: user records read from the system account database.
+// Every value is an array of strings, keyed by attribute type.
+
+extern NSString *const ODLocalNodeName;
+
+NSError *ODLocalError(NSInteger code);
+// nil when the name is not one of the nodes a standalone system has
+NSString *ODLocalNodeNameForName(NSString *name);
+NSString *ODLocalNodeNameForType(unsigned int type);
+NSDictionary *ODLocalUserAttributes(NSString *name);
+NSArray *ODLocalAllUserAttributes(void);
+// a single record type or a list of them
+BOOL ODLocalRecordTypesIncludeUsers(id recordTypeOrList);
+
+ODRecord *ODLocalRecordCreate(NSDictionary *attributes);
diff --git a/src/frameworks/OpenDirectory/src/ODLocalDirectory.m b/src/frameworks/OpenDirectory/src/ODLocalDirectory.m
new file mode 100644
--- /dev/null
+++ b/src/frameworks/OpenDirectory/src/ODLocalDirectory.m
@@ -0,0 +1,141 @@
+/*
+ This file is part of Darling.
+
+ Copyright (C) 2026 Darling Team
+
+ Darling is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Darling is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Darling. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#import "ODLocalDirectory.h"
+
+#include <arpa/inet.h>
+#include <membership.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <uuid/uuid.h>
+
+NSString *const ODFrameworkErrorDomain = @"com.apple.OpenDirectory";
+NSString *const ODLocalNodeName = @"/Local/Default";
+
+// getpwent() iterates process-wide state
+static pthread_mutex_t enumerationLock = PTHREAD_MUTEX_INITIALIZER;
+
+NSError *ODLocalError(NSInteger code)
+{
+ return [NSError errorWithDomain: ODFrameworkErrorDomain code: code userInfo: nil];
+}
+
+NSString *ODLocalNodeNameForName(NSString *name)
+{
+ for (NSString *known in @[ODLocalNodeName, @"/Search", @"/Contacts"]) {
+ if ([known isEqualToString: name])
+ return known;
+ }
+ return nil;
+}
+
+NSString *ODLocalNodeNameForType(unsigned int type)
+{
+ switch (type) {
+ case 0x2201: // kODNodeTypeAuthentication
+ case 0x2205: // kODNodeTypeNetwork
+ return @"/Search";
+ case 0x2204: // kODNodeTypeContacts
+ return @"/Contacts";
+ default:
+ return ODLocalNodeName;
+ }
+}
+
+static NSDictionary *attributesForEntry(const struct passwd *entry)
+{
+ NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
+ NSString *name = [NSString stringWithUTF8String: entry->pw_name];
+ uuid_t uuid;
+
+ if (name == nil)
+ return nil;
+
+ attributes[@"dsAttrTypeStandard:RecordName"] = @[name];
+ attributes[@"dsAttrTypeStandard:RecordType"] = @[@"dsRecTypeStandard:Users"];
+ attributes[@"dsAttrTypeStandard:AppleMetaNodeLocation"] = @[ODLocalNodeName];
+ attributes[@"dsAttrTypeStandard:UniqueID"] = @[[NSString stringWithFormat: @"%u", (unsigned int)entry->pw_uid]];
+ attributes[@"dsAttrTypeStandard:PrimaryGroupID"] = @[[NSString stringWithFormat: @"%u", (unsigned int)entry->pw_gid]];
+
+ if (mbr_uid_to_uuid(entry->pw_uid, uuid) != 0) {
+ // No stored UUID: use the membership library's compatibility mapping
+ // for such accounts, FFFFEEEE-DDDD-CCCC-BBBB-AAAA followed by the uid.
+ static const uuid_t prefix = {0xff, 0xff, 0xee, 0xee, 0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa};
+ uint32_t uid = htonl(entry->pw_uid);
+
+ memcpy(uuid, prefix, sizeof(uuid_t));
+ memcpy(&uuid[12], &uid, sizeof(uid));
+ }
+ uuid_string_t text;
+ uuid_unparse_upper(uuid, text);
+ attributes[@"dsAttrTypeStandard:GeneratedUID"] = @[[NSString stringWithUTF8String: text]];
+
+ NSString *value;
+ if (entry->pw_gecos != NULL && (value = [NSString stringWithUTF8String: entry->pw_gecos]) != nil)
+ attributes[@"dsAttrTypeStandard:RealName"] = @[value];
+ if (entry->pw_dir != NULL && (value = [NSString stringWithUTF8String: entry->pw_dir]) != nil)
+ attributes[@"dsAttrTypeStandard:NFSHomeDirectory"] = @[value];
+ if (entry->pw_shell != NULL && (value = [NSString stringWithUTF8String: entry->pw_shell]) != nil)
+ attributes[@"dsAttrTypeStandard:UserShell"] = @[value];
+
+ return attributes;
+}
+
+NSDictionary *ODLocalUserAttributes(NSString *name)
+{
+ struct passwd entry, *result = NULL;
+ char buffer[4096];
+
+ if (![name isKindOfClass: [NSString class]] || [name UTF8String] == NULL)
+ return nil;
+ if (getpwnam_r([name UTF8String], &entry, buffer, sizeof(buffer), &result) != 0 || result == NULL)
+ return nil;
+
+ return attributesForEntry(result);
+}
+
+NSArray *ODLocalAllUserAttributes(void)
+{
+ NSMutableArray *users = [NSMutableArray array];
+ struct passwd *entry;
+
+ pthread_mutex_lock(&enumerationLock);
+ setpwent();
+ while ((entry = getpwent()) != NULL) {
+ NSDictionary *attributes = attributesForEntry(entry);
+ if (attributes != nil)
+ [users addObject: attributes];
+ }
+ endpwent();
+ pthread_mutex_unlock(&enumerationLock);
+
+ return users;
+}
+
+BOOL ODLocalRecordTypesIncludeUsers(id recordTypeOrList)
+{
+ NSArray *types = [recordTypeOrList isKindOfClass: [NSArray class]] ? recordTypeOrList
+ : recordTypeOrList != nil ? @[recordTypeOrList] : @[];
+
+ for (id type in types) {
+ if ([type isKindOfClass: [NSString class]] && [type isEqualToString: @"dsRecTypeStandard:Users"])
+ return YES;
+ }
+ return NO;
+}
diff --git a/src/frameworks/OpenGL/CGLAsyncPresent.inc b/src/frameworks/OpenGL/CGLAsyncPresent.inc
new file mode 100644
--- /dev/null
+++ b/src/frameworks/OpenGL/CGLAsyncPresent.inc
@@ -0,0 +1,190 @@
+/* Bounded GPU-only staging: the producer never waits for display cadence.
+ * Three textures cover the currently displayed, pending, and writing frames.
+ * GPU fences order both the completed producer copy and subsequent reuse. */
+struct DarlingAsyncPresent {
+ CGLContextObj owner;
+ pthread_mutex_t mutex;
+ pthread_t thread;
+ int started, running, failed, displayed;
+ unsigned width, height;
+ GLuint textures[3], copy_fbos[2];
+ int state[3]; /* 0 free, 1 pending, 2 display, 3 producer writing */
+ GLsync produced[3], consumed[3];
+ unsigned submissions, frames;
+ uint64_t serial[3];
+};
+static uint64_t nativeMonotonicNS(void) {
+ struct timespec now;
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228))return 0;
+ return (uint64_t)now.tv_sec*1000000000ull+now.tv_nsec;
+}
+static void nativeWaitUntil(uint64_t deadline) {
+ struct timespec wake={deadline/1000000000ull,deadline%1000000000ull};
+ while(__lolsys_syscall(1,1,(long)&wake,0,0,0,230)==-4){}
+}
+static void *asyncPresentWorker(void *opaque) {
+ struct DarlingAsyncPresent *a=opaque;
+ CGLContextObj ctx=a->owner;
+ int valid=p_eglBindAPI(EGL_OPENGL_API) && p_eglMakeCurrent(ctx->egl_display,
+ ctx->native_present_surface,ctx->native_present_surface,ctx->native_present_context);
+ GLuint fbo=0;
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_ASYNC worker_context=%d\n",valid);
+ /* The async context can wait for native VSync without blocking the
+ * Chromium producer. Keep the monotonic limiter as a fallback/bound. */
+ int nativeVSync=0;
+ unsigned slowNativeSwaps=0;
+ if(valid){
+ const char *vsync=getenv("RCLOL_GPU_DIRECT_VSYNC");
+ nativeVSync=vsync && vsync[0]=='1' && p_eglSwapInterval(ctx->egl_display,1);
+ if(!nativeVSync)p_eglSwapInterval(ctx->egl_display,0);
+ glGenFramebuffers(1,&fbo);
+ }
+ uint64_t deadline=nativeMonotonicNS();
+ for(;;){
+ pthread_mutex_lock(&a->mutex);
+ int running=a->running,index=-1;
+ GLsync produced=NULL;
+ if(running && valid){
+ for(int n=0;n<3;++n)if(a->state[n]==1 && a->produced[n] && (a->displayed<0 || a->serial[n]>a->serial[a->displayed])){
+ GLenum ready=glClientWaitSync(a->produced[n],0,0);
+ if(ready==GL_WAIT_FAILED){valid=0;break;}
+ if((ready==GL_ALREADY_SIGNALED || ready==GL_CONDITION_SATISFIED) &&
+ (index<0 || a->serial[n]>a->serial[index]))index=n;
+ }
+ if(valid && index>=0){
+ for(int n=0;n<3;++n)if(a->state[n]==1 && a->serial[n]<a->serial[index])a->state[n]=0;
+ a->state[index]=2;produced=a->produced[index];a->produced[index]=NULL;
+ }
+ }
+ pthread_mutex_unlock(&a->mutex);
+ if(!running || !valid)break;
+ DarlingNativePresentTarget *target=ctx->native_present_target;
+ if(!target || __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)!=DARLING_NATIVE_PRESENT_MAGIC || target->target!=ctx->native_present_window){if(produced)glDeleteSync(produced);valid=0;break;}
+ if(index>=0){
+ uint64_t workStarted=nativeMonotonicNS();
+ if(produced){glWaitSync(produced,0,GL_TIMEOUT_IGNORED);glDeleteSync(produced);}
+ glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,a->textures[index],0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);glDrawBuffer(GL_BACK);glReadBuffer(GL_COLOR_ATTACHMENT0);
+ glDisable(GL_SCISSOR_TEST);glDisable(GL_FRAMEBUFFER_SRGB);
+ if(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE){valid=0;break;}
+ glBlitFramebuffer(0,a->height,a->width,0,0,0,a->width,a->height,GL_COLOR_BUFFER_BIT,GL_NEAREST);
+ GLsync consumed=glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE,0);glFlush();
+ int swapped=consumed && glGetError()==GL_NO_ERROR && p_eglSwapBuffers(ctx->egl_display,ctx->native_present_surface);
+ uint64_t workFinished=nativeMonotonicNS();
+ /* Xwayland may acknowledge interval 1 but throttle an occluded
+ * surface to one swap per second. Two consecutive long swaps
+ * select our existing bounded 60 Hz clock for this surface.
+ * Keep texture ownership and the producer independent of it. */
+ if(nativeVSync && swapped){
+ slowNativeSwaps=workFinished-workStarted>=100000000ull?slowNativeSwaps+1:0;
+ if(slowNativeSwaps>=2 && p_eglSwapInterval(ctx->egl_display,0)){
+ nativeVSync=0;
+ deadline=workFinished;
+ }
+ }
+ pthread_mutex_lock(&a->mutex);
+ a->consumed[index]=consumed;
+ if(a->displayed>=0)a->state[a->displayed]=0;
+ a->displayed=index;
+ if(swapped)++a->frames;
+ pthread_mutex_unlock(&a->mutex);
+ if(!swapped){valid=0;break;}
+ __atomic_store_n(&target->presenter,getpid(),__ATOMIC_RELEASE);
+ __atomic_fetch_add(&target->sequence,1,__ATOMIC_RELEASE);
+ __atomic_store_n(&target->ready,3,__ATOMIC_RELEASE);
+ __atomic_fetch_add(&target->reserved[3],(uint32_t)((workFinished-workStarted)/1000),__ATOMIC_RELAXED);
+ __atomic_store_n(&target->reserved[6],nativeVSync?0x544d4732u:0x544d4731u,__ATOMIC_RELEASE);
+ }
+ /* If the producer finishes just after our refresh deadline, wait for
+ * that frame in short increments instead of losing a whole refresh.
+ * Only a real swap advances the display clock. */
+ if(index<0){__atomic_fetch_add(&target->reserved[1],1,__ATOMIC_RELAXED);nativeWaitUntil(nativeMonotonicNS()+1000000ull);continue;}
+ /* Native VSync owns the refresh cadence; avoid a second clock that
+ * can miss the compositor's next refresh after a variable swap wait. */
+ if(nativeVSync)continue;
+ deadline+=16666667ull;
+ uint64_t now=nativeMonotonicNS();
+ /* Preserve the refresh phase across small scheduling delays. Reset only
+ * 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;}
+ nativeWaitUntil(deadline);
+ }
+ if(fbo)glDeleteFramebuffers(1,&fbo);
+ p_eglMakeCurrent(ctx->egl_display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);
+ pthread_mutex_lock(&a->mutex);a->failed=!valid;a->running=0;pthread_mutex_unlock(&a->mutex);
+ if(ctx->native_present_target)__atomic_store_n(&ctx->native_present_target->ready,0,__ATOMIC_RELEASE);
+ return NULL;
+}
+static void stopAsyncPresent(CGLContextObj ctx) {
+ struct DarlingAsyncPresent *a=ctx->native_async;
+ if(!a)return;
+ pthread_mutex_lock(&a->mutex);a->running=0;pthread_mutex_unlock(&a->mutex);
+ if(a->started)pthread_join(a->thread,NULL);
+ CGLContextObj previous=CGLGetCurrentContext();
+ if(p_eglMakeCurrent(ctx->egl_display,ctx->egl_surface,ctx->egl_surface,ctx->egl_context)){
+ for(int n=0;n<3;++n){if(a->produced[n])glDeleteSync(a->produced[n]);if(a->consumed[n])glDeleteSync(a->consumed[n]);}
+ glDeleteFramebuffers(2,a->copy_fbos);glDeleteTextures(3,a->textures);
+ if(previous && previous!=ctx)p_eglMakeCurrent(previous->egl_display,previous->egl_surface,previous->egl_surface,previous->egl_context);
+ else if(!previous)p_eglMakeCurrent(ctx->egl_display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);
+ }
+ pthread_mutex_destroy(&a->mutex);free(a);ctx->native_async=NULL;
+}
+static int enqueueAsyncPresent(CGLContextObj ctx,GLuint texture,GLenum textureTarget,unsigned width,unsigned height) {
+ struct DarlingAsyncPresent *a=ctx->native_async;
+ if(a && (a->width!=width || a->height!=height)){stopAsyncPresent(ctx);a=NULL;}
+ if(!a){
+ a=calloc(1,sizeof(*a));if(!a)return 0;
+ a->owner=ctx;a->width=width;a->height=height;a->displayed=-1;
+ pthread_mutex_init(&a->mutex,NULL);
+ GLint oldTexture;glGetIntegerv(GL_TEXTURE_BINDING_2D,&oldTexture);
+ glGenFramebuffers(2,a->copy_fbos);glGenTextures(3,a->textures);
+ for(int n=0;n<3;++n){glBindTexture(GL_TEXTURE_2D,a->textures[n]);glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL);}
+ glBindTexture(GL_TEXTURE_2D,oldTexture);ctx->native_async=a;
+ GLenum allocationError=glGetError();if(allocationError!=GL_NO_ERROR){if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_ASYNC allocation_error=%u\n",allocationError);stopAsyncPresent(ctx);return 0;}
+ a->running=1;
+ pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setstacksize(&attr,1024*1024);
+ int status=pthread_create(&a->thread,&attr,asyncPresentWorker,a);pthread_attr_destroy(&attr);
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_ASYNC worker_create=%d\n",status);
+ if(status){stopAsyncPresent(ctx);return 0;}a->started=1;
+ }
+ pthread_mutex_lock(&a->mutex);
+ if(a->failed || !a->running){pthread_mutex_unlock(&a->mutex);return 0;}
+ int index=-1;
+ for(int n=0;n<3;++n)if(!a->state[n]){index=n;break;}
+ /* Keep an older pending frame available to finish while replacing the
+ * newest pending frame with fresh producer work. Never touch display slots. */
+ if(index<0)for(int n=0;n<3;++n)if(a->state[n]==1 &&
+ (index<0 || a->serial[n]>a->serial[index]))index=n;
+ if(index<0){pthread_mutex_unlock(&a->mutex);return 0;}
+ a->state[index]=3;
+ GLsync consumed=a->consumed[index],produced=a->produced[index];
+ a->consumed[index]=a->produced[index]=NULL;
+ pthread_mutex_unlock(&a->mutex);
+ if(consumed){glWaitSync(consumed,0,GL_TIMEOUT_IGNORED);glDeleteSync(consumed);}
+ if(produced)glDeleteSync(produced);
+ GLint oldRead,oldDraw;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING,&oldRead);glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING,&oldDraw);
+ GLboolean scissor=glIsEnabled(GL_SCISSOR_TEST),srgb=glIsEnabled(GL_FRAMEBUFFER_SRGB);
+ glDisable(GL_SCISSOR_TEST);glDisable(GL_FRAMEBUFFER_SRGB);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER,a->copy_fbos[0]);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,textureTarget,texture,0);glReadBuffer(GL_COLOR_ATTACHMENT0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER,a->copy_fbos[1]);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,a->textures[index],0);glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ int complete=glCheckFramebufferStatus(GL_READ_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE && glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE;
+ if(complete)glBlitFramebuffer(0,0,width,height,0,0,width,height,GL_COLOR_BUFFER_BIT,GL_NEAREST);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER,oldRead);glBindFramebuffer(GL_DRAW_FRAMEBUFFER,oldDraw);
+ if(scissor)glEnable(GL_SCISSOR_TEST);if(srgb)glEnable(GL_FRAMEBUFFER_SRGB);
+ produced=glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE,0);glFlush();
+ GLenum copyError=glGetError();
+ if(!complete || !produced || copyError!=GL_NO_ERROR){if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_ASYNC copy_error=%u fence=%d\n",copyError,produced!=NULL);if(produced)glDeleteSync(produced);pthread_mutex_lock(&a->mutex);a->state[index]=0;pthread_mutex_unlock(&a->mutex);return 0;}
+ pthread_mutex_lock(&a->mutex);
+ a->produced[index]=produced;a->state[index]=1;a->serial[index]=++a->submissions;
+ if(ctx->native_present_target)__atomic_fetch_add(&ctx->native_present_target->reserved[0],1,__ATOMIC_RELAXED);
+ if(ctx->native_present_target){
+ __atomic_store_n(&ctx->native_present_target->reserved[4],__atomic_load_n(&nativeUploadCount,__ATOMIC_RELAXED),__ATOMIC_RELAXED);
+ __atomic_store_n(&ctx->native_present_target->reserved[5],__atomic_load_n(&nativeUploadMicroseconds,__ATOMIC_RELAXED),__ATOMIC_RELAXED);
+ }
+ pthread_mutex_unlock(&a->mutex);
+ return 1;
+}
diff --git a/src/frameworks/OpenGL/include/OpenGL/DarlingNativePresent.h b/src/frameworks/OpenGL/include/OpenGL/DarlingNativePresent.h
new file mode 100644
--- /dev/null
+++ b/src/frameworks/OpenGL/include/OpenGL/DarlingNativePresent.h
@@ -0,0 +1,11 @@
+#ifndef DARLING_NATIVE_PRESENT_H
+#define DARLING_NATIVE_PRESENT_H
+#include <stdint.h>
+#define DARLING_NATIVE_PRESENT_MAGIC 0x47505532u
+/* Host X11 target metadata only; no pixels, input, or account information. */
+typedef struct {
+ uint32_t magic,target,width,height;
+ uint32_t ready,presenter,sequence,visual;
+ uint32_t flags,reserved[7];
+} DarlingNativePresentTarget;
+#endif
diff --git a/src/startup/mldr/commpage-cpu-caps.h b/src/startup/mldr/commpage-cpu-caps.h
new file mode 100644
--- /dev/null
+++ b/src/startup/mldr/commpage-cpu-caps.h
@@ -0,0 +1,41 @@
+#ifndef MLDR_COMMPAGE_CPU_CAPS_H
+#define MLDR_COMMPAGE_CPU_CAPS_H
+#include <stdint.h>
+
+/* CPUID leaf 1 uses EDX for legacy SIMD and ECX for newer SIMD. AVX
+ * also needs the operating system to save both XMM and YMM state. */
+static inline uint64_t commpage_decode_cpu_caps(uint32_t ecx1, uint32_t edx1,
+ uint32_t ebx7, uint64_t xcr0, int long_mode)
+{
+ uint64_t caps = long_mode ? k64Bit : 0;
+#define FEATURE(reg, bit, cap) do { if ((reg) & (UINT32_C(1) << (bit))) caps |= (cap); } while (0)
+ FEATURE(edx1, 23, kHasMMX);
+ FEATURE(edx1, 25, kHasSSE);
+ FEATURE(edx1, 26, kHasSSE2);
+ FEATURE(ecx1, 0, kHasSSE3);
+ FEATURE(ecx1, 9, kHasSupplementalSSE3);
+ FEATURE(ecx1, 19, kHasSSE4_1);
+ FEATURE(ecx1, 20, kHasSSE4_2);
+ FEATURE(ecx1, 25, kHasAES);
+ FEATURE(ecx1, 30, kHasRDRAND);
+ const uint32_t avx_requirements = (UINT32_C(1) << 26) | (UINT32_C(1) << 27) | (UINT32_C(1) << 28);
+ const int avx_usable = (ecx1 & avx_requirements) == avx_requirements && (xcr0 & 6) == 6;
+ if (avx_usable) {
+ caps |= kHasAVX1_0;
+ FEATURE(ecx1, 12, kHasFMA);
+ FEATURE(ecx1, 29, kHasF16C);
+ FEATURE(ebx7, 5, kHasAVX2_0);
+ }
+ FEATURE(ebx7, 9, kHasENFSTRG);
+ FEATURE(ebx7, 3, kHasBMI1);
+ FEATURE(ebx7, 8, kHasBMI2);
+ FEATURE(ebx7, 11, kHasRTM);
+ FEATURE(ebx7, 4, kHasHLE);
+ FEATURE(ebx7, 18, kHasRDSEED);
+ FEATURE(ebx7, 19, kHasADX);
+ FEATURE(ebx7, 14, kHasMPX);
+ FEATURE(ebx7, 2, kHasSGX);
+#undef FEATURE
+ return caps;
+}
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5c924b34d..54f134873 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -119,6 +119,13 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}")
+# Recent clang compiles an empty @[] / @{} for a macOS 11+ runtime into a reference to
+# __NSArray0__struct / __NSDictionary0__struct, which Darling's CoreFoundation does not define
+# (it provides __NSArray0__ / __NSDictionary0__). Keep the earlier code generation.
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fobjc-runtime=macosx-10.15")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-runtime=macosx-10.15")
+set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -fobjc-runtime=macosx-10.15")
+
add_definitions(-target ${APPLE_TARGET_TRIPLET_PRIMARY})
include(darling_lib)
include(darling_static_lib)
diff --git a/src/libelfloader/wrapgen/wrapgen.cpp b/src/libelfloader/wrapgen/wrapgen.cpp
index 7079e613e..397ec6de0 100644
--- a/src/libelfloader/wrapgen/wrapgen.cpp
+++ b/src/libelfloader/wrapgen/wrapgen.cpp
@@ -268,7 +268,10 @@ void generate_wrapper(std::ofstream& output, const char* soname, const std::set<
for (const std::string& sym : symbols)
{
- output << "void* " << sym << "() {\n"
+ // ld64's x86-64 resolver stub saves integer argument registers only.
+ // ELF lookup can clobber XMM arguments before the stub jumps to its
+ // resolved function. Preserve them in the resolver itself.
+ output << "__attribute__((preserve_all)) void* " << sym << "() {\n"
"\t__asm__(\".symbol_resolver _" << sym << "\");\n"
"\treturn _elfcalls->dlsym_fatal(lib_handle, \"" << sym << "\");\n"
"}\n\n";
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m b/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
index 4cb2a4941..f08d05a2b 100644
--- a/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
@@ -106,7 +106,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
{
if (!inBundleIdentifier)
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, paramErr, NULL);
return NULL;
}
@@ -116,7 +116,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
if (dq == nil)
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
return NULL;
}
@@ -138,7 +138,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
}
else
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
retval = NULL;
}
@@ -247,7 +247,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
if (dq == nil)
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
return NULL;
}
@@ -275,7 +275,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
}
else
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
retval = NULL;
}
@@ -292,7 +292,7 @@ CFURLRef LSCopyDefaultApplicationURLForURL(CFURLRef inURL, LSRolesMask inRoleMas
if (status != noErr)
{
- if (*outError)
+ if (outError)
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, status, NULL);
return NULL;
}