mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
The patch comments and the Mach-O strings filter named an anti-cheat product. Use neutral wording; the patches still differ only in comments. The platform-identity document now says the overrides are for compatibility, not a bypass of anti-cheat, telemetry, licensing or platform controls.
11371 lines
434 KiB
Diff
11371 lines
434 KiB
Diff
diff --git a/src/CoreAudio/AudioToolbox/AUComponent.cpp b/src/CoreAudio/AudioToolbox/AUComponent.cpp
|
|
index d48eec1ef..1deb304e9 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
|
|
@@ -121,7 +148,14 @@ AudioUnitGetParameter(AudioUnit inUnit,
|
|
AudioUnitElement inElement,
|
|
AudioUnitParameterValue * outValue)
|
|
{
|
|
- return dispatchCall(inUnit, kAudioUnitGetParameterSelect, inID, inScope, inElement, outValue);
|
|
+ OSStatus status = dispatchCall(inUnit, kAudioUnitGetParameterSelect, inID, inScope, inElement, outValue);
|
|
+ {
|
|
+ static unsigned count = 0;
|
|
+ if (rclolAudioTraceEnabled() && (count++ < 200 || count % 500 == 0))
|
|
+ rclolAudioTrace("UNIT_GET_PARAM unit=%p id=%u scope=%u elem=%u value=%g status=%d\n", (void*) inUnit,
|
|
+ (unsigned) inID, (unsigned) inScope, (unsigned) inElement, (outValue && !status) ? (double) *outValue : 0.0, (int) status);
|
|
+ }
|
|
+ return status;
|
|
}
|
|
|
|
OSStatus
|
|
@@ -132,7 +166,14 @@ AudioUnitSetParameter(AudioUnit inUnit,
|
|
AudioUnitParameterValue inValue,
|
|
UInt32 inBufferOffsetInFrames)
|
|
{
|
|
- return dispatchCall(inUnit, kAudioUnitSetParameterSelect, inID, inScope, inElement, inValue, inBufferOffsetInFrames);
|
|
+ OSStatus status = dispatchCall(inUnit, kAudioUnitSetParameterSelect, inID, inScope, inElement, inValue, inBufferOffsetInFrames);
|
|
+ {
|
|
+ static unsigned count = 0;
|
|
+ if (rclolAudioTraceEnabled() && (count++ < 200 || count % 500 == 0))
|
|
+ rclolAudioTrace("UNIT_SET_PARAM unit=%p id=%u scope=%u elem=%u value=%g offset=%u status=%d\n", (void*) inUnit,
|
|
+ (unsigned) inID, (unsigned) inScope, (unsigned) inElement, (double) inValue, (unsigned) inBufferOffsetInFrames, (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..8588e69d8 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(rclolAudioTraceEnabled() && 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..5672fcba1 100644
|
|
--- a/src/CoreAudio/AudioToolbox/AudioConverter.cpp
|
|
+++ b/src/CoreAudio/AudioToolbox/AudioConverter.cpp
|
|
@@ -19,16 +19,39 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "AudioConverter.h"
|
|
#include "AudioConverterImpl.h"
|
|
+#include "../AudioTrace.h"
|
|
#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 = rclolAudioTraceEnabled();
|
|
+ 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 +70,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..647c8aff7 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,45 +101,94 @@ void AudioObjectShow(AudioObjectID inObjectID)
|
|
Boolean AudioObjectHasProperty(AudioObjectID inObjectID,
|
|
const AudioObjectPropertyAddress* inAddress)
|
|
{
|
|
+ if((inObjectID&0xffff0000u)==0x44410000u) {
|
|
+ UInt32 size=0;bool has=GetStreamPropertyData(inObjectID,inAddress,&size,nullptr)==noErr;
|
|
+ rclolAudioTrace("HAS stream=%08x selector=%08x scope=%08x has=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,has);
|
|
+ return has;
|
|
+ }
|
|
AudioHardwareImpl* obj = GetObject(inObjectID);
|
|
- if (!obj)
|
|
+ if (!obj) {
|
|
+ rclolAudioTrace("HAS object=%u selector=%08x BADOBJECT\n",inObjectID,inAddress->mSelector);
|
|
return 0;
|
|
+ }
|
|
|
|
- return obj->hasProperty(inAddress);
|
|
+ bool has=obj->hasProperty(inAddress);
|
|
+ rclolAudioTrace("HAS object=%u selector=%08x scope=%08x elem=%u has=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,inAddress->mElement,has);
|
|
+ return has;
|
|
}
|
|
|
|
OSStatus AudioObjectIsPropertySettable(AudioObjectID inObjectID,
|
|
const AudioObjectPropertyAddress* inAddress, Boolean* outIsSettable)
|
|
{
|
|
AudioHardwareImpl* obj = GetObject(inObjectID);
|
|
- if (!obj)
|
|
+ if (!obj) {
|
|
+ rclolAudioTrace("SETTABLE object=%08x selector=%08x BADOBJECT\n",inObjectID,inAddress->mSelector);
|
|
return kAudioHardwareBadObjectError;
|
|
+ }
|
|
|
|
- return obj->isPropertySettable(inAddress, outIsSettable);
|
|
+ OSStatus status=obj->isPropertySettable(inAddress, outIsSettable);
|
|
+ rclolAudioTrace("SETTABLE object=%u selector=%08x scope=%08x elem=%u status=%d settable=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,inAddress->mElement,status,status?0:*outIsSettable);
|
|
+ return status;
|
|
}
|
|
|
|
OSStatus AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
|
const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize,
|
|
const void* inQualifierData, UInt32* outDataSize)
|
|
{
|
|
+ if((inObjectID&0xffff0000u)==0x44410000u) {
|
|
+ OSStatus status=GetStreamPropertyData(inObjectID,inAddress,outDataSize,nullptr);
|
|
+ rclolAudioTrace("SIZE stream=%08x selector=%08x scope=%08x status=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,status);
|
|
+ return status;
|
|
+ }
|
|
AudioHardwareImpl* obj = GetObject(inObjectID);
|
|
- if (!obj)
|
|
+ if (!obj) {
|
|
+ rclolAudioTrace("SIZE object=%08x selector=%08x BADOBJECT\n",inObjectID,inAddress->mSelector);
|
|
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) {
|
|
+ OSStatus status=GetStreamPropertyData(inObjectID,inAddress,ioDataSize,outData);
|
|
+ rclolAudioTrace("GET stream=%08x selector=%08x scope=%08x status=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,status);
|
|
+ return status;
|
|
+ }
|
|
AudioHardwareImpl* obj = GetObject(inObjectID);
|
|
- if (!obj)
|
|
+ if (!obj) {
|
|
+ rclolAudioTrace("GET object=%08x selector=%08x BADOBJECT\n",inObjectID,inAddress->mSelector);
|
|
return kAudioHardwareBadObjectError;
|
|
+ }
|
|
|
|
- return obj->getPropertyData(inAddress, inQualifierDataSize, inQualifierData,
|
|
- ioDataSize, outData);
|
|
+ static unsigned traceCount=0;
|
|
+ if(rclolAudioTraceEnabled() && 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 elem=%u status=%d size=%u\n",inObjectID,inAddress->mSelector,inAddress->mScope,inAddress->mElement,status,status?0:*ioDataSize);
|
|
+ return status;
|
|
}
|
|
|
|
OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID,
|
|
@@ -114,11 +196,15 @@ OSStatus AudioObjectSetPropertyData(AudioObjectID inObjectID,
|
|
const void* inQualifierData, UInt32 inDataSize, const void* inData)
|
|
{
|
|
AudioHardwareImpl* obj = GetObject(inObjectID);
|
|
- if (!obj)
|
|
+ if (!obj) {
|
|
+ rclolAudioTrace("SET object=%08x selector=%08x scope=%08x elem=%u BADOBJECT\n",inObjectID,inAddress->mSelector,inAddress->mScope,inAddress->mElement);
|
|
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 elem=%u size=%u value=%g status=%d\n",inObjectID,inAddress->mSelector,inAddress->mScope,inAddress->mElement,inDataSize,
|
|
+ (inData && inDataSize==sizeof(Float32))?(double)*(const Float32*)inData:0.0,status);
|
|
+ return status;
|
|
}
|
|
|
|
OSStatus AudioObjectAddPropertyListener(AudioObjectID inObjectID,
|
|
@@ -129,7 +215,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..03a530bfd 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPA.h
|
|
@@ -38,6 +38,9 @@ public:
|
|
const void* inQualifierData, UInt32 inDataSize, const void* inData) override;
|
|
|
|
void getPAContext(void (^cb)(pa_context*));
|
|
+ void syncPA(void (^block)(void));
|
|
+ dispatch_queue_t paQueue() const { return m_loop ? m_loop->queue() : nullptr; }
|
|
+ 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 +52,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..fd9efc988 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
|
|
@@ -21,10 +21,37 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#include "AudioHardwareImplPA.h"
|
|
#include <iostream>
|
|
#include <vector>
|
|
+#include <algorithm>
|
|
+#include <cstring>
|
|
+#include <mach/mach_time.h>
|
|
+#include "../../AudioTrace.h"
|
|
+
|
|
+static const mach_timebase_info_data_t& timebase() {
|
|
+ static mach_timebase_info_data_t tb = [] {
|
|
+ mach_timebase_info_data_t t = {0, 0};
|
|
+ mach_timebase_info(&t);
|
|
+ if (!t.numer || !t.denom) t.numer = t.denom = 1;
|
|
+ return t;
|
|
+ }();
|
|
+ return tb;
|
|
+}
|
|
+static uint64_t hostToNs(uint64_t host) {
|
|
+ return uint64_t((unsigned __int128) host * timebase().numer / timebase().denom);
|
|
+}
|
|
+static uint64_t nsToHost(uint64_t ns) {
|
|
+ return uint64_t((unsigned __int128) ns * timebase().denom / timebase().numer);
|
|
+}
|
|
+static unsigned g_readEvents = 0;
|
|
+static bool readShouldTrace() {
|
|
+ if (!rclolAudioTraceEnabled()) return false;
|
|
+ unsigned n = g_readEvents++;
|
|
+ return n < 40 || n % 1000 == 0;
|
|
+}
|
|
|
|
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,25 +59,26 @@ 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;
|
|
|
|
- AudioTimeStamp fake = {0};
|
|
+ const pa_sample_spec* spec = pa_stream_get_sample_spec(This->m_stream);
|
|
+ const size_t frameBytes = spec ? pa_frame_size(spec) : 0;
|
|
+ const double rate = spec ? double(spec->rate) : 0;
|
|
+ if (!frameBytes || rate <= 0)
|
|
+ return;
|
|
+
|
|
AudioBufferList* abl = static_cast<AudioBufferList*>(alloca(sizeof(AudioBufferList) + sizeof(AudioBuffer)));
|
|
- std::vector<uint8_t> hole;
|
|
|
|
// std::cout << "AudioHardwareStreamPAInput::paStreamReadCB()\n";
|
|
|
|
while (true)
|
|
{
|
|
- abl->mNumberBuffers = 1;
|
|
- abl->mBuffers[0].mNumberChannels = 2;
|
|
-
|
|
- size_t nbytes;
|
|
+ const void* data = nullptr;
|
|
+ size_t nbytes = 0;
|
|
|
|
- int rv = pa_stream_peek(This->m_stream, (const void**) &abl->mBuffers[0].mData, &nbytes);
|
|
+ int rv = pa_stream_peek(This->m_stream, &data, &nbytes);
|
|
|
|
if (rv < 0 || !nbytes)
|
|
{
|
|
@@ -58,29 +86,82 @@ void AudioHardwareStreamPAInput::paStreamReadCB(pa_stream* s, size_t length, voi
|
|
break;
|
|
}
|
|
|
|
- if (nbytes && !abl->mBuffers[0].mData)
|
|
+ // The fragment at the read index was captured `latency` ago: source latency plus
|
|
+ // everything still unread. Without timing info yet, count only the unread data.
|
|
+ pa_usec_t usec = 0;
|
|
+ int negative = 0;
|
|
+ uint64_t latencyNs;
|
|
+ if (pa_stream_get_latency(This->m_stream, &usec, &negative) == 0)
|
|
+ latencyNs = negative ? 0 : uint64_t(usec) * 1000;
|
|
+ else
|
|
{
|
|
- // "If there is a hole, data will be NULL and nbytes will contain the length of the hole."
|
|
- hole.resize(nbytes);
|
|
- abl->mBuffers[0].mData = hole.data();
|
|
+ size_t readable = pa_stream_readable_size(This->m_stream);
|
|
+ latencyNs = uint64_t(pa_bytes_to_usec(std::max(readable, nbytes), spec)) * 1000;
|
|
}
|
|
- abl->mBuffers[0].mDataByteSize = nbytes;
|
|
-
|
|
- // This->m_dump.write((char*) abl->mBuffers[0].mData, nbytes);
|
|
- // This->m_dump.flush();
|
|
- if (This->m_convertSignedUnsigned)
|
|
- This->transformSignedUnsigned(abl);
|
|
+ const uint64_t nowHost = mach_absolute_time();
|
|
|
|
- // 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);
|
|
-
|
|
- pa_stream_drop(This->m_stream);
|
|
+ const bool hole = !data;
|
|
+ if (hole || This->m_convertSignedUnsigned)
|
|
+ {
|
|
+ // "If there is a hole, data will be NULL and nbytes will contain the length of the hole."
|
|
+ // Holes are delivered as silence so the sample clock keeps counting real time.
|
|
+ // The peeked memory belongs to PulseAudio, so convert a private copy.
|
|
+ This->m_scratch.resize(nbytes);
|
|
+ if (data)
|
|
+ memcpy(This->m_scratch.data(), data, nbytes);
|
|
+ else
|
|
+ memset(This->m_scratch.data(), spec->format == PA_SAMPLE_U8 ? 0x80 : 0, nbytes);
|
|
+ data = This->m_scratch.data();
|
|
+ }
|
|
|
|
- if (status != noErr)
|
|
+ // Like a real HAL, hand the IOProc at most one device buffer per call.
|
|
+ const size_t block = std::max(frameBytes, This->m_blockBytes);
|
|
+ for (size_t offset = 0; offset + frameBytes <= nbytes; )
|
|
{
|
|
- pa_stream_cork(This->m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
|
|
- break;
|
|
+ size_t piece = std::min(block, nbytes - offset);
|
|
+ piece -= piece % frameBytes;
|
|
+
|
|
+ abl->mNumberBuffers = 1;
|
|
+ abl->mBuffers[0].mNumberChannels = spec->channels;
|
|
+ abl->mBuffers[0].mData = const_cast<uint8_t*>(static_cast<const uint8_t*>(data) + offset);
|
|
+ abl->mBuffers[0].mDataByteSize = piece;
|
|
+
|
|
+ if (This->m_convertSignedUnsigned)
|
|
+ This->transformSignedUnsigned(abl);
|
|
+
|
|
+ const uint64_t offsetNs = uint64_t(double(offset / frameBytes) * 1e9 / rate);
|
|
+ const uint64_t agoNs = latencyNs > offsetNs ? latencyNs - offsetNs : 0;
|
|
+ uint64_t inputHost = nowHost - std::min(nowHost, nsToHost(agoNs));
|
|
+ // Keep host time monotonic even when PA's latency estimate jumps.
|
|
+ if (inputHost <= This->m_lastInputHost)
|
|
+ inputHost = This->m_lastInputHost + 1;
|
|
+ This->m_lastInputHost = inputHost;
|
|
+
|
|
+ AudioTimeStamp input = {0};
|
|
+ input.mSampleTime = double(This->m_frames);
|
|
+ input.mHostTime = inputHost;
|
|
+ input.mRateScalar = 1.0;
|
|
+ input.mFlags = kAudioTimeStampHostTimeValid | kAudioTimeStampSampleTimeValid | kAudioTimeStampRateScalarValid;
|
|
+
|
|
+ AudioTimeStamp now = input;
|
|
+ now.mHostTime = std::max(nowHost, inputHost);
|
|
+ now.mSampleTime = input.mSampleTime + double(hostToNs(now.mHostTime - inputHost)) * rate / 1e9;
|
|
+
|
|
+ // This->m_dump.write((char*) abl->mBuffers[0].mData, nbytes);
|
|
+ // This->m_dump.flush();
|
|
+ // A real HAL ignores the IOProc's result; one failed callback must not
|
|
+ // silence the microphone for the rest of the session.
|
|
+ OSStatus status = This->m_callback(This->m_hw->id(), &now, abl, &input, nullptr, nullptr, This->m_clientData);
|
|
+ if (readShouldTrace())
|
|
+ rclolAudioTrace("PA_IN iop bytes=%zu frames=%llu latencyMs=%.2f hole=%d status=%d\n", piece,
|
|
+ (unsigned long long) This->m_frames, double(latencyNs) / 1e6, hole, (int) status);
|
|
+
|
|
+ if (!This->m_running) return;
|
|
+ This->m_frames += piece / frameBytes;
|
|
+ offset += piece;
|
|
}
|
|
+
|
|
+ pa_stream_drop(This->m_stream);
|
|
}
|
|
}
|
|
|
|
@@ -88,10 +169,19 @@ void AudioHardwareStreamPAInput::start()
|
|
{
|
|
struct pa_buffer_attr battr;
|
|
|
|
- battr.fragsize = uint32_t(-1);
|
|
- battr.maxlength = uint32_t(m_bufferSize);
|
|
+ // Record streams only use maxlength and fragsize. Ask for fragments of one device buffer
|
|
+ // (kAudioDevicePropertyBufferFrameSize) for voice latency, and bound the server-side
|
|
+ // backlog so a stalled client can't accumulate seconds of delay.
|
|
+ const uint32_t bpf = m_hw->asbd().mBytesPerFrame;
|
|
+ uint32_t block = bpf ? (m_hw->bufferSize() / bpf) * bpf : m_bufferSize;
|
|
+ if (!block)
|
|
+ block = m_bufferSize;
|
|
+ m_blockBytes = block;
|
|
+
|
|
+ battr.fragsize = block;
|
|
+ battr.maxlength = block * 16;
|
|
battr.minreq = uint32_t(-1);
|
|
- battr.prebuf = battr.maxlength;
|
|
+ battr.prebuf = uint32_t(-1);
|
|
battr.tlength = uint32_t(-1);
|
|
|
|
// std::cout << "AudioHardwareStreamPAInput::start()\n";
|
|
@@ -101,6 +191,9 @@ void AudioHardwareStreamPAInput::start()
|
|
int rv = pa_stream_connect_record(m_stream, nullptr, &battr,
|
|
pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE)
|
|
);
|
|
+ if (rclolAudioTraceEnabled())
|
|
+ rclolAudioTrace("PA_IN start block=%u rate=%g channels=%u rv=%d\n", block, m_hw->asbd().mSampleRate,
|
|
+ m_hw->asbd().mChannelsPerFrame, rv);
|
|
|
|
// std::cout << "AudioHardwareStreamPAInput::start() rv = " << rv << std::endl;
|
|
AudioHardwareStreamPA::start();
|
|
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
|
|
index 278630f02..54b463c2f 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
|
|
@@ -21,71 +21,318 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#include "AudioHardwareImplPA.h"
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
+#include <mach/mach_time.h>
|
|
+#include "../../AudioTrace.h"
|
|
+
|
|
+// Real CoreAudio calls an output IOProc once per IO cycle: evenly spaced at the buffer
|
|
+// period, with inOutputTime.mHostTime advancing by one buffer duration per call. Clients
|
|
+// such as Chromium's AUHALStream rely on that (they read the previous renderer buffer and
|
|
+// request the next one on every call), so PulseAudio's bursty write requests must not
|
|
+// drive the IOProc directly. Instead a dispatch timer on the PA queue ticks at the block
|
|
+// period and renders at most one block per tick (plus bounded catch-up after a late wakeup).
|
|
+
|
|
+static const unsigned kPrefillBlocks = 8; // == tlength
|
|
+static const unsigned kMaxCatchUpBlocks = 2; // extra blocks after a late wakeup
|
|
+static const unsigned kServoWindowTicks = 32;
|
|
+
|
|
+static bool rateLimited(unsigned& counter, unsigned head, unsigned every) {
|
|
+ if (!rclolAudioTraceEnabled()) return false;
|
|
+ unsigned n = counter++;
|
|
+ return n < head || n % every == 0;
|
|
+}
|
|
+static unsigned g_wcbEvents = 0, g_iopEvents = 0, g_tickEvents = 0, g_paceEvents = 0;
|
|
+static bool wcbShouldTrace() { return rateLimited(g_wcbEvents, 50, 1000); }
|
|
+static bool iopShouldTrace() { return rateLimited(g_iopEvents, 200, 500); }
|
|
+static bool tickShouldTrace() { return rateLimited(g_tickEvents, 200, 500); }
|
|
+static bool paceShouldTrace() { return rateLimited(g_paceEvents, 100, 100); }
|
|
+
|
|
+static const mach_timebase_info_data_t& timebase() {
|
|
+ static mach_timebase_info_data_t tb = [] {
|
|
+ mach_timebase_info_data_t t = {0, 0};
|
|
+ mach_timebase_info(&t);
|
|
+ if (!t.numer || !t.denom) t.numer = t.denom = 1;
|
|
+ return t;
|
|
+ }();
|
|
+ return tb;
|
|
+}
|
|
+static uint64_t hostToNs(uint64_t host) {
|
|
+ return uint64_t((unsigned __int128) host * timebase().numer / timebase().denom);
|
|
+}
|
|
+static uint64_t nsToHost(uint64_t ns) {
|
|
+ return uint64_t((unsigned __int128) ns * timebase().denom / timebase().numer);
|
|
+}
|
|
+static uint64_t nowNs() { return hostToNs(mach_absolute_time()); }
|
|
|
|
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::~AudioHardwareStreamPAOutput()
|
|
{
|
|
- AudioHardwareStreamPAOutput* This = static_cast<AudioHardwareStreamPAOutput*>(self);
|
|
- std::unique_lock<std::mutex> l(This->m_stopMutex);
|
|
+ // Normally already done by stop(); the timer must never outlive this object.
|
|
+ static_cast<AudioHardwareImplPA*>(m_hw)->syncPA(^{ m_running = false; cancelTimer(); });
|
|
+}
|
|
|
|
- if (!This->m_running)
|
|
+void AudioHardwareStreamPAOutput::stop()
|
|
+{
|
|
+ // Runs on (or synchronously with) the PA queue, so no tick is in flight unless stop()
|
|
+ // was called from inside the IOProc, which renderBlock() handles via m_running.
|
|
+ // Cancellation guarantees the timer handler is never invoked again.
|
|
+ static_cast<AudioHardwareImplPA*>(m_hw)->syncPA(^{ m_running = false; cancelTimer(); });
|
|
+ AudioHardwareStreamPA::stop();
|
|
+}
|
|
+
|
|
+void AudioHardwareStreamPAOutput::cancelTimer()
|
|
+{
|
|
+ if (m_timer)
|
|
{
|
|
- pa_stream_cork(This->m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
|
|
- return;
|
|
+ dispatch_source_cancel(m_timer);
|
|
+ dispatch_release(m_timer);
|
|
+ m_timer = nullptr;
|
|
+ }
|
|
+}
|
|
+
|
|
+uint64_t AudioHardwareStreamPAOutput::outputLatencyNs(size_t writable, bool* fromPA)
|
|
+{
|
|
+ pa_usec_t usec = 0;
|
|
+ int negative = 0;
|
|
+ if (pa_stream_get_latency(m_stream, &usec, &negative) == 0)
|
|
+ {
|
|
+ *fromPA = true;
|
|
+ return negative ? 0 : uint64_t(usec) * 1000;
|
|
}
|
|
+ // No timing info yet: at least account for what is queued in the PA buffer.
|
|
+ *fromPA = false;
|
|
+ const pa_buffer_attr* attr = pa_stream_get_buffer_attr(m_stream);
|
|
+ const pa_sample_spec* spec = pa_stream_get_sample_spec(m_stream);
|
|
+ if (!attr || !spec || writable == size_t(-1) || attr->tlength <= writable)
|
|
+ return 0;
|
|
+ return uint64_t(pa_bytes_to_usec(attr->tlength - writable, spec)) * 1000;
|
|
+}
|
|
+
|
|
+AudioHardwareStreamPAOutput::RenderResult AudioHardwareStreamPAOutput::renderBlock(bool trace)
|
|
+{
|
|
+ if (!m_running || m_corked || !m_stream || !m_blockBytes)
|
|
+ return RenderResult::Stopped;
|
|
|
|
- // std::cout << "AudioHardwareStreamPAOutput::paStreamWriteCB()\n";
|
|
+ const size_t writable = pa_stream_writable_size(m_stream);
|
|
+ if (writable == size_t(-1) || writable < m_blockBytes)
|
|
+ return RenderResult::NoSpace;
|
|
|
|
- AudioTimeStamp fake = {0};
|
|
AudioBufferList* abl = static_cast<AudioBufferList*>(alloca(sizeof(AudioBufferList) + sizeof(AudioBuffer)));
|
|
+ // Non-interleaved (planar) audio would have multiple buffers, but PA doesn't even support that AFAIK
|
|
+ abl->mNumberBuffers = 1;
|
|
+ abl->mBuffers[0].mNumberChannels = m_hw->asbd().mChannelsPerFrame;
|
|
+
|
|
+ size_t rqsize = m_blockBytes;
|
|
+ if (pa_stream_begin_write(m_stream, (void**)&abl->mBuffers[0].mData, &rqsize) < 0 || !rqsize)
|
|
+ {
|
|
+ if (paceShouldTrace()) rclolAudioTrace("PA_OUT begin_write failed rqsize=%zu\n", rqsize);
|
|
+ return RenderResult::NoSpace;
|
|
+ }
|
|
+ if (rqsize < m_blockBytes)
|
|
+ {
|
|
+ // Never hand the client a partial block (real HALs use a fixed frame count).
|
|
+ if (paceShouldTrace()) rclolAudioTrace("PA_OUT granted rqsize=%zu < block=%zu: cancel\n", rqsize, m_blockBytes);
|
|
+ pa_stream_cancel_write(m_stream);
|
|
+ return RenderResult::NoSpace;
|
|
+ }
|
|
+ abl->mBuffers[0].mDataByteSize = m_blockBytes;
|
|
+
|
|
+ bool latencyFromPA = false;
|
|
+ const uint64_t latencyNs = outputLatencyNs(writable, &latencyFromPA);
|
|
+ const double rate = m_hw->asbd().mSampleRate;
|
|
+ const double latencyFrames = rate > 0 ? double(latencyNs) * rate / 1e9 : 0;
|
|
+ const uint64_t nowHost = mach_absolute_time();
|
|
|
|
- size_t done = 0;
|
|
+ AudioTimeStamp now = {0};
|
|
+ now.mHostTime = nowHost;
|
|
+ // Device position currently being played: the block about to be rendered starts
|
|
+ // latencyFrames after it, matching the host-time difference below.
|
|
+ now.mSampleTime = std::max(0.0, double(m_frames) - latencyFrames);
|
|
+ now.mRateScalar = 1.0;
|
|
+ now.mFlags = kAudioTimeStampHostTimeValid | kAudioTimeStampSampleTimeValid | kAudioTimeStampRateScalarValid;
|
|
|
|
- while (done < length && This->m_running)
|
|
+ AudioTimeStamp output = now;
|
|
+ output.mHostTime = nowHost + nsToHost(latencyNs);
|
|
+ output.mSampleTime = double(m_frames);
|
|
+
|
|
+ const double dHostMs = m_lastIOProcHost ? double(hostToNs(nowHost - m_lastIOProcHost)) / 1e6 : 0;
|
|
+ m_lastIOProcHost = nowHost;
|
|
+
|
|
+ OSStatus status = m_callback(m_hw->id(), &now, nullptr, nullptr, abl, &output, m_clientData);
|
|
+ if (trace)
|
|
+ rclolAudioTrace("PA_OUT iop dHostMs=%.3f writable=%zu latencyMs=%.2f(%s) frames=%llu status=%d size=%u\n",
|
|
+ dHostMs, writable, double(latencyNs) / 1e6, latencyFromPA ? "pa" : "est",
|
|
+ (unsigned long long) m_frames, (int) status, (unsigned) abl->mBuffers[0].mDataByteSize);
|
|
+
|
|
+ if (!m_running)
|
|
{
|
|
- // Non-interleaved (planar) audio would have multiple buffers, but PA doesn't even support that AFAIK
|
|
- abl->mNumberBuffers = 1;
|
|
+ // The client stopped/disconnected the stream during its IOProc.
|
|
+ pa_stream_cancel_write(m_stream);
|
|
+ return RenderResult::Stopped;
|
|
+ }
|
|
+ if (status != noErr || !abl->mBuffers[0].mDataByteSize)
|
|
+ {
|
|
+ if (paceShouldTrace()) rclolAudioTrace("PA_OUT IOProc failed/empty status=%d size=%u: CORK\n", (int) status,
|
|
+ (unsigned) abl->mBuffers[0].mDataByteSize);
|
|
+ pa_stream_cancel_write(m_stream);
|
|
+ pa_stream_cork(m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
|
|
+ m_corked = true;
|
|
+ return RenderResult::Stopped;
|
|
+ }
|
|
|
|
- abl->mBuffers[0].mNumberChannels = 2;
|
|
- // abl->mBuffers[0].mData = This->m_buffer;
|
|
+ if (m_convertSignedUnsigned)
|
|
+ transformSignedUnsigned(abl);
|
|
|
|
- // 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);
|
|
+ // Always submit exactly one block so the sample clock stays block-aligned.
|
|
+ int rv = pa_stream_write(m_stream, abl->mBuffers[0].mData, m_blockBytes, nullptr, 0, PA_SEEK_RELATIVE);
|
|
+ if (rv != 0)
|
|
+ {
|
|
+ if (paceShouldTrace()) rclolAudioTrace("PA_OUT pa_stream_write rv=%d\n", rv);
|
|
+ return RenderResult::NoSpace;
|
|
+ }
|
|
+ m_frames += m_blockFrames;
|
|
+ return RenderResult::Written;
|
|
+}
|
|
|
|
- abl->mBuffers[0].mDataByteSize = rqsize;
|
|
+uint64_t AudioHardwareStreamPAOutput::deadlineNs() const
|
|
+{
|
|
+ return m_anchorNs + uint64_t(std::max(0.0, double(m_tickIndex) * m_periodNs - m_advanceNs));
|
|
+}
|
|
|
|
- // Call the client for more data
|
|
- OSStatus status = This->m_callback(This->m_hw->id(), &fake, nullptr, nullptr, abl, &fake, This->m_clientData);
|
|
- if (status != noErr || !abl->mBuffers[0].mDataByteSize)
|
|
- {
|
|
- // std::cerr << "AudioDeviceIOProc returned " << status << ", corking...\n";
|
|
+void AudioHardwareStreamPAOutput::armTimer(uint64_t now)
|
|
+{
|
|
+ if (!m_timer)
|
|
+ return;
|
|
+ const uint64_t deadline = deadlineNs();
|
|
+ const int64_t delta = deadline > now ? int64_t(deadline - now) : 0;
|
|
+ dispatch_source_set_timer(m_timer, dispatch_time(DISPATCH_TIME_NOW, delta), DISPATCH_TIME_FOREVER, 100 * NSEC_PER_USEC);
|
|
+}
|
|
|
|
- pa_stream_cork(This->m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
|
|
- break;
|
|
- }
|
|
- else
|
|
- {
|
|
- if (This->m_convertSignedUnsigned)
|
|
- This->transformSignedUnsigned(abl);
|
|
-
|
|
- // std::cout << "AudioHardwareStreamPAOutput::paStreamWriteCB(): got " << abl->mBuffers[0].mDataByteSize << " bytes\n";
|
|
- int rv = pa_stream_write(This->m_stream, abl->mBuffers[0].mData, abl->mBuffers[0].mDataByteSize, nullptr, 0, PA_SEEK_RELATIVE);
|
|
- if (rv != 0)
|
|
- {
|
|
- // std::cerr << "pa_stream_write() failed\n";
|
|
- break;
|
|
- }
|
|
- //else
|
|
- // std::cout << "pa_stream_write() OK\n";
|
|
- }
|
|
+void AudioHardwareStreamPAOutput::prefill()
|
|
+{
|
|
+ if (!m_running || m_corked || m_primed || !m_stream || pa_stream_get_state(m_stream) != PA_STREAM_READY)
|
|
+ return;
|
|
+
|
|
+ // The only burst: fill the target buffer once at start.
|
|
+ unsigned blocks = 0;
|
|
+ RenderResult r = RenderResult::Written;
|
|
+ while (blocks < kPrefillBlocks && (r = renderBlock(iopShouldTrace())) == RenderResult::Written)
|
|
+ ++blocks;
|
|
+ if (rclolAudioTraceEnabled())
|
|
+ rclolAudioTrace("PA_OUT prefill blocks=%u result=%d periodMs=%.3f blockFrames=%u\n", blocks, (int) r,
|
|
+ m_periodNs / 1e6, m_blockFrames);
|
|
+ if (r == RenderResult::Stopped || !blocks)
|
|
+ return;
|
|
+
|
|
+ m_primed = true;
|
|
+ m_anchorNs = nowNs();
|
|
+ m_tickIndex = 1;
|
|
+ m_advanceNs = 0;
|
|
+ armTimer(m_anchorNs);
|
|
+}
|
|
+
|
|
+void AudioHardwareStreamPAOutput::tick()
|
|
+{
|
|
+ if (!m_running || m_corked || !m_timer)
|
|
+ return;
|
|
+ if (!m_primed)
|
|
+ {
|
|
+ prefill();
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ uint64_t now = nowNs();
|
|
+ uint64_t deadline = deadlineNs();
|
|
+ if (now < deadline)
|
|
+ {
|
|
+ // Early wakeup (leeway); wait for the real deadline.
|
|
+ armTimer(now);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ const bool trace = tickShouldTrace();
|
|
+ const size_t writable = pa_stream_writable_size(m_stream);
|
|
+ if (writable != size_t(-1))
|
|
+ m_windowMinWritable = std::min(m_windowMinWritable, writable);
|
|
+
|
|
+ const uint64_t behind = uint64_t(double(now - deadline) / m_periodNs);
|
|
+ unsigned toRender = 1;
|
|
+ if (behind > kMaxCatchUpBlocks)
|
|
+ {
|
|
+ // Far behind (stalled queue, suspended process): don't burst, restart the clock.
|
|
+ if (paceShouldTrace())
|
|
+ rclolAudioTrace("PA_OUT reanchor lateMs=%.3f behindBlocks=%llu\n", double(now - deadline) / 1e6,
|
|
+ (unsigned long long) behind);
|
|
+ m_anchorNs = now;
|
|
+ m_tickIndex = 0;
|
|
+ m_advanceNs = 0;
|
|
+ }
|
|
+ else if (behind)
|
|
+ {
|
|
+ toRender += unsigned(behind);
|
|
+ if (paceShouldTrace())
|
|
+ rclolAudioTrace("PA_OUT catchup lateMs=%.3f blocks=%u\n", double(now - deadline) / 1e6, toRender);
|
|
+ }
|
|
+
|
|
+ if (trace)
|
|
+ rclolAudioTrace("PA_OUT tick lateMs=%.3f writable=%zu render=%u\n", double(now - deadline) / 1e6, writable, toRender);
|
|
+
|
|
+ for (unsigned i = 0; i < toRender; i++)
|
|
+ {
|
|
+ RenderResult r = renderBlock(iopShouldTrace());
|
|
+ if (r == RenderResult::Stopped)
|
|
+ return; // also covers stop() from inside the IOProc (timer already cancelled)
|
|
+ if (r == RenderResult::NoSpace && paceShouldTrace())
|
|
+ rclolAudioTrace("PA_OUT skip: writable=%zu < block=%zu\n", pa_stream_writable_size(m_stream), m_blockBytes);
|
|
+ // The IO cycle elapses either way; a skipped cycle means PA is consuming slower than us.
|
|
+ ++m_tickIndex;
|
|
+ }
|
|
|
|
- done += abl->mBuffers[0].mDataByteSize;
|
|
+ // Drift servo: the PA sink clock isn't our clock. If the buffer stayed under-filled for a
|
|
+ // whole window (min writable >= 2 blocks), run ~6% faster for the next window. Each tick
|
|
+ // stays a single, evenly spaced IOProc call.
|
|
+ if (++m_windowTicks >= kServoWindowTicks)
|
|
+ {
|
|
+ const bool low = m_windowMinWritable != SIZE_MAX && m_windowMinWritable >= 2 * m_blockBytes;
|
|
+ const double step = low ? m_periodNs / 16 : 0;
|
|
+ if (step != m_servoStepNs && paceShouldTrace())
|
|
+ rclolAudioTrace("PA_OUT servo minWritable=%zu step=%s\n", m_windowMinWritable, low ? "fast" : "off");
|
|
+ m_servoStepNs = step;
|
|
+ m_windowMinWritable = SIZE_MAX;
|
|
+ m_windowTicks = 0;
|
|
}
|
|
+ m_advanceNs += m_servoStepNs;
|
|
+
|
|
+ armTimer(nowNs());
|
|
+}
|
|
+
|
|
+void AudioHardwareStreamPAOutput::paStreamWriteCB(pa_stream* s, size_t length, void* self)
|
|
+{
|
|
+ AudioHardwareStreamPAOutput* This = static_cast<AudioHardwareStreamPAOutput*>(self);
|
|
+
|
|
+ if (!This->m_running)
|
|
+ {
|
|
+ if (wcbShouldTrace()) rclolAudioTrace("PA_WRITE_CB length=%zu not-running: corking\n", length);
|
|
+ pa_stream_cork(This->m_stream, true, [](pa_stream*, int, void*) {}, nullptr);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Rendering is paced by the IO timer; the write request only triggers the initial prefill.
|
|
+ if (wcbShouldTrace())
|
|
+ rclolAudioTrace("PA_WRITE_CB length=%zu writable=%zu primed=%d corked=%d\n", length,
|
|
+ pa_stream_writable_size(s), This->m_primed, This->m_corked);
|
|
+ if (!This->m_timer)
|
|
+ {
|
|
+ // No pacing timer could be created: fall back to rendering whole blocks on demand.
|
|
+ while (This->renderBlock(iopShouldTrace()) == RenderResult::Written) {}
|
|
+ return;
|
|
+ }
|
|
+ if (!This->m_primed)
|
|
+ This->prefill();
|
|
}
|
|
|
|
void AudioHardwareStreamPAOutput::start()
|
|
@@ -93,10 +340,44 @@ void AudioHardwareStreamPAOutput::start()
|
|
struct pa_buffer_attr battr;
|
|
|
|
battr.fragsize = uint32_t(-1);
|
|
- battr.maxlength = uint32_t(m_bufferSize);
|
|
- battr.minreq = uint32_t(-1);
|
|
- battr.prebuf = battr.maxlength;
|
|
- battr.tlength = uint32_t(-1);
|
|
+ // Size the server buffer in whole IOProc blocks so every IO cycle can supply one.
|
|
+ uint32_t bpf = m_hw->asbd().mBytesPerFrame;
|
|
+ uint32_t block = bpf ? (m_hw->bufferSize() / bpf) * bpf : m_bufferSize;
|
|
+ if (!block)
|
|
+ block = m_bufferSize;
|
|
+ // ~46 ms target at 256 frames/44.1 kHz: a 2-block target underran, since each
|
|
+ // block waits on a cross-process round trip to the client's renderer.
|
|
+ battr.maxlength = block * 16;
|
|
+ battr.minreq = block;
|
|
+ battr.prebuf = block * 2;
|
|
+ battr.tlength = block * kPrefillBlocks;
|
|
+
|
|
+ // Real HALs always call the IOProc with the same frame count (kAudioDevicePropertyBufferFrameSize).
|
|
+ m_blockFrames = bpf ? m_hw->bufferSize() / bpf : 0;
|
|
+ m_blockBytes = size_t(m_blockFrames) * bpf;
|
|
+ const double rate = m_hw->asbd().mSampleRate;
|
|
+ m_periodNs = rate > 0 ? double(m_blockFrames) * 1e9 / rate : 0;
|
|
+
|
|
+ // start() runs on the PA queue, so the timer shares its serialization with all pa_* calls.
|
|
+ // Disabled: Darling's dispatch timers fire 3-35 ms late (one stalled 30 s), which starved
|
|
+ // PA into silence. Render on PA's write requests instead; the output timestamps still
|
|
+ // come from PA's latency, so blocks within one request get correctly spaced play times.
|
|
+ static const bool kUsePacingTimer = false;
|
|
+ dispatch_queue_t queue = static_cast<AudioHardwareImplPA*>(m_hw)->paQueue();
|
|
+ if (kUsePacingTimer && queue && m_blockBytes && m_periodNs > 0)
|
|
+ {
|
|
+ m_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
|
+ if (m_timer)
|
|
+ {
|
|
+ dispatch_source_set_event_handler(m_timer, ^{ tick(); });
|
|
+ // Resumed immediately (a suspended source can't be released); armed after prefill.
|
|
+ dispatch_source_set_timer(m_timer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
|
|
+ dispatch_resume(m_timer);
|
|
+ }
|
|
+ }
|
|
+ if (rclolAudioTraceEnabled())
|
|
+ rclolAudioTrace("PA_OUT start block=%zu frames=%u periodMs=%.3f timer=%d\n", m_blockBytes, m_blockFrames,
|
|
+ m_periodNs / 1e6, m_timer != nullptr);
|
|
|
|
pa_stream_set_write_callback(m_stream, paStreamWriteCB, this);
|
|
|
|
@@ -105,7 +386,5 @@ void AudioHardwareStreamPAOutput::start()
|
|
|PA_STREAM_AUTO_TIMING_UPDATE) /* PA_STREAM_START_CORKED */,
|
|
nullptr, nullptr);
|
|
|
|
- // 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..8d73f9902 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.h
|
|
@@ -20,15 +20,46 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#ifndef AUDIOHARDWARESTREAMPAOUTPUT_H
|
|
#define AUDIOHARDWARESTREAMPAOUTPUT_H
|
|
#include "AudioHardwareStreamPA.h"
|
|
+#include <dispatch/dispatch.h>
|
|
+#include <cstdint>
|
|
|
|
class AudioHardwareStreamPAOutput : public AudioHardwareStreamPA
|
|
{
|
|
public:
|
|
AudioHardwareStreamPAOutput(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData);
|
|
+ ~AudioHardwareStreamPAOutput();
|
|
+ void stop() override;
|
|
private:
|
|
+ enum class RenderResult { Written, NoSpace, Stopped };
|
|
+
|
|
+ uint64_t m_frames = 0;
|
|
void start() override;
|
|
static void paStreamWriteCB(pa_stream* s, size_t length, void* self);
|
|
+
|
|
+ // IO cycle pacing; everything below is only touched on the PA queue.
|
|
+ void cancelTimer();
|
|
+ void prefill();
|
|
+ void tick();
|
|
+ void armTimer(uint64_t nowNs);
|
|
+ uint64_t deadlineNs() const;
|
|
+ RenderResult renderBlock(bool trace);
|
|
+ uint64_t outputLatencyNs(size_t writable, bool* fromPA);
|
|
+
|
|
+ dispatch_source_t m_timer = nullptr;
|
|
+ size_t m_blockBytes = 0;
|
|
+ uint32_t m_blockFrames = 0;
|
|
+ double m_periodNs = 0;
|
|
+ bool m_primed = false;
|
|
+ bool m_corked = false;
|
|
+ // Deadline k is m_anchorNs + k * m_periodNs - m_advanceNs.
|
|
+ uint64_t m_anchorNs = 0;
|
|
+ uint64_t m_tickIndex = 0;
|
|
+ double m_advanceNs = 0;
|
|
+ // Drift servo: smallest writable size seen over the current window of ticks.
|
|
+ size_t m_windowMinWritable = SIZE_MAX;
|
|
+ unsigned m_windowTicks = 0;
|
|
+ double m_servoStepNs = 0;
|
|
+ uint64_t m_lastIOProcHost = 0;
|
|
};
|
|
|
|
#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..1e86f9e99 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/PADispatchMainLoop.h
|
|
@@ -30,8 +30,12 @@ public:
|
|
|
|
void suspend();
|
|
void resume();
|
|
+ void sync(void (^block)(void));
|
|
+ void async(void (^block)(void));
|
|
|
|
pa_mainloop_api* getAPI();
|
|
+ // The serial queue all pa_* calls and callbacks run on.
|
|
+ dispatch_queue_t queue() const { return m_queue; }
|
|
private:
|
|
static pa_io_event* io_new(pa_mainloop_api *a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata);
|
|
static void io_enable(pa_io_event *e, pa_io_event_flags_t events);
|
|
@@ -50,6 +54,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..769b06586 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,15 @@ 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>
|
|
+#include <atomic>
|
|
+#include <cmath>
|
|
+#include <algorithm>
|
|
+#include <mach/mach_time.h>
|
|
|
|
#pragma GCC visibility push(default)
|
|
AUDIOCOMPONENT_ENTRY(AUOutputBaseFactory, AUHAL);
|
|
@@ -29,8 +39,11 @@ enum {
|
|
kInputBus
|
|
};
|
|
|
|
+// Like macOS: element 0 is the output (app -> device) side, element 1 the input
|
|
+// (device -> app) side, on both scopes. Without output element 1 the app's
|
|
+// recording format and AudioUnitRender(bus 1) had nowhere to go.
|
|
AUHAL::AUHAL(AudioComponentInstance inInstance, bool supportRecording)
|
|
-: AUBase(inInstance, 1, supportRecording ? 1 : 0)
|
|
+: AUBase(inInstance, supportRecording ? 2 : 1, supportRecording ? 2 : 1)
|
|
{
|
|
UInt32 propSize = sizeof(AudioDeviceID);
|
|
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propSize, &m_outputDevice);
|
|
@@ -42,6 +55,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 +93,36 @@ bool AUHAL::CanScheduleParameters() const
|
|
|
|
bool AUHAL::StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element)
|
|
{
|
|
- return !m_running && IsInitialized();
|
|
+ return !m_running;
|
|
+}
|
|
+
|
|
+// AUBase only accepts the canonical layout (Float32, non-interleaved). A real AUHAL
|
|
+// converts any linear PCM, and Vivox asks for interleaved Float32 on both client sides
|
|
+// (recording: output scope element 1; playback: input scope element 0, at its own rate).
|
|
+// Those accept what PulseAudio converts and our copies handle: native-endian float32 or
|
|
+// signed 16/32-bit integers, either layout. Missing frames are padded with zeros, which is
|
|
+// only silence for float and signed samples, so unsigned (and 8-bit) formats are left out;
|
|
+// so is 24-bit (PulseAudio may use 4-byte samples while we copy 3). Device sides keep the
|
|
+// canonical rule.
|
|
+bool AUHAL::ValidFormat(AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& format)
|
|
+{
|
|
+ const bool clientSide = (scope == kAudioUnitScope_Output && element == kInputBus)
|
|
+ || (scope == kAudioUnitScope_Input && element == kOutputBus);
|
|
+ if (!clientSide)
|
|
+ return AUBase::ValidFormat(scope, element, format);
|
|
+
|
|
+ const UInt32 flags = format.mFormatFlags;
|
|
+ const UInt32 bits = format.mBitsPerChannel;
|
|
+ const bool isFloat = flags & kAudioFormatFlagIsFloat;
|
|
+ if (format.mFormatID != kAudioFormatLinearPCM || format.mFramesPerPacket != 1
|
|
+ || format.mSampleRate <= 0 || format.mChannelsPerFrame < 1 || format.mChannelsPerFrame > 32
|
|
+ || (flags & kAudioFormatFlagIsBigEndian) != (kAudioFormatFlagsNativeEndian & kAudioFormatFlagIsBigEndian))
|
|
+ return false;
|
|
+ if (isFloat ? bits != 32 : (!(flags & kAudioFormatFlagIsSignedInteger) || (bits != 16 && bits != 32)))
|
|
+ return false;
|
|
+
|
|
+ const UInt32 frameBytes = (flags & kAudioFormatFlagIsNonInterleaved) ? bits / 8 : format.mChannelsPerFrame * bits / 8;
|
|
+ return format.mBytesPerFrame == frameBytes && format.mBytesPerPacket == frameBytes;
|
|
}
|
|
|
|
OSStatus AUHAL::Version()
|
|
@@ -57,45 +130,129 @@ OSStatus AUHAL::Version()
|
|
return 1;
|
|
}
|
|
|
|
-// Provide data from the microphone
|
|
-OSStatus AUHAL::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames)
|
|
+// Provide data from the microphone: AudioUnitRender(bus 1), normally called from the input callback.
|
|
+// Never blocks; frames not recorded yet are silence. Bypasses NeedsToRender(), as a real AUHAL
|
|
+// renders whatever timestamp the client passes.
|
|
+OSStatus AUHAL::RenderBus(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames)
|
|
{
|
|
- std::unique_lock<std::mutex> lk(m_dataAvailableMutex);
|
|
+ if (inBusNumber != kInputBus || !m_enableInput)
|
|
+ return kAudioUnitErr_NoConnection;
|
|
|
|
- m_dataAvailableCV.wait(lk, [=]{ return m_dataAvailable; });
|
|
+ const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
|
|
+ const UInt32 sampleBytes = desc.mBitsPerChannel / 8;
|
|
+ const UInt32 frameBytes = desc.mChannelsPerFrame * sampleBytes;
|
|
+ if (!sampleBytes || !frameBytes)
|
|
+ return kAudioUnitErr_FormatNotSupported;
|
|
+ const bool planar = desc.mFormatFlags & kAudioFormatFlagIsNonInterleaved;
|
|
|
|
AudioBufferList& abl = GetOutput(kInputBus)->GetBufferList();
|
|
+ std::lock_guard<std::mutex> lk(m_dataAvailableMutex);
|
|
+ const UInt32 frames = std::min<UInt32>(inNumberFrames, m_bufferUsed / frameBytes);
|
|
+ const uint8_t* source = m_buffer.get();
|
|
+
|
|
+ if (planar)
|
|
+ {
|
|
+ for (UInt32 c = 0; c < abl.mNumberBuffers && c < desc.mChannelsPerFrame; c++)
|
|
+ {
|
|
+ uint8_t* destination = static_cast<uint8_t*>(abl.mBuffers[c].mData);
|
|
+ for (UInt32 f = 0; f < frames; f++)
|
|
+ memcpy(destination + f * sampleBytes, source + f * frameBytes + c * sampleBytes, sampleBytes);
|
|
+ memset(destination + frames * sampleBytes, 0, (inNumberFrames - frames) * sampleBytes);
|
|
+ abl.mBuffers[c].mDataByteSize = inNumberFrames * sampleBytes;
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ uint8_t* destination = static_cast<uint8_t*>(abl.mBuffers[0].mData);
|
|
+ memcpy(destination, source, frames * frameBytes);
|
|
+ memset(destination + frames * frameBytes, 0, (inNumberFrames - frames) * frameBytes);
|
|
+ abl.mBuffers[0].mDataByteSize = inNumberFrames * frameBytes;
|
|
+ }
|
|
|
|
- // TODO: Prepare for non-interleaved audio
|
|
- UInt32 howMuch = std::min<UInt32>(abl.mBuffers[0].mDataByteSize, m_bufferUsed);
|
|
- memcpy(abl.mBuffers[0].mData, m_buffer.get(), howMuch);
|
|
- abl.mBuffers[0].mDataByteSize = howMuch;
|
|
+ // Level only (no samples): shows whether the client pulls real sound or silence.
|
|
+ static std::atomic<unsigned> renders{0};
|
|
+ const unsigned n = renders++;
|
|
+ if (rclolAudioTraceEnabled() && (n < 40 || n % 1000 == 0))
|
|
+ {
|
|
+ float peak = 0;
|
|
+ const UInt32 count = frames * desc.mChannelsPerFrame;
|
|
+ if (desc.IsFloat() && sampleBytes == 4)
|
|
+ for (UInt32 i = 0; i < count; i++)
|
|
+ peak = std::max(peak, std::abs(reinterpret_cast<const float*>(source)[i]));
|
|
+ else if (sampleBytes == 2)
|
|
+ for (UInt32 i = 0; i < count; i++)
|
|
+ peak = std::max(peak, std::abs(reinterpret_cast<const int16_t*>(source)[i] / 32768.0f));
|
|
+ rclolAudioTrace("AUDIO_INPUT_RENDER n=%u frames=%u available=%u planar=%d peak=%.4f\n",
|
|
+ n, inNumberFrames, frames, (int) planar, peak);
|
|
+ }
|
|
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus AUHAL::Start()
|
|
{
|
|
+ if(rclolAudioTraceEnabled()) 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)
|
|
{
|
|
- AudioDeviceCreateIOProcID(m_inputDevice, recordCallback, this, &m_inputProcID);
|
|
- const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
|
|
- AudioDeviceSetProperty(m_inputDevice, nullptr, 0, true, kAudioDevicePropertyStreamFormat, sizeof(AudioStreamBasicDescription), &desc);
|
|
- AudioDeviceStart(m_inputDevice, m_inputProcID);
|
|
+ // The device records directly in the client's bus 1 format (PulseAudio converts rate,
|
|
+ // channels and sample type); only planar layouts are converted, in RenderBus().
|
|
+ OSStatus status = AudioDeviceCreateIOProcID(m_inputDevice, recordCallback, this, &m_inputProcID);
|
|
+ if (!status)
|
|
+ {
|
|
+ const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
|
|
+ AudioStreamBasicDescription hardware = desc;
|
|
+ hardware.mFormatFlags &= ~kAudioFormatFlagIsNonInterleaved;
|
|
+ hardware.mBytesPerFrame = hardware.mChannelsPerFrame * hardware.mBitsPerChannel / 8;
|
|
+ hardware.mBytesPerPacket = hardware.mBytesPerFrame;
|
|
+ // The device keeps its buffer size in bytes; keep the frame count across the format change.
|
|
+ AudioObjectPropertyAddress p = {kAudioDevicePropertyBufferFrameSize, kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMaster};
|
|
+ UInt32 frames = 0, size = sizeof(frames);
|
|
+ const bool haveFrames = AudioObjectGetPropertyData(m_inputDevice, &p, 0, nullptr, &size, &frames) == noErr;
|
|
+ status = AudioDeviceSetProperty(m_inputDevice, nullptr, 0, true, kAudioDevicePropertyStreamFormat, sizeof(hardware), &hardware);
|
|
+ if (!status && haveFrames)
|
|
+ AudioObjectSetPropertyData(m_inputDevice, &p, 0, nullptr, sizeof(frames), &frames);
|
|
+ {
|
|
+ std::lock_guard<std::mutex> lk(m_dataAvailableMutex);
|
|
+ m_bufferUsed = 0;
|
|
+ }
|
|
+ if (!status)
|
|
+ status = AudioDeviceStart(m_inputDevice, m_inputProcID);
|
|
+ if (status)
|
|
+ {
|
|
+ AudioDeviceDestroyIOProcID(m_inputDevice, m_inputProcID);
|
|
+ m_inputProcID = 0;
|
|
+ }
|
|
+ }
|
|
+ if (status)
|
|
+ {
|
|
+ if (m_outputProcID)
|
|
+ {
|
|
+ AudioDeviceStop(m_outputDevice, m_outputProcID);
|
|
+ AudioDeviceDestroyIOProcID(m_outputDevice, m_outputProcID);
|
|
+ m_outputProcID = 0;
|
|
+ }
|
|
+ return status;
|
|
+ }
|
|
}
|
|
|
|
m_running = m_enableOutput || m_enableInput;
|
|
@@ -111,20 +268,49 @@ 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;
|
|
}
|
|
|
|
+// Volume/mute/gain related property selectors (HAL device properties and AUHAL parameters).
|
|
+static bool rclolVolumeRelated(UInt32 id)
|
|
+{
|
|
+ switch (id)
|
|
+ {
|
|
+ case 'volm': case 'vold': case 'vdb#': case 'mute': case 'thru':
|
|
+ case 'pvol': case 'pvod': case 'mvol': case 'mbal': case 'span':
|
|
+ case 'svlm': case 'smut': case 'v2db': case 'db2v':
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+
|
|
OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize)
|
|
{
|
|
+ if (rclolAudioTraceEnabled() && rclolVolumeRelated(inID))
|
|
+ rclolAudioTrace("AUHAL_SET_VOLPROP property=%08x scope=%u element=%u size=%u value=%g\n",inID,inScope,inElement,inDataSize,
|
|
+ (inData && inDataSize==sizeof(Float32))?(double)*(const Float32*)inData:0.0);
|
|
switch (inID)
|
|
{
|
|
+ case kAudioDevicePropertyBufferFrameSize: {
|
|
+ // An input-only unit (EnableIO output=0) sizes its input device's IO cycle.
|
|
+ AudioObjectPropertyAddress p={inID,kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
|
|
+ OSStatus status=noErr;
|
|
+ if(m_enableOutput || !m_enableInput) status=AudioObjectSetPropertyData(m_outputDevice,&p,0,nullptr,inDataSize,inData);
|
|
+ if(!status && m_enableInput) {
|
|
+ p.mScope=kAudioObjectPropertyScopeInput;
|
|
+ status=AudioObjectSetPropertyData(m_inputDevice,&p,0,nullptr,inDataSize,inData);
|
|
+ }
|
|
+ return status;
|
|
+ }
|
|
case kAudioOutputUnitProperty_SetInputCallback:
|
|
{
|
|
ca_require(inDataSize == sizeof(AURenderCallbackStruct), InvalidPropertyValue);
|
|
@@ -161,7 +347,14 @@ OSStatus AUHAL::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, Au
|
|
ca_require(inDataSize == sizeof(AudioDeviceID), InvalidPropertyValue);
|
|
const AudioDeviceID* dev = static_cast<const AudioDeviceID*>(inData);
|
|
|
|
- if (inElement == kOutputBus)
|
|
+ // macOS AUHAL has one current device, normally set on element 0; for an
|
|
+ // input-only unit (EnableIO input=1, output=0) that is the capture device.
|
|
+ if (inElement == kOutputBus && m_enableInput && !m_enableOutput)
|
|
+ {
|
|
+ m_inputDevice = *dev;
|
|
+ PropertyChanged(inID, inScope, inElement);
|
|
+ }
|
|
+ else if (inElement == kOutputBus)
|
|
{
|
|
m_outputDevice = *dev;
|
|
PropertyChanged(inID, inScope, inElement);
|
|
@@ -176,20 +369,28 @@ 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 && rclolAudioTraceEnabled()) rclolAudioTrace("AUDIO_UNIT_SET property=%u scope=%u element=%u status=%d\n",inID,inScope,inElement,status);
|
|
+ return status;
|
|
+ }
|
|
InvalidPropertyValue:
|
|
return kAudioUnitErr_InvalidPropertyValue;
|
|
}
|
|
|
|
OSStatus AUHAL::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable)
|
|
{
|
|
+ if (rclolAudioTraceEnabled() && rclolVolumeRelated(inID))
|
|
+ rclolAudioTrace("AUHAL_INFO_VOLPROP property=%08x scope=%u element=%u\n",inID,inScope,inElement);
|
|
switch (inID)
|
|
{
|
|
+ case kAudioDevicePropertyBufferFrameSize:
|
|
+ outDataSize=sizeof(UInt32);outWritable=true;return noErr;
|
|
case kAudioOutputUnitProperty_SetInputCallback:
|
|
{
|
|
outDataSize = sizeof(AURenderCallbackStruct);
|
|
outWritable = true;
|
|
- break;
|
|
+ return noErr;
|
|
}
|
|
case kAudioOutputUnitProperty_EnableIO:
|
|
{
|
|
@@ -210,13 +411,23 @@ 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 && rclolAudioTraceEnabled()) 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)
|
|
{
|
|
+ if (rclolAudioTraceEnabled() && rclolVolumeRelated(inID))
|
|
+ rclolAudioTrace("AUHAL_GET_VOLPROP property=%08x scope=%u element=%u\n",inID,inScope,inElement);
|
|
switch (inID)
|
|
{
|
|
+ case kAudioDevicePropertyBufferFrameSize: {
|
|
+ const bool input=m_enableInput && !m_enableOutput;
|
|
+ AudioObjectPropertyAddress p={inID,input?kAudioObjectPropertyScopeInput:kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
|
|
+ UInt32 size=sizeof(UInt32);
|
|
+ return AudioObjectGetPropertyData(input?m_inputDevice:m_outputDevice,&p,0,nullptr,&size,outData);
|
|
+ }
|
|
case kAudioOutputUnitProperty_SetInputCallback:
|
|
{
|
|
memcpy(outData, &m_outputAvailableCb, sizeof(m_outputAvailableCb));
|
|
@@ -228,12 +439,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;
|
|
@@ -241,7 +452,11 @@ OSStatus AUHAL::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, Au
|
|
}
|
|
case kAudioOutputUnitProperty_CurrentDevice:
|
|
{
|
|
- if (inElement == kOutputBus)
|
|
+ if (inElement == kOutputBus && m_enableInput && !m_enableOutput)
|
|
+ {
|
|
+ memcpy(outData, &m_inputDevice, sizeof(m_inputDevice));
|
|
+ }
|
|
+ else if (inElement == kOutputBus)
|
|
{
|
|
memcpy(outData, &m_outputDevice, sizeof(m_outputDevice));
|
|
}
|
|
@@ -277,9 +492,89 @@ OSStatus AUHAL::recordCallback(AudioObjectID inObjectID,
|
|
return This->doRecord(inNow, inInputData, inInputTime);
|
|
}
|
|
|
|
+// Diagnostic only: classify each rendered block (see AUDIO_BLOCK_STATE).
|
|
+void AUHAL::traceBlock(const AudioTimeStamp* inNow, const AudioTimeStamp* inOutputTime, OSStatus result,
|
|
+ const CAStreamBasicDescription& desc, UInt32 nFrames, AudioBufferList* out)
|
|
+{
|
|
+ static std::atomic<unsigned> stateLines(0);
|
|
+ static mach_timebase_info_data_t tb={0,0};
|
|
+ if(!tb.denom) mach_timebase_info(&tb);
|
|
+ BlockTrace& t=m_blockTrace;
|
|
+ const unsigned long long cb=++t.callbacks;
|
|
+ if(result) {
|
|
+ if(++t.pullFailures<=50 || t.pullFailures%200==0)
|
|
+ rclolAudioTrace("AUDIO_PULL_FAIL unit=%p cb=%llu status=%d frames=%u count=%llu\n",
|
|
+ (void*)this,cb,(int)result,nFrames,t.pullFailures);
|
|
+ return;
|
|
+ }
|
|
+ if(!desc.IsFloat() || desc.mBitsPerChannel!=32 || !nFrames || !desc.mChannelsPerFrame) return;
|
|
+ const bool planar=desc.mFormatFlags&kAudioFormatFlagIsNonInterleaved;
|
|
+ const UInt32 ch=desc.mChannelsPerFrame;
|
|
+ const float* base=planar?(const float*)m_playbackSamples.data():(const float*)out->mBuffers[0].mData;
|
|
+ if(!base) return;
|
|
+ auto at=[&](UInt32 c,UInt32 f)->float { return planar?base[(size_t)c*nFrames+f]:base[(size_t)f*ch+c]; };
|
|
+ float peak[2]={0,0}; double sq[2]={0,0}; bool halves[2]={true,true}; bool zero=true;
|
|
+ const UInt32 half=nFrames/2;
|
|
+ for(UInt32 c=0;c<ch && c<2;++c) {
|
|
+ for(UInt32 f=0;f<nFrames;++f) {
|
|
+ float v=at(c,f);
|
|
+ if(v!=0 || std::signbit(v)) { if(v!=0) zero=false; }
|
|
+ peak[c]=std::max(peak[c],std::abs(v)); sq[c]+=(double)v*v;
|
|
+ if(f<half && (nFrames&1)==0) {
|
|
+ float w=at(c,f+half);
|
|
+ if(memcmp(&v,&w,4)) halves[c]=false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ const bool halvesSame=(nFrames&1)==0 && halves[0] && (ch<2 || halves[1]);
|
|
+ const double rms0=sqrt(sq[0]/nFrames), rms1=ch>1?sqrt(sq[1]/nFrames):0;
|
|
+ bool same=false;
|
|
+ const size_t total=(size_t)nFrames*ch;
|
|
+ if(t.havePrev && t.prev.size()==total) {
|
|
+ same=true;
|
|
+ for(UInt32 c=0;c<ch&&same;++c) for(UInt32 f=0;f<nFrames;++f) {
|
|
+ float v=at(c,f);
|
|
+ if(memcmp(&v,&t.prev[(size_t)c*nFrames+f],4)) { same=false; break; }
|
|
+ }
|
|
+ }
|
|
+ // 0 silent, 1 live, 2 repeat-of-previous-nonsilent, 3 halves-identical-nonsilent
|
|
+ int state=zero?0:same?2:halvesSame?3:1;
|
|
+ t.prev.resize(total);
|
|
+ for(UInt32 c=0;c<ch;++c) for(UInt32 f=0;f<nFrames;++f) t.prev[(size_t)c*nFrames+f]=at(c,f);
|
|
+ t.havePrev=true;
|
|
+ ++t.stateCounts[state];
|
|
+ static const char* names[4]={"silent","live","repeat","halves"};
|
|
+ double dSample=0, dHostMs=0;
|
|
+ const double sample=inOutputTime->mSampleTime;
|
|
+ if(t.haveTime) {
|
|
+ dSample=sample-t.lastSample;
|
|
+ dHostMs=(double)(inOutputTime->mHostTime-t.lastHost)*tb.numer/tb.denom/1e6;
|
|
+ if(dHostMs>t.maxGapMs) t.maxGapMs=dHostMs;
|
|
+ }
|
|
+ t.haveTime=true; t.lastSample=sample; t.lastHost=inOutputTime->mHostTime;
|
|
+ const bool transition=state!=t.state;
|
|
+ if(transition && t.state>=0 && stateLines.fetch_add(1)<2000)
|
|
+ rclolAudioTrace("AUDIO_BLOCK_STATE unit=%p from=%s to=%s cb=%llu frames=%u peakL=%g peakR=%g rmsL=%g rmsR=%g\n",
|
|
+ (void*)this,names[t.state],names[state],cb,nFrames,peak[0],peak[1],rms0,rms1);
|
|
+ if(cb<=40 || cb%200==0 || transition)
|
|
+ rclolAudioTrace("AUDIO_BLOCK unit=%p cb=%llu state=%s frames=%u peakL=%g peakR=%g rmsL=%g rmsR=%g "
|
|
+ "halvesL=%d halvesR=%d same=%d zero=%d outSample=%.0f nowSample=%.0f dSample=%.0f outHost=%llu nowHost=%llu "
|
|
+ "dHostMs=%.3f maxGapMs=%.3f\n",
|
|
+ (void*)this,cb,names[state],nFrames,peak[0],peak[1],rms0,rms1,(int)halves[0],(int)halves[1],(int)same,(int)zero,
|
|
+ sample,inNow->mSampleTime,dSample,(unsigned long long)inOutputTime->mHostTime,
|
|
+ (unsigned long long)inNow->mHostTime,dHostMs,t.maxGapMs);
|
|
+ if(cb%2000==0)
|
|
+ rclolAudioTrace("AUDIO_BLOCK_SUMMARY unit=%p cb=%llu silent=%llu live=%llu repeat=%llu halves=%llu pullFail=%llu maxGapMs=%.3f\n",
|
|
+ (void*)this,cb,t.stateCounts[0],t.stateCounts[1],t.stateCounts[2],t.stateCounts[3],t.pullFailures,t.maxGapMs);
|
|
+ t.state=state;
|
|
+}
|
|
+
|
|
OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime)
|
|
{
|
|
// std::cout << "AUHAL::DoPlayback()\n";
|
|
+ static unsigned stackTraces=0;
|
|
+ if(rclolAudioTraceEnabled() && 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 +585,75 @@ 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);
|
|
+ }
|
|
+
|
|
+ if(rclolAudioTraceEnabled()) traceBlock(inNow,inOutputTime,result,desc,nFrames,outOutputData);
|
|
+
|
|
+ // Diagnostic: Chromium's audio source can start handing out one identical
|
|
+ // block forever (heard as static). Log the timestamp Darling supplied at the
|
|
+ // moment that begins, so we can tell whether our clock inputs look sane.
|
|
+ {
|
|
+ const unsigned char* produced;
|
|
+ size_t producedBytes;
|
|
+ if(desc.mFormatFlags&kAudioFormatFlagIsNonInterleaved) {
|
|
+ produced=m_playbackSamples.data();
|
|
+ producedBytes=(size_t)nFrames*frameBytes;
|
|
+ } else {
|
|
+ produced=(const unsigned char*)outOutputData->mBuffers[0].mData;
|
|
+ producedBytes=outOutputData->mBuffers[0].mDataByteSize;
|
|
+ }
|
|
+ uint64_t sum=1469598103934665603ull;
|
|
+ for(size_t i=0;i<producedBytes;++i) sum=(sum^produced[i])*1099511628211ull;
|
|
+ static uint64_t lastSum=0;
|
|
+ static unsigned repeatRun=0;
|
|
+ if(producedBytes && sum==lastSum) {
|
|
+ ++repeatRun;
|
|
+ if(rclolAudioTraceEnabled() && (repeatRun==2 || repeatRun%500==0))
|
|
+ rclolAudioTrace("AUDIO_INPUT_REPEAT frames=%u run=%u host=%llu sample=%.0f flags=%u\n",
|
|
+ nFrames,repeatRun,(unsigned long long)inOutputTime->mHostTime,inOutputTime->mSampleTime,
|
|
+ (unsigned)inOutputTime->mFlags);
|
|
+ } else {
|
|
+ repeatRun=0;
|
|
+ }
|
|
+ lastSum=sum;
|
|
+ }
|
|
+
|
|
+ static unsigned traceRenders=0;
|
|
+ if (rclolAudioTraceEnabled() && 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);
|
|
@@ -303,30 +664,46 @@ OSStatus AUHAL::doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutp
|
|
|
|
OSStatus AUHAL::doRecord(const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime)
|
|
{
|
|
- // TODO: Prepare for non-interleaved audio
|
|
- std::unique_lock<std::mutex> lk(m_dataAvailableMutex);
|
|
-
|
|
- if (m_bufferSize < inInputData->mBuffers[0].mDataByteSize)
|
|
- {
|
|
- m_buffer.reset(new uint8_t[inInputData->mBuffers[0].mDataByteSize]);
|
|
- m_bufferSize = inInputData->mBuffers[0].mDataByteSize;
|
|
- }
|
|
-
|
|
- m_bufferUsed = inInputData->mBuffers[0].mDataByteSize;
|
|
- memcpy(m_buffer.get(), inInputData->mBuffers[0].mData, m_bufferUsed);
|
|
-
|
|
- m_dataAvailable = true;
|
|
+ // The device records interleaved data in the bus 1 output format (see Start()).
|
|
+ const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
|
|
+ const UInt32 frameBytes = desc.mChannelsPerFrame * (desc.mBitsPerChannel / 8);
|
|
+ if (!inInputData || !inInputData->mNumberBuffers || !frameBytes)
|
|
+ return noErr;
|
|
|
|
- lk.unlock();
|
|
- m_dataAvailableCV.notify_one();
|
|
+ const uint8_t* source = static_cast<const uint8_t*>(inInputData->mBuffers[0].mData);
|
|
+ const UInt32 totalFrames = inInputData->mBuffers[0].mDataByteSize / frameBytes;
|
|
+ // AudioUnitRender refuses more than kAudioUnitProperty_MaximumFramesPerSlice frames.
|
|
+ const UInt32 maxFrames = std::max<UInt32>(1, GetMaxFramesPerSlice());
|
|
+ static mach_timebase_info_data_t tb = {0, 0};
|
|
+ if (!tb.denom) mach_timebase_info(&tb);
|
|
|
|
- if (m_outputAvailableCb.inputProc)
|
|
+ for (UInt32 offset = 0; offset < totalFrames; )
|
|
{
|
|
- AudioUnitRenderActionFlags flags = kAudioUnitRenderAction_PostRender;
|
|
- const CAStreamBasicDescription& desc = GetStreamFormat(kAudioUnitScope_Output, kInputBus);
|
|
- UInt32 numFrames = m_bufferUsed / desc.mBytesPerFrame;
|
|
+ const UInt32 numFrames = std::min(maxFrames, totalFrames - offset);
|
|
+ const size_t bytes = size_t(numFrames) * frameBytes;
|
|
+ {
|
|
+ std::lock_guard<std::mutex> lk(m_dataAvailableMutex);
|
|
+ if (m_bufferSize < bytes)
|
|
+ {
|
|
+ m_buffer.reset(new uint8_t[bytes]);
|
|
+ m_bufferSize = bytes;
|
|
+ }
|
|
+ memcpy(m_buffer.get(), source + size_t(offset) * frameBytes, bytes);
|
|
+ m_bufferUsed = bytes;
|
|
+ }
|
|
|
|
- m_outputAvailableCb.inputProc(m_outputAvailableCb.inputProcRefCon, &flags, inInputTime, kInputBus, numFrames, nullptr);
|
|
+ AudioTimeStamp time = *inInputTime;
|
|
+ time.mSampleTime += offset;
|
|
+ if (offset && desc.mSampleRate > 0 && tb.numer)
|
|
+ time.mHostTime += uint64_t(double(offset) / desc.mSampleRate * 1e9 * tb.denom / tb.numer);
|
|
+
|
|
+ // The client pulls the slice with AudioUnitRender(bus 1, numFrames) from this callback.
|
|
+ if (m_outputAvailableCb.inputProc)
|
|
+ {
|
|
+ AudioUnitRenderActionFlags flags = 0;
|
|
+ m_outputAvailableCb.inputProc(m_outputAvailableCb.inputProcRefCon, &flags, &time, kInputBus, numFrames, nullptr);
|
|
+ }
|
|
+ offset += numFrames;
|
|
}
|
|
|
|
return noErr;
|
|
diff --git a/src/CoreAudio/CoreAudioComponent/AUHAL.h b/src/CoreAudio/CoreAudioComponent/AUHAL.h
|
|
index bc21bcbbb..fad727962 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,8 +33,12 @@ 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;
|
|
+ bool ValidFormat(AudioUnitScope scope, AudioUnitElement element, const CAStreamBasicDescription& format) override;
|
|
OSStatus Version() override;
|
|
|
|
OSStatus Start() override;
|
|
@@ -43,7 +48,7 @@ public:
|
|
OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable) override;
|
|
OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData) override;
|
|
|
|
- OSStatus Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames) override;
|
|
+ OSStatus RenderBus(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames) override;
|
|
|
|
static OSStatus ComponentEntryDispatch(ComponentParameters *params, AUHAL *This);
|
|
private:
|
|
@@ -59,6 +64,7 @@ private:
|
|
void* inClientData);
|
|
|
|
OSStatus doPlayback(const AudioTimeStamp* inNow, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime);
|
|
+ void traceBlock(const AudioTimeStamp* inNow, const AudioTimeStamp* inOutputTime, OSStatus result, const CAStreamBasicDescription& desc, UInt32 nFrames, AudioBufferList* out);
|
|
OSStatus doRecord(const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime);
|
|
|
|
protected:
|
|
@@ -69,13 +75,29 @@ protected:
|
|
AudioDeviceID m_outputDevice, m_inputDevice;
|
|
AudioDeviceIOProcID m_outputProcID = 0, m_inputProcID = 0;
|
|
|
|
- bool m_dataAvailable = false;
|
|
- std::condition_variable m_dataAvailableCV;
|
|
+ // The latest recorded slice (interleaved, in the bus 1 output format), handed to
|
|
+ // AudioUnitRender(bus 1) from inside the input callback.
|
|
std::mutex m_dataAvailableMutex;
|
|
-
|
|
- // 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;
|
|
+
|
|
+ // Diagnostic-only block statistics (touched only when audio tracing is on).
|
|
+ struct BlockTrace {
|
|
+ unsigned long long callbacks = 0, pullFailures = 0;
|
|
+ unsigned long long stateCounts[4] = {0,0,0,0};
|
|
+ int state = -1;
|
|
+ std::vector<float> prev;
|
|
+ bool havePrev = false;
|
|
+ bool haveTime = false;
|
|
+ double lastSample = 0;
|
|
+ unsigned long long lastHost = 0;
|
|
+ double maxGapMs = 0;
|
|
+ } m_blockTrace;
|
|
};
|
|
|
|
#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 integrity module went to its
|
|
+ * error state (state 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..4b3dd6541 100644
|
|
--- a/src/frameworks/AVFoundation/src/AVCaptureDevice.m
|
|
+++ b/src/frameworks/AVFoundation/src/AVCaptureDevice.m
|
|
@@ -18,9 +18,75 @@
|
|
*/
|
|
|
|
#import <AVFoundation/AVCaptureDevice.h>
|
|
+#include <dispatch/dispatch.h>
|
|
+#include <dlfcn.h>
|
|
+#include <CoreAudio/AudioHardware.h>
|
|
|
|
@implementation AVCaptureDevice
|
|
|
|
++ (NSArray *)devices {
|
|
+ return [NSArray array];
|
|
+}
|
|
+
|
|
++ (NSArray *)devicesWithMediaType:(AVMediaType)mediaType {
|
|
+ (void)mediaType;
|
|
+ return [self devices];
|
|
+}
|
|
+
|
|
++ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType {
|
|
+ (void)mediaType;
|
|
+ return nil;
|
|
+}
|
|
+
|
|
+
|
|
+// Darling has no TCC consent store. Microphone input works through CoreAudio
|
|
+// (PulseAudio), so audio is authorized whenever CoreAudio reports a default input
|
|
+// device. CoreAudio is looked up at run time: AVFoundation does not link it, and
|
|
+// this query only reads its device table (no PulseAudio connection is made).
|
|
+static BOOL hasDefaultInputDevice(void)
|
|
+{
|
|
+ static OSStatus (*getProperty)(AudioObjectID, const AudioObjectPropertyAddress*, UInt32, const void*, UInt32*, void*);
|
|
+ static dispatch_once_t once;
|
|
+ dispatch_once(&once, ^{
|
|
+ // Never closed: CoreAudio keeps process-wide state once loaded.
|
|
+ void* coreAudio = dlopen("/System/Library/Frameworks/CoreAudio.framework/CoreAudio", RTLD_LAZY | RTLD_LOCAL);
|
|
+ if (coreAudio)
|
|
+ getProperty = dlsym(coreAudio, "AudioObjectGetPropertyData");
|
|
+ });
|
|
+ if (!getProperty)
|
|
+ return NO;
|
|
+ AudioDeviceID device = kAudioObjectUnknown;
|
|
+ UInt32 size = sizeof(device);
|
|
+ AudioObjectPropertyAddress address = {
|
|
+ kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster
|
|
+ };
|
|
+ return getProperty(kAudioObjectSystemObject, &address, 0, NULL, &size, &device) == noErr
|
|
+ && device != kAudioObjectUnknown;
|
|
+}
|
|
+
|
|
++ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType
|
|
+{
|
|
+ // There is no camera capture; report Denied so callers take their
|
|
+ // no-access path instead of raising an unrecognized-selector exception.
|
|
+ if ([mediaType isEqualToString: AVMediaTypeAudio] && hasDefaultInputDevice())
|
|
+ return AVAuthorizationStatusAuthorized;
|
|
+ return AVAuthorizationStatusDenied;
|
|
+}
|
|
+
|
|
++ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL))handler
|
|
+{
|
|
+ if (!handler)
|
|
+ return;
|
|
+ BOOL granted = [self authorizationStatusForMediaType: mediaType] == AVAuthorizationStatusAuthorized;
|
|
+ // macOS calls the handler later on an arbitrary queue, never inside this call;
|
|
+ // callers may hold locks here or wait on the main thread for the result.
|
|
+ void (^copied)(BOOL) = [handler copy];
|
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
+ copied(granted);
|
|
+ [copied release];
|
|
+ });
|
|
+}
|
|
+
|
|
- (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..8fa95a174 100644
|
|
--- a/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
|
|
@@ -19,8 +19,104 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
#include <LaunchServices/LaunchServices.h>
|
|
+#include <LaunchServices/LaunchServicesPriv.h>
|
|
#include <CarbonCore/MacErrors.h>
|
|
+#include "LSHostOpen.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;
|
|
+
|
|
+ if ((params == NULL || params->application == NULL) && _LSIsHostOpenURL(url))
|
|
+ return _LSOpenURLOnHost(url);
|
|
+
|
|
+ // 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 +127,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..8a76923c4 100644
|
|
--- a/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LaunchServices.c
|
|
@@ -19,9 +19,14 @@
|
|
|
|
#include <LaunchServices/LaunchServices.h>
|
|
#include <LaunchServices/LaunchServicesPriv.h>
|
|
+#include "LSHostOpen.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);
|
|
|
|
@@ -77,6 +82,12 @@ OSStatus LSOpenFromURLSpec(const LSLaunchURLSpec *inLaunchSpec, CFURLRef *outLau
|
|
if (extension != NULL)
|
|
CFRelease(extension);
|
|
}
|
|
+ else if (_LSIsHostOpenURL(url))
|
|
+ {
|
|
+ temp = _LSOpenURLOnHost(url);
|
|
+ if (temp)
|
|
+ ret = temp;
|
|
+ }
|
|
else
|
|
{
|
|
printf("We can't handle this scheme yet\n");
|
|
@@ -108,8 +119,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 +139,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 +205,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,887 @@
|
|
#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;
|
|
+ static uint64_t cvIntervalNS = 0;
|
|
+ if (!cvIntervalNS) {
|
|
+ const char *hz = getenv("RCLOL_REFRESH_HZ");
|
|
+ int rate = hz ? atoi(hz) : 0;
|
|
+ cvIntervalNS = (rate > 0 && rate <= 1000) ? 1000000000ull / (uint64_t)rate : (1000000000ull / 60ull);
|
|
+ }
|
|
+ for (;;) {
|
|
+ deadline.nsec += cvIntervalNS;
|
|
+ if(deadline.nsec>=NSEC_PER_SEC){deadline.nsec-=NSEC_PER_SEC;++deadline.sec;}
|
|
+ long result;
|
|
+ do { result=__lolsys_syscall(1,1,(long)&deadline,0,0,0,230); } while(result==-4);
|
|
+ if(result<0) return NULL;
|
|
+ dispatchTimerFire(NULL);
|
|
+ struct { long sec, nsec; } now;
|
|
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0) {
|
|
+ long long diffNS = ((long long)now.sec - (long long)deadline.sec) * 1000000000LL + (now.nsec - deadline.nsec);
|
|
+ if (diffNS > (long long)cvIntervalNS) {
|
|
+ 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 +890,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 +1033,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 +1059,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(®istryOnce, 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..a40a634d3 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,19 @@ 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;
|
|
+ int swap_interval_requested;
|
|
+ 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 +144,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 +172,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 +184,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 +200,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 +276,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 +291,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 +300,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;
|
|
@@ -170,15 +309,53 @@ CGLWindowRef CGLGetWindow(void *native_window) {
|
|
return (CGLWindowRef) surface;
|
|
}
|
|
|
|
+/* The default config has no alpha, and NVIDIA rejects it on 32-bit windows (EGL_BAD_CONFIG).
|
|
+ * Translucent windows need an alpha config whose native visual is the window's. */
|
|
+CGLWindowRef CGLGetWindowWithAlpha(void *native_window, unsigned long visual_id) {
|
|
+ static EGLint const alpha_attribute_list[] = {
|
|
+ EGL_RED_SIZE, 1,
|
|
+ EGL_GREEN_SIZE, 1,
|
|
+ EGL_BLUE_SIZE, 1,
|
|
+ EGL_ALPHA_SIZE, 1,
|
|
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
|
+ EGL_NONE
|
|
+ };
|
|
+ EGLint count = 0;
|
|
+ if (!display || !p_eglGetConfigAttrib ||
|
|
+ !p_eglChooseConfig(display, alpha_attribute_list, NULL, 0, &count) || count <= 0)
|
|
+ return NULL;
|
|
+ EGLConfig *configs = malloc(count * sizeof(EGLConfig));
|
|
+ EGLSurface surface = EGL_NO_SURFACE;
|
|
+ if (configs && p_eglChooseConfig(display, alpha_attribute_list, configs, count, &count)) {
|
|
+ for (EGLint i = 0; i < count && surface == EGL_NO_SURFACE; i++) {
|
|
+ EGLint visual = 0;
|
|
+ if (p_eglGetConfigAttrib(display, configs[i], EGL_NATIVE_VISUAL_ID, &visual) &&
|
|
+ (unsigned long) visual == visual_id)
|
|
+ surface = p_eglCreateWindowSurface(display, configs[i],
|
|
+ (EGLNativeWindowType) native_window, NULL);
|
|
+ }
|
|
+ }
|
|
+ free(configs);
|
|
+ return surface == EGL_NO_SURFACE ? NULL : (CGLWindowRef) surface;
|
|
+}
|
|
+
|
|
CGL_EXPORT void CGLDestroyWindow(CGLWindowRef window) {
|
|
- eglDestroySurface(display, (EGLSurface) window);
|
|
+ /* CGLGetWindow returns NULL when eglCreateWindowSurface fails. */
|
|
+ if (window == NULL)
|
|
+ return;
|
|
+ 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 +375,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 +424,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 +454,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]);
|
|
|
|
@@ -303,37 +510,99 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) {
|
|
return pixelFormat->retain_count;
|
|
}
|
|
|
|
-CGLError CGLCreateContext(CGLPixelFormatObj pixelFormat, CGLContextObj share, CGLContextObj *resultp) {
|
|
+static CGLError createContext(CGLPixelFormatObj pixelFormat, CGLContextObj share,
|
|
+ EGLConfig windowConfig, CGLContextObj *resultp) {
|
|
+ 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 : windowConfig ? windowConfig : 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->swap_interval_requested = 0;
|
|
+ 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;
|
|
|
|
return kCGLNoError;
|
|
}
|
|
|
|
+CGLError CGLCreateContext(CGLPixelFormatObj pixelFormat, CGLContextObj share, CGLContextObj *resultp) {
|
|
+ /* Cocotron window compositing requests the default format with NULL. */
|
|
+ return createContext(pixelFormat, share, NULL, resultp);
|
|
+}
|
|
+
|
|
+CGLError CGLCreateContextForWindow(CGLWindowRef window, CGLContextObj *resultp) {
|
|
+ EGLint id = 0, count = 0;
|
|
+ EGLConfig windowConfig = NULL;
|
|
+ ensureDefaultDisplay();
|
|
+ if (window && display && p_eglQuerySurface &&
|
|
+ p_eglQuerySurface(display, (EGLSurface) window, EGL_CONFIG_ID, &id)) {
|
|
+ EGLint const attributes[] = {EGL_CONFIG_ID, id, EGL_NONE};
|
|
+ if (!p_eglChooseConfig(display, attributes, &windowConfig, 1, &count))
|
|
+ count = 0;
|
|
+ }
|
|
+ return createContext(NULL, NULL, count == 1 ? windowConfig : NULL, resultp);
|
|
+}
|
|
+
|
|
CGLContextObj CGLRetainContext(CGLContextObj context) {
|
|
if (context == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
- context->retain_count++;
|
|
+ __sync_add_and_fetch(&context->retain_count, 1);
|
|
return context;
|
|
}
|
|
|
|
@@ -342,11 +611,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 +629,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);
|
|
}
|
|
@@ -384,8 +663,325 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
|
}
|
|
|
|
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" to unconfigured contexts while current. */
|
|
+ if (!context->swap_interval_requested && p_eglSwapInterval && CGLGetCurrentContext() == context) {
|
|
+ p_eglSwapInterval(context->egl_display, 0);
|
|
+ }
|
|
+ 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;
|
|
+ uint64_t paceNS=nativeRefreshIntervalNS();
|
|
+ if(!deadline || (current>deadline && current-deadline>paceNS))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+paceNS;
|
|
+ }
|
|
+ }
|
|
+ if(getenv("RCLOL_GPU_DIRECT_ASYNC")){int queued=enqueueAsyncPresent(ctx,binding.texture,binding.target,width,height);if(!queued){stopAsyncPresent(ctx);__atomic_store_n(&target->ready,0,__ATOMIC_RELEASE);}CGLReleaseContext(ctx);return queued;}
|
|
+ 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) {
|
|
@@ -395,9 +991,11 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
|
if (parameter == kCGLCPSwapInterval)
|
|
{
|
|
GLint v = *value;
|
|
- EGLBoolean success = eglSwapInterval(display, v);
|
|
- if (success)
|
|
+ EGLBoolean success = p_eglSwapInterval(display, v);
|
|
+ if (success) {
|
|
context->swap_interval = v;
|
|
+ context->swap_interval_requested = 1;
|
|
+ }
|
|
return success ? kCGLNoError : kCGLBadValue;
|
|
}
|
|
fprintf(stderr, "CGLSetParameter unimplemented for parameter %d\n", parameter);
|
|
@@ -417,14 +1015,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/CGLInternal.h b/src/frameworks/OpenGL/include/OpenGL/CGLInternal.h
|
|
index 1d0b10e65..5cdd9a89c 100644
|
|
--- a/src/frameworks/OpenGL/include/OpenGL/CGLInternal.h
|
|
+++ b/src/frameworks/OpenGL/include/OpenGL/CGLInternal.h
|
|
@@ -14,8 +14,12 @@ typedef void *CGLWindowRef;
|
|
CGL_EXPORT CGLError CGLRegisterNativeDisplay(void *native_display);
|
|
|
|
CGL_EXPORT CGLWindowRef CGLGetWindow(void *native_window);
|
|
+// A surface with an alpha channel, for a window created with the given 32-bit visual.
|
|
+CGL_EXPORT CGLWindowRef CGLGetWindowWithAlpha(void *native_window, unsigned long visual_id);
|
|
CGL_EXPORT void CGLDestroyWindow(CGLWindowRef window);
|
|
|
|
+// A context that can be made current on the window: one created from its surface's config.
|
|
+CGL_EXPORT CGLError CGLCreateContextForWindow(CGLWindowRef window, CGLContextObj *result);
|
|
CGL_EXPORT CGLError CGLContextMakeCurrentAndAttachToWindow(CGLContextObj context, CGLWindowRef window);
|
|
CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid);
|
|
CGLContextObj CGWindowContextCreate(CGSConnectionID cid, CGSWindowID wid, CFDictionaryRef options);
|
|
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 f5ba615..53d04ad 100644
|
|
--- a/src/shellspawn/shellspawn.c
|
|
+++ b/src/shellspawn/shellspawn.c
|
|
@@ -34,9 +34,10 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#include <sys/ioctl.h>
|
|
#include <signal.h>
|
|
#include "shellspawn.h"
|
|
+#include "shellspawn-fds.h"
|
|
#include "duct_signals.h"
|
|
|
|
-#define DBG 0
|
|
+#define DBG (getenv("DARLING_SHELLSPAWN_DEBUG") && strcmp(getenv("DARLING_SHELLSPAWN_DEBUG"), "1") == 0)
|
|
|
|
int g_serverSocket = -1;
|
|
struct sigaction sigchld_oldaction;
|
|
@@ -50,6 +51,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
|
|
@@ -125,7 +137,7 @@ void spawnShell(int fd)
|
|
{
|
|
pid_t shell_pid = -1;
|
|
int shellfd[3] = { -1, -1, -1 };
|
|
- int pipefd[2];
|
|
+ int pipefd[2] = { -1, -1 };
|
|
int rv;
|
|
struct pollfd pfd[2];
|
|
char** argv = NULL;
|
|
@@ -143,6 +155,14 @@ void spawnShell(int fd)
|
|
|
|
char* alloc_exec = NULL;
|
|
|
|
+ // Keep the control socket out of the stdio slots before receiving streams.
|
|
+ if (fd <= STDERR_FILENO) {
|
|
+ int moved = fcntl(fd, F_DUPFD_CLOEXEC, 3);
|
|
+ if (moved < 0) goto err;
|
|
+ close(fd);
|
|
+ fd = moved;
|
|
+ }
|
|
+
|
|
// Read commands from client
|
|
while (read_cmds)
|
|
{
|
|
@@ -262,25 +282,20 @@ void spawnShell(int fd)
|
|
argv = (char**) realloc(argv, sizeof(char*) * (argc + 1));
|
|
argv[argc] = NULL;
|
|
|
|
- if (pipe(pipefd) == -1)
|
|
- goto err;
|
|
+ if (shellspawn_remap_stdio(shellfd) < 0) goto err;
|
|
+ if (pipe(pipefd) == -1) goto err;
|
|
|
|
setsid();
|
|
setpgrp();
|
|
|
|
- close(STDIN_FILENO);
|
|
- close(STDOUT_FILENO);
|
|
- close(STDERR_FILENO);
|
|
-
|
|
- dup2(shellfd[0], STDIN_FILENO);
|
|
- dup2(shellfd[1], STDOUT_FILENO);
|
|
- dup2(shellfd[2], STDERR_FILENO);
|
|
-
|
|
ioctl(STDIN_FILENO, TIOCSCTTY, STDIN_FILENO);
|
|
|
|
shell_pid = fork();
|
|
+ if (shell_pid < 0) goto err;
|
|
if (shell_pid == 0)
|
|
{
|
|
+ shellspawn_close_streams(shellfd);
|
|
+ close(pipefd[0]);
|
|
close(fd);
|
|
|
|
fcntl(pipefd[1], F_SETFD, FD_CLOEXEC);
|
|
@@ -296,6 +311,7 @@ void spawnShell(int fd)
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
+ shellspawn_close_streams(shellfd);
|
|
if (alloc_exec)
|
|
{
|
|
free(alloc_exec);
|
|
@@ -304,12 +320,14 @@ void spawnShell(int fd)
|
|
|
|
// Check that exec succeeded
|
|
close(pipefd[1]); // close the write end
|
|
+ pipefd[1] = -1;
|
|
if (read(pipefd[0], &rv, sizeof(rv)) == sizeof(rv))
|
|
{
|
|
errno = rv;
|
|
goto err;
|
|
}
|
|
close(pipefd[0]);
|
|
+ pipefd[0] = -1;
|
|
|
|
// Now we start passing signals
|
|
// and check for child process exit
|
|
@@ -372,7 +390,7 @@ void spawnShell(int fd)
|
|
|
|
if (darwin_signal != 0)
|
|
{
|
|
- int fg_pid = tcgetpgrp(shellfd[0]);
|
|
+ int fg_pid = tcgetpgrp(STDIN_FILENO);
|
|
if (fg_pid != -1)
|
|
{
|
|
if (DBG) printf("fg_pid = %d\n", fg_pid);
|
|
@@ -394,11 +412,7 @@ void spawnShell(int fd)
|
|
kill(shell_pid, SIGKILL);
|
|
|
|
// Close shell fds
|
|
- for (int i = 0; i < 3; i++)
|
|
- {
|
|
- if (shellfd[i] != -1)
|
|
- close(shellfd[0]);
|
|
- }
|
|
+ shellspawn_close_streams(shellfd);
|
|
|
|
// Reap the child
|
|
int wstatus;
|
|
@@ -415,13 +429,11 @@ void spawnShell(int fd)
|
|
reapAll();
|
|
return;
|
|
err:
|
|
+ if (pipefd[0] >= 0) close(pipefd[0]);
|
|
+ if (pipefd[1] >= 0) close(pipefd[1]);
|
|
if (DBG) fprintf(stderr, "Error spawning shell: %s\n", strerror(errno));
|
|
|
|
- for (int i = 0; i < 3; i++)
|
|
- {
|
|
- if (shellfd[i] != -1)
|
|
- close(shellfd[0]);
|
|
- }
|
|
+ shellspawn_close_streams(shellfd);
|
|
|
|
if (shell_pid != -1)
|
|
kill(shell_pid, SIGKILL);
|
|
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..a227abee8 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;
|
|
@@ -648,6 +658,11 @@ static void socket_bitmap_put(socket_bitmap_t* bitmap, int socket) {
|
|
|
|
pthread_mutex_lock(&bitmap->mutex);
|
|
|
|
+ // darling-lol: placed above the slots because its slot was taken (socket_bitmap_place).
|
|
+ if (socket > bitmap->highest) {
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
index = bitmap->highest - socket;
|
|
|
|
bitmap->bits[index / 8] &= ~(1 << (index % 8));
|
|
@@ -673,7 +688,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 +706,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;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -698,6 +727,22 @@ out:
|
|
pthread_mutex_unlock(&bitmap->mutex);
|
|
};
|
|
|
|
+/* darling-lol: move `fd` to the next slot. dup2() silently closed whatever the guest or a native library (NVIDIA's
|
|
+ * driver: one fd per allocation) already held on that number, and its owner later closed our socket. F_DUPFD never
|
|
+ * replaces a descriptor: if the slot is taken, it returns the lowest free number above it, which is ours alone. */
|
|
+static int socket_bitmap_place(int fd) {
|
|
+ int slot = socket_bitmap_get(&socket_bitmap);
|
|
+ if (slot < 0) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ int placed = fcntl(fd, F_DUPFD, slot);
|
|
+ if (placed != slot) {
|
|
+ socket_bitmap_put(&socket_bitmap, slot);
|
|
+ }
|
|
+ return placed;
|
|
+}
|
|
+
|
|
int __mldr_create_rpc_socket(void) {
|
|
int pre_fd = -1;
|
|
int fd = -1;
|
|
@@ -707,18 +752,11 @@ int __mldr_create_rpc_socket(void) {
|
|
goto err_out;
|
|
}
|
|
|
|
- fd = socket_bitmap_get(&socket_bitmap);
|
|
+ fd = socket_bitmap_place(pre_fd);
|
|
if (fd < 0) {
|
|
goto err_out;
|
|
}
|
|
|
|
- if (dup2(pre_fd, fd) < 0) {
|
|
- // we have to put it away ourselves here because `fd` is not yet valid, so we can't close() it in the error handler
|
|
- socket_bitmap_put(&socket_bitmap, fd);
|
|
- fd = -1;
|
|
- goto err_out;
|
|
- }
|
|
-
|
|
close(pre_fd);
|
|
pre_fd = -1;
|
|
|
|
@@ -772,17 +810,11 @@ int __mldr_create_process_lifetime_pipe(int* fds) {
|
|
}
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
- fds[i] = socket_bitmap_get(&socket_bitmap);
|
|
+ fds[i] = socket_bitmap_place(pre_fds[i]);
|
|
if (fds[i] < 0) {
|
|
goto err_out;
|
|
}
|
|
|
|
- if (dup2(pre_fds[i], fds[i]) < 0) {
|
|
- socket_bitmap_put(&socket_bitmap, fds[i]);
|
|
- fds[i] = -1;
|
|
- goto err_out;
|
|
- }
|
|
-
|
|
close(pre_fds[i]);
|
|
pre_fds[i] = -1;
|
|
}
|
|
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,37 @@
|
|
+#ifndef RCLOL_AUDIO_TRACE_H
|
|
+#define RCLOL_AUDIO_TRACE_H
|
|
+#include <cstdio>
|
|
+#include <cstdlib>
|
|
+#include <cstdarg>
|
|
+#include <ctime>
|
|
+#include <atomic>
|
|
+#include <unistd.h>
|
|
+// Opt-in API/status diagnostics only; never records audio samples or names.
|
|
+// Enabled by env RCLOL_AUDIO_TRACE or by the existence of /tmp/rclol-audio-trace
|
|
+// or /Users/Shared/rclol-audio-trace (the latter survives /tmp wipes at launch).
|
|
+// A negative result is re-checked at most once every ~2 seconds; a positive
|
|
+// result is cached for the life of the process.
|
|
+static inline bool rclolAudioTraceEnabled() {
|
|
+ static std::atomic<bool> enabled{false};
|
|
+ static std::atomic<long long> nextCheckMs{0};
|
|
+ if (enabled.load(std::memory_order_relaxed)) return true;
|
|
+ struct timespec ts;
|
|
+ clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
+ long long nowMs = (long long) ts.tv_sec * 1000 + ts.tv_nsec / 1000000 + 1;
|
|
+ if (nowMs < nextCheckMs.load(std::memory_order_relaxed)) return false;
|
|
+ nextCheckMs.store(nowMs + 2000, std::memory_order_relaxed);
|
|
+ if (getenv("RCLOL_AUDIO_TRACE") != nullptr ||
|
|
+ access("/tmp/rclol-audio-trace", F_OK) == 0 ||
|
|
+ access("/Users/Shared/rclol-audio-trace", F_OK) == 0) {
|
|
+ enabled.store(true, std::memory_order_relaxed);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+}
|
|
+static inline void rclolAudioTrace(const char* format, ...) {
|
|
+ if (!rclolAudioTraceEnabled()) 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 (rclolAudioTraceEnabled() && 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
|
|
index 000000000..e5363c06a
|
|
--- /dev/null
|
|
+++ b/src/frameworks/OpenGL/CGLAsyncPresent.inc
|
|
@@ -0,0 +1,204 @@
|
|
+/* 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. */
|
|
+
|
|
+/* darling-lol: software refresh period for the paced paths, from RCLOL_REFRESH_HZ
|
|
+ * (1-1000 Hz). Unset or out of range keeps the previous 60 Hz period. */
|
|
+static uint64_t nativeRefreshIntervalNS(void){
|
|
+ static uint64_t interval;
|
|
+ if(!interval){
|
|
+ const char *hz=getenv("RCLOL_REFRESH_HZ");
|
|
+ long rate=hz?strtol(hz,NULL,10):0;
|
|
+ interval=(rate>0&&rate<=1000)?1000000000ull/(uint64_t)rate:16666667ull;
|
|
+ }
|
|
+ return interval;
|
|
+}
|
|
+
|
|
+struct DarlingAsyncPresent {
|
|
+ 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;
|
|
+ uint64_t refreshNS=nativeRefreshIntervalNS();
|
|
+ deadline += refreshNS;
|
|
+ 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>refreshNS){__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..29eef9956 100644
|
|
--- a/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSInfo.m
|
|
@@ -20,6 +20,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include <LaunchServices/LaunchServices.h>
|
|
#include <CarbonCore/MacErrors.h>
|
|
+#include "LSHostOpen.h"
|
|
#include <stdio.h>
|
|
#include <sqlite3.h>
|
|
#import <Foundation/NSString.h>
|
|
@@ -106,7 +107,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
|
{
|
|
if (!inBundleIdentifier)
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, paramErr, NULL);
|
|
return NULL;
|
|
}
|
|
@@ -116,7 +117,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
|
|
|
if (dq == nil)
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
|
|
return NULL;
|
|
}
|
|
@@ -138,7 +139,7 @@ CFArrayRef LSCopyApplicationURLsForBundleIdentifier(CFStringRef inBundleIdentifi
|
|
}
|
|
else
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
|
|
retval = NULL;
|
|
}
|
|
@@ -247,7 +248,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
|
|
|
|
if (dq == nil)
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, fnfErr, NULL);
|
|
return NULL;
|
|
}
|
|
@@ -275,7 +276,7 @@ CFURLRef LSCopyDefaultApplicationURLForContentType(CFStringRef inContentType, LS
|
|
}
|
|
else
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, kLSApplicationNotFoundErr, NULL);
|
|
retval = NULL;
|
|
}
|
|
@@ -292,7 +293,7 @@ CFURLRef LSCopyDefaultApplicationURLForURL(CFURLRef inURL, LSRolesMask inRoleMas
|
|
|
|
if (status != noErr)
|
|
{
|
|
- if (*outError)
|
|
+ if (outError)
|
|
*outError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, status, NULL);
|
|
return NULL;
|
|
}
|
|
@@ -306,6 +307,9 @@ CFURLRef LSCopyDefaultApplicationURLForURL(CFURLRef inURL, LSRolesMask inRoleMas
|
|
|
|
OSStatus LSGetApplicationForURL(CFURLRef inURL, LSRolesMask inRoleMask, FSRef *outAppRef, CFURLRef *outAppURL)
|
|
{
|
|
+ if (_LSGetHostHandlerForURL(inURL, outAppRef, outAppURL) == noErr)
|
|
+ return noErr;
|
|
+
|
|
NSString* extension = [(NSURL*) inURL pathExtension];
|
|
return LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, (CFStringRef) extension, inRoleMask, outAppRef, outAppURL);
|
|
}
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/CMakeLists.txt b/src/frameworks/CoreServices/src/LaunchServices/CMakeLists.txt
|
|
index bb7f35068..957cc9d3e 100644
|
|
--- a/src/frameworks/CoreServices/src/LaunchServices/CMakeLists.txt
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/CMakeLists.txt
|
|
@@ -1,5 +1,8 @@
|
|
set(SUBFRAMEWORK_VERSION "A")
|
|
|
|
+# LSHostOpen.c loads the host's libsystemd through the ELF loader.
|
|
+include_directories(${CMAKE_SOURCE_DIR}/src/startup/mldr/elfcalls)
|
|
+
|
|
generate_sdk_subframework(LaunchServices
|
|
BASE_PATH ${coreservices_sdk_path}
|
|
VERSION ${SUBFRAMEWORK_VERSION}
|
|
@@ -20,6 +23,8 @@ add_framework(LaunchServices
|
|
LSApplicationWorkspace.m
|
|
LSRunning.m
|
|
constants.c
|
|
+ LSHostOpen.c
|
|
+ LSHostOpenBus.c
|
|
LSInfo.m
|
|
LSOpen.m
|
|
UTType.m
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.h b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.h
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.h
|
|
@@ -0,0 +1,43 @@
|
|
+/*
|
|
+This file is part of Darling.
|
|
+
|
|
+Copyright (C) 2026 Darling developers
|
|
+
|
|
+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/>.
|
|
+*/
|
|
+
|
|
+#ifndef LS_HOST_OPEN_H_
|
|
+#define LS_HOST_OPEN_H_
|
|
+
|
|
+#include <CoreFoundation/CoreFoundation.h>
|
|
+#include <LaunchServices/LaunchServices.h>
|
|
+
|
|
+#ifdef __cplusplus
|
|
+extern "C" {
|
|
+#endif
|
|
+
|
|
+/* URLs with these schemes (http, https, mailto) are opened by the host desktop. */
|
|
+Boolean _LSIsHostOpenURL(CFURLRef url);
|
|
+
|
|
+/* Hands the URL to the host desktop. */
|
|
+OSStatus _LSOpenURLOnHost(CFURLRef url);
|
|
+
|
|
+/* Reports the host opener as the default application for a host-opened URL. */
|
|
+OSStatus _LSGetHostHandlerForURL(CFURLRef url, FSRef* outAppRef, CFURLRef* outAppURL);
|
|
+
|
|
+#ifdef __cplusplus
|
|
+}
|
|
+#endif
|
|
+
|
|
+#endif
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.c b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.c
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpen.c
|
|
@@ -0,0 +1,207 @@
|
|
+/*
|
|
+This file is part of Darling.
|
|
+
|
|
+Copyright (C) 2026 Darling developers
|
|
+
|
|
+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/>.
|
|
+*/
|
|
+
|
|
+/*
|
|
+ * Web and mail URLs have no handler inside the prefix, so they are handed to
|
|
+ * the Linux desktop. sd-bus is used from the host's libsystemd, loaded through
|
|
+ * the ELF loader; the guest shares the host's user and network, so the
|
|
+ * session bus is reachable directly.
|
|
+ */
|
|
+
|
|
+#include "LSHostOpen.h"
|
|
+#include "LSHostOpenBus.h"
|
|
+#include <CarbonCore/MacErrors.h>
|
|
+#include <elfcalls.h>
|
|
+#include <pthread.h>
|
|
+#include <stdlib.h>
|
|
+#include <stdio.h>
|
|
+#include <string.h>
|
|
+
|
|
+extern struct elf_calls* _elfcalls;
|
|
+
|
|
+/* Where the host's root file system appears inside the prefix. */
|
|
+#define HOST_ROOT "/Volumes/SystemRoot"
|
|
+
|
|
+static struct ls_host_api host_api;
|
|
+static Boolean host_api_ready;
|
|
+static pthread_once_t host_api_once = PTHREAD_ONCE_INIT;
|
|
+
|
|
+static void load_host_api(void)
|
|
+{
|
|
+ void *systemd, *libc;
|
|
+ struct ls_host_api api;
|
|
+
|
|
+ if (_elfcalls == NULL)
|
|
+ return;
|
|
+
|
|
+ systemd = _elfcalls->dlopen("libsystemd.so.0");
|
|
+ if (systemd == NULL)
|
|
+ return;
|
|
+ libc = _elfcalls->dlopen("libc.so.6");
|
|
+ if (libc == NULL)
|
|
+ {
|
|
+ _elfcalls->dlclose(systemd);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ memset(&api, 0, sizeof(api));
|
|
+#define BIND(lib, field) \
|
|
+ if ((*(void**) &api.field = _elfcalls->dlsym(lib, #field)) == NULL) \
|
|
+ goto fail;
|
|
+ BIND(systemd, sd_bus_new);
|
|
+ BIND(systemd, sd_bus_set_address);
|
|
+ BIND(systemd, sd_bus_set_bus_client);
|
|
+ BIND(systemd, sd_bus_start);
|
|
+ BIND(systemd, sd_bus_flush_close_unref);
|
|
+ BIND(systemd, sd_bus_message_new_method_call);
|
|
+ BIND(systemd, sd_bus_message_append);
|
|
+ BIND(systemd, sd_bus_message_append_strv);
|
|
+ BIND(systemd, sd_bus_message_open_container);
|
|
+ BIND(systemd, sd_bus_message_close_container);
|
|
+ BIND(systemd, sd_bus_call);
|
|
+ BIND(systemd, sd_bus_message_unref);
|
|
+ BIND(systemd, sd_bus_error_free);
|
|
+ /* Host IDs and paths, not the prefix's view of them. */
|
|
+ BIND(libc, getuid);
|
|
+ BIND(libc, access);
|
|
+#undef BIND
|
|
+ api.getenv = getenv;
|
|
+ api.runtime_dir_root = "/run/user";
|
|
+
|
|
+ host_api = api;
|
|
+ host_api_ready = TRUE;
|
|
+ return;
|
|
+
|
|
+fail:
|
|
+ _elfcalls->dlclose(libc);
|
|
+ _elfcalls->dlclose(systemd);
|
|
+}
|
|
+
|
|
+static const struct ls_host_api* get_host_api(void)
|
|
+{
|
|
+ pthread_once(&host_api_once, load_host_api);
|
|
+ return host_api_ready ? &host_api : NULL;
|
|
+}
|
|
+
|
|
+/* Returns the absolute URL as a malloc'd UTF-8 string. */
|
|
+static char* copy_url_string(CFURLRef url)
|
|
+{
|
|
+ CFURLRef absolute;
|
|
+ CFStringRef string;
|
|
+ CFIndex size;
|
|
+ char* buffer = NULL;
|
|
+
|
|
+ if (url == NULL)
|
|
+ return NULL;
|
|
+ absolute = CFURLCopyAbsoluteURL(url);
|
|
+ if (absolute == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ string = CFURLGetString(absolute);
|
|
+ size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1;
|
|
+ if (size <= LS_HOST_OPEN_MAX_URL_LENGTH * 4 + 1)
|
|
+ {
|
|
+ buffer = malloc(size);
|
|
+ if (buffer != NULL && !CFStringGetCString(string, buffer, size, kCFStringEncodingUTF8))
|
|
+ {
|
|
+ free(buffer);
|
|
+ buffer = NULL;
|
|
+ }
|
|
+ }
|
|
+ CFRelease(absolute);
|
|
+ return buffer;
|
|
+}
|
|
+
|
|
+Boolean _LSIsHostOpenURL(CFURLRef url)
|
|
+{
|
|
+ CFStringRef scheme;
|
|
+ Boolean result = FALSE;
|
|
+
|
|
+ if (url == NULL)
|
|
+ return FALSE;
|
|
+ scheme = CFURLCopyScheme(url);
|
|
+ if (scheme == NULL)
|
|
+ return FALSE;
|
|
+
|
|
+ result = CFStringCompare(scheme, CFSTR("http"), kCFCompareCaseInsensitive) == kCFCompareEqualTo
|
|
+ || CFStringCompare(scheme, CFSTR("https"), kCFCompareCaseInsensitive) == kCFCompareEqualTo
|
|
+ || CFStringCompare(scheme, CFSTR("mailto"), kCFCompareCaseInsensitive) == kCFCompareEqualTo;
|
|
+ CFRelease(scheme);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+OSStatus _LSOpenURLOnHost(CFURLRef url)
|
|
+{
|
|
+ const struct ls_host_api* api;
|
|
+ char* string = copy_url_string(url);
|
|
+ int r;
|
|
+
|
|
+ if (string == NULL || _LSHostOpenValidateURL(string) < 0)
|
|
+ {
|
|
+ free(string);
|
|
+ return paramErr;
|
|
+ }
|
|
+
|
|
+ api = get_host_api();
|
|
+ if (api == NULL)
|
|
+ {
|
|
+ fprintf(stderr, "LaunchServices: cannot open URL: host libsystemd is unavailable\n");
|
|
+ free(string);
|
|
+ return kLSApplicationNotFoundErr;
|
|
+ }
|
|
+
|
|
+ r = _LSHostOpenURL(api, string, NULL);
|
|
+ free(string);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ fprintf(stderr, "LaunchServices: host desktop did not accept URL (error %d)\n", -r);
|
|
+ return kLSServerCommunicationErr;
|
|
+ }
|
|
+ return noErr;
|
|
+}
|
|
+
|
|
+OSStatus _LSGetHostHandlerForURL(CFURLRef url, FSRef* outAppRef, CFURLRef* outAppURL)
|
|
+{
|
|
+ const struct ls_host_api* api;
|
|
+ const char* opener;
|
|
+ char path[256];
|
|
+
|
|
+ if (!_LSIsHostOpenURL(url))
|
|
+ return kLSApplicationNotFoundErr;
|
|
+ api = get_host_api();
|
|
+ if (api == NULL)
|
|
+ return kLSApplicationNotFoundErr;
|
|
+
|
|
+ opener = _LSHostOpenFindOpener(api);
|
|
+ snprintf(path, sizeof(path), HOST_ROOT "%s", opener ? opener : "/usr/bin/xdg-open");
|
|
+
|
|
+ if (outAppRef != NULL)
|
|
+ {
|
|
+ OSStatus status = FSPathMakeRef((const UInt8*) path, outAppRef, NULL);
|
|
+ if (status != noErr)
|
|
+ return status;
|
|
+ }
|
|
+ if (outAppURL != NULL)
|
|
+ {
|
|
+ *outAppURL = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*) path, strlen(path), FALSE);
|
|
+ if (*outAppURL == NULL)
|
|
+ return kLSApplicationNotFoundErr;
|
|
+ }
|
|
+ return noErr;
|
|
+}
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.h b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.h
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.h
|
|
@@ -0,0 +1,111 @@
|
|
+/*
|
|
+This file is part of Darling.
|
|
+
|
|
+Copyright (C) 2026 Darling developers
|
|
+
|
|
+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/>.
|
|
+*/
|
|
+
|
|
+/*
|
|
+ * Hands a URL to the host desktop over the user's D-Bus session bus.
|
|
+ *
|
|
+ * This part has no Darling or CoreFoundation dependencies: every host
|
|
+ * function comes from the table below, so it can also be built natively on
|
|
+ * Linux against libsystemd for testing. Errors are negative Linux errno
|
|
+ * values as returned by sd-bus.
|
|
+ */
|
|
+
|
|
+#ifndef LS_HOST_OPEN_BUS_H_
|
|
+#define LS_HOST_OPEN_BUS_H_
|
|
+
|
|
+#include <stdint.h>
|
|
+
|
|
+#ifdef __cplusplus
|
|
+extern "C" {
|
|
+#endif
|
|
+
|
|
+#define LS_HOST_OPEN_MAX_URL_LENGTH 4096
|
|
+
|
|
+/* Linux errno values; the guest's errno numbering differs. */
|
|
+#define LS_HOST_LINUX_ENOENT 2
|
|
+#define LS_HOST_LINUX_EINVAL 22
|
|
+#define LS_HOST_LINUX_ENOSYS 38
|
|
+#define LS_HOST_LINUX_ETIMEDOUT 110
|
|
+
|
|
+/* Layout of sd_bus_error. */
|
|
+struct ls_host_bus_error
|
|
+{
|
|
+ const char* name;
|
|
+ const char* message;
|
|
+ int _need_free;
|
|
+};
|
|
+
|
|
+struct ls_host_api
|
|
+{
|
|
+ /* libsystemd (sd-bus); bus and message objects are opaque */
|
|
+ int (*sd_bus_new)(void** ret);
|
|
+ int (*sd_bus_set_address)(void* bus, const char* address);
|
|
+ int (*sd_bus_set_bus_client)(void* bus, int b);
|
|
+ int (*sd_bus_start)(void* bus);
|
|
+ void* (*sd_bus_flush_close_unref)(void* bus);
|
|
+ int (*sd_bus_message_new_method_call)(void* bus, void** m, const char* destination,
|
|
+ const char* path, const char* interface, const char* member);
|
|
+ int (*sd_bus_message_append)(void* m, const char* types, ...);
|
|
+ int (*sd_bus_message_append_strv)(void* m, char** l);
|
|
+ int (*sd_bus_message_open_container)(void* m, char type, const char* contents);
|
|
+ int (*sd_bus_message_close_container)(void* m);
|
|
+ int (*sd_bus_call)(void* bus, void* m, uint64_t usec, struct ls_host_bus_error* ret_error, void** reply);
|
|
+ void* (*sd_bus_message_unref)(void* m);
|
|
+ void (*sd_bus_error_free)(struct ls_host_bus_error* e);
|
|
+
|
|
+ /* Host process environment */
|
|
+ char* (*getenv)(const char* name);
|
|
+ unsigned int (*getuid)(void);
|
|
+ int (*access)(const char* path, int mode);
|
|
+
|
|
+ /* Parent of the per-user runtime directories, normally "/run/user" */
|
|
+ const char* runtime_dir_root;
|
|
+ /* Per-call timeout; 0 selects the default */
|
|
+ uint64_t timeout_usec;
|
|
+};
|
|
+
|
|
+enum
|
|
+{
|
|
+ LS_HOST_OPEN_VIA_PORTAL = 1,
|
|
+ LS_HOST_OPEN_VIA_SYSTEMD = 2,
|
|
+};
|
|
+
|
|
+/* 1 if the URL has a scheme that is handed to the host (http, https, mailto). */
|
|
+int _LSHostOpenSchemeSupported(const char* url);
|
|
+
|
|
+/* 0 if the URL may be handed to the host, otherwise -EINVAL. */
|
|
+int _LSHostOpenValidateURL(const char* url);
|
|
+
|
|
+/* Host path of xdg-open, or NULL if it is not in the usual locations. */
|
|
+const char* _LSHostOpenFindOpener(const struct ls_host_api* api);
|
|
+
|
|
+/*
|
|
+ * Opens the URL through org.freedesktop.portal.OpenURI, or, if that fails,
|
|
+ * runs xdg-open in a transient systemd user service. Either way the handler
|
|
+ * is started by the host session, never as a child of the caller.
|
|
+ * Returns 0 and stores the method used in *method (may be NULL), or a
|
|
+ * negative errno.
|
|
+ */
|
|
+int _LSHostOpenURL(const struct ls_host_api* api, const char* url, int* method);
|
|
+
|
|
+#ifdef __cplusplus
|
|
+}
|
|
+#endif
|
|
+
|
|
+#endif
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.c b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.c
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/src/frameworks/CoreServices/src/LaunchServices/LSHostOpenBus.c
|
|
@@ -0,0 +1,278 @@
|
|
+/*
|
|
+This file is part of Darling.
|
|
+
|
|
+Copyright (C) 2026 Darling developers
|
|
+
|
|
+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 "LSHostOpenBus.h"
|
|
+#include <stdio.h>
|
|
+#include <string.h>
|
|
+#include <strings.h>
|
|
+#include <time.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+#define DEFAULT_TIMEOUT_USEC (10ULL * 1000 * 1000)
|
|
+#define HOST_X_OK 1
|
|
+
|
|
+static const char* const supported_schemes[] = { "http:", "https:", "mailto:" };
|
|
+
|
|
+/* Passed to the transient unit so xdg-open finds the user's display. */
|
|
+static const char* const forwarded_environment[] = { "DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP" };
|
|
+
|
|
+int _LSHostOpenSchemeSupported(const char* url)
|
|
+{
|
|
+ if (url == NULL)
|
|
+ return 0;
|
|
+ for (size_t i = 0; i < sizeof(supported_schemes) / sizeof(supported_schemes[0]); i++)
|
|
+ {
|
|
+ size_t len = strlen(supported_schemes[i]);
|
|
+ if (strncasecmp(url, supported_schemes[i], len) == 0)
|
|
+ return 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int _LSHostOpenValidateURL(const char* url)
|
|
+{
|
|
+ size_t len;
|
|
+
|
|
+ if (!_LSHostOpenSchemeSupported(url))
|
|
+ return -LS_HOST_LINUX_EINVAL;
|
|
+
|
|
+ len = strlen(url);
|
|
+ if (len > LS_HOST_OPEN_MAX_URL_LENGTH)
|
|
+ return -LS_HOST_LINUX_EINVAL;
|
|
+
|
|
+ for (size_t i = 0; i < len; i++)
|
|
+ {
|
|
+ unsigned char c = (unsigned char) url[i];
|
|
+ if (c < 0x20 || c == 0x7f)
|
|
+ return -LS_HOST_LINUX_EINVAL;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+const char* _LSHostOpenFindOpener(const struct ls_host_api* api)
|
|
+{
|
|
+ static const char* const candidates[] = {
|
|
+ "/usr/local/bin/xdg-open",
|
|
+ "/usr/bin/xdg-open",
|
|
+ "/bin/xdg-open",
|
|
+ };
|
|
+
|
|
+ if (api == NULL || api->access == NULL)
|
|
+ return NULL;
|
|
+ for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++)
|
|
+ {
|
|
+ if (api->access(candidates[i], HOST_X_OK) == 0)
|
|
+ return candidates[i];
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static int api_complete(const struct ls_host_api* api)
|
|
+{
|
|
+ return api != NULL && api->sd_bus_new && api->sd_bus_set_address && api->sd_bus_set_bus_client
|
|
+ && api->sd_bus_start && api->sd_bus_flush_close_unref && api->sd_bus_message_new_method_call
|
|
+ && api->sd_bus_message_append && api->sd_bus_message_append_strv
|
|
+ && api->sd_bus_message_open_container && api->sd_bus_message_close_container
|
|
+ && api->sd_bus_call && api->sd_bus_message_unref && api->sd_bus_error_free
|
|
+ && api->getenv && api->getuid;
|
|
+}
|
|
+
|
|
+static int open_session_bus(const struct ls_host_api* api, void** ret)
|
|
+{
|
|
+ char address[512];
|
|
+ const char* env = api->getenv("DBUS_SESSION_BUS_ADDRESS");
|
|
+ void* bus = NULL;
|
|
+ int r;
|
|
+
|
|
+ if (env != NULL && env[0] != '\0')
|
|
+ {
|
|
+ if (strlen(env) >= sizeof(address))
|
|
+ return -LS_HOST_LINUX_EINVAL;
|
|
+ strcpy(address, env);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const char* root = api->runtime_dir_root ? api->runtime_dir_root : "/run/user";
|
|
+ if (snprintf(address, sizeof(address), "unix:path=%s/%u/bus", root, api->getuid())
|
|
+ >= (int) sizeof(address))
|
|
+ return -LS_HOST_LINUX_EINVAL;
|
|
+ }
|
|
+
|
|
+ r = api->sd_bus_new(&bus);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+ r = api->sd_bus_set_address(bus, address);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_set_bus_client(bus, 1);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_start(bus);
|
|
+ if (r < 0)
|
|
+ {
|
|
+ api->sd_bus_flush_close_unref(bus);
|
|
+ return r;
|
|
+ }
|
|
+ *ret = bus;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int call(const struct ls_host_api* api, void* bus, void* m)
|
|
+{
|
|
+ struct ls_host_bus_error error = { NULL, NULL, 0 };
|
|
+ void* reply = NULL;
|
|
+ uint64_t timeout = api->timeout_usec ? api->timeout_usec : DEFAULT_TIMEOUT_USEC;
|
|
+ int r = api->sd_bus_call(bus, m, timeout, &error, &reply);
|
|
+
|
|
+ if (reply != NULL)
|
|
+ api->sd_bus_message_unref(reply);
|
|
+ api->sd_bus_error_free(&error);
|
|
+ return r < 0 ? r : 0;
|
|
+}
|
|
+
|
|
+static int open_via_portal(const struct ls_host_api* api, void* bus, const char* url)
|
|
+{
|
|
+ void* m = NULL;
|
|
+ int r = api->sd_bus_message_new_method_call(bus, &m, "org.freedesktop.portal.Desktop",
|
|
+ "/org/freedesktop/portal/desktop", "org.freedesktop.portal.OpenURI", "OpenURI");
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ r = api->sd_bus_message_append(m, "ss", "", url);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_append(m, "a{sv}", 0);
|
|
+ if (r >= 0)
|
|
+ r = call(api, bus, m);
|
|
+ api->sd_bus_message_unref(m);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int append_environment(const struct ls_host_api* api, void* m)
|
|
+{
|
|
+ char storage[3][1024];
|
|
+ char* entries[4];
|
|
+ size_t count = 0;
|
|
+ int r;
|
|
+
|
|
+ for (size_t i = 0; i < sizeof(forwarded_environment) / sizeof(forwarded_environment[0]); i++)
|
|
+ {
|
|
+ const char* value = api->getenv(forwarded_environment[i]);
|
|
+ int n;
|
|
+
|
|
+ if (value == NULL || value[0] == '\0')
|
|
+ continue;
|
|
+ n = snprintf(storage[count], sizeof(storage[count]), "%s=%s", forwarded_environment[i], value);
|
|
+ if (n < 0 || n >= (int) sizeof(storage[count]))
|
|
+ continue;
|
|
+ entries[count] = storage[count];
|
|
+ count++;
|
|
+ }
|
|
+ entries[count] = NULL;
|
|
+ if (count == 0)
|
|
+ return 0;
|
|
+
|
|
+ r = api->sd_bus_message_open_container(m, 'r', "sv");
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_append(m, "s", "Environment");
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_open_container(m, 'v', "as");
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_append_strv(m, entries);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_close_container(m);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_close_container(m);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static int open_via_systemd(const struct ls_host_api* api, void* bus, const char* url)
|
|
+{
|
|
+ static unsigned int counter;
|
|
+ struct timespec now;
|
|
+ char unit[128];
|
|
+ const char* opener = _LSHostOpenFindOpener(api);
|
|
+ void* m = NULL;
|
|
+ int r;
|
|
+
|
|
+ /* Bare names are resolved by systemd >= 248. */
|
|
+ if (opener == NULL)
|
|
+ opener = "xdg-open";
|
|
+
|
|
+ clock_gettime(CLOCK_REALTIME, &now);
|
|
+ snprintf(unit, sizeof(unit), "app-darling-openurl-%d-%llx-%u.service", (int) getpid(),
|
|
+ (unsigned long long) now.tv_sec * 1000000000ULL + (unsigned long long) now.tv_nsec,
|
|
+ __atomic_add_fetch(&counter, 1, __ATOMIC_RELAXED));
|
|
+
|
|
+ r = api->sd_bus_message_new_method_call(bus, &m, "org.freedesktop.systemd1",
|
|
+ "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartTransientUnit");
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ r = api->sd_bus_message_append(m, "ss", unit, "fail");
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_open_container(m, 'a', "(sv)");
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_append(m, "(sv)(sv)(sv)(sv)",
|
|
+ "Description", "s", "Open URL from Darling",
|
|
+ /* Keep the handler running after xdg-open exits. */
|
|
+ "KillMode", "s", "process",
|
|
+ "CollectMode", "s", "inactive-or-failed",
|
|
+ "ExecStart", "a(sasb)", 1, opener, 2, "xdg-open", url, 0);
|
|
+ if (r >= 0)
|
|
+ r = append_environment(api, m);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_close_container(m);
|
|
+ if (r >= 0)
|
|
+ r = api->sd_bus_message_append(m, "a(sa(sv))", 0);
|
|
+ if (r >= 0)
|
|
+ r = call(api, bus, m);
|
|
+ api->sd_bus_message_unref(m);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+int _LSHostOpenURL(const struct ls_host_api* api, const char* url, int* method)
|
|
+{
|
|
+ void* bus = NULL;
|
|
+ int r;
|
|
+
|
|
+ r = _LSHostOpenValidateURL(url);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+ if (!api_complete(api))
|
|
+ return -LS_HOST_LINUX_ENOSYS;
|
|
+
|
|
+ r = open_session_bus(api, &bus);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ r = open_via_portal(api, bus, url);
|
|
+ if (r >= 0)
|
|
+ {
|
|
+ if (method)
|
|
+ *method = LS_HOST_OPEN_VIA_PORTAL;
|
|
+ }
|
|
+ /* A portal that timed out may still open the URL; don't open it twice. */
|
|
+ else if (r != -LS_HOST_LINUX_ETIMEDOUT)
|
|
+ {
|
|
+ r = open_via_systemd(api, bus, url);
|
|
+ if (r >= 0 && method)
|
|
+ *method = LS_HOST_OPEN_VIA_SYSTEMD;
|
|
+ }
|
|
+
|
|
+ api->sd_bus_flush_close_unref(bus);
|
|
+ return r < 0 ? r : 0;
|
|
+}
|
|
diff --git a/src/frameworks/CoreServices/src/CarbonCore/Gestalt.cpp b/src/frameworks/CoreServices/src/CarbonCore/Gestalt.cpp
|
|
index dffdd4cb2..7430f587b 100644
|
|
--- a/src/frameworks/CoreServices/src/CarbonCore/Gestalt.cpp
|
|
+++ b/src/frameworks/CoreServices/src/CarbonCore/Gestalt.cpp
|
|
@@ -25,6 +25,9 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
OSStatus Gestalt(uint32_t type, int* value)
|
|
{
|
|
+ if (!value)
|
|
+ return paramErr;
|
|
+ *value = 0;
|
|
switch (type)
|
|
{
|
|
case gestaltSystemVersion:
|
|
@@ -32,24 +35,31 @@ OSStatus Gestalt(uint32_t type, int* value)
|
|
case gestaltSystemVersionMinor:
|
|
case gestaltSystemVersionPatch:
|
|
{
|
|
- // We may be mixing the Darwin and OS version, but this API is deprecated anyway
|
|
- int name[2] = { CTL_KERN, KERN_OSRELEASE };
|
|
- char version[32];
|
|
- size_t version_len = sizeof(version) - 1;
|
|
- int maj, min, patch;
|
|
-
|
|
- sysctl(name, 2, version, &version_len, nullptr, 0);
|
|
- sscanf(version, "%d.%d.%d", &maj, &min, &patch);
|
|
-
|
|
- if (type == gestaltSystemVersion)
|
|
- *value = 0x100 * maj + 0x10 * min;
|
|
- else if (type == gestaltSystemVersionMajor)
|
|
- *value = maj;
|
|
- else if (type == gestaltSystemVersionMinor)
|
|
- *value = min;
|
|
- else if (type == gestaltSystemVersionPatch)
|
|
- *value = patch;
|
|
- }
|
|
+ // Darwin's kernel release is not the macOS product version.
|
|
+ char version[64] = {};
|
|
+ size_t version_len = sizeof(version);
|
|
+ int major = 0, minor = 0, patch = 0;
|
|
+ if (sysctlbyname("kern.osproductversion", version, &version_len, nullptr, 0) != 0)
|
|
+ return gestaltUnknownErr;
|
|
+ version[sizeof(version) - 1] = '\0';
|
|
+ if (sscanf(version, "%d.%d.%d", &major, &minor, &patch) < 1 ||
|
|
+ major < 0 || major > 99 || minor < 0 || patch < 0)
|
|
+ return gestaltUnknownErr;
|
|
+
|
|
+ if (type == gestaltSystemVersion) {
|
|
+ // Legacy packed BCD: minor and bug-fix components saturate at 9.
|
|
+ const int packedMajor = ((major / 10) << 4) | (major % 10);
|
|
+ *value = (packedMajor << 8) | ((minor > 9 ? 9 : minor) << 4) |
|
|
+ (patch > 9 ? 9 : patch);
|
|
+ } else if (type == gestaltSystemVersionMajor)
|
|
+ *value = major;
|
|
+ else if (type == gestaltSystemVersionMinor)
|
|
+ *value = minor;
|
|
+ else
|
|
+ *value = patch;
|
|
+ // Version selectors must not fall through to the architecture case.
|
|
+ return 0;
|
|
+ }
|
|
case gestaltSysArchitecture:
|
|
#ifdef __powerpc__
|
|
*value = gestaltPowerPC;
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/CMakeLists.txt b/src/frameworks/Accelerate/vecLib/vDSP/CMakeLists.txt
|
|
index 6ef701609..9bdcace68 100644
|
|
--- a/src/frameworks/Accelerate/vecLib/vDSP/CMakeLists.txt
|
|
+++ b/src/frameworks/Accelerate/vecLib/vDSP/CMakeLists.txt
|
|
@@ -7,7 +7,11 @@ set(DYLIB_CURRENT_VERSION "671.250.4")
|
|
add_darling_library(vDSP SHARED
|
|
src/vDSP.c
|
|
src/extrema.c
|
|
+ src/arith.c
|
|
+ src/fft.c
|
|
)
|
|
+# WebAudio calls these on the realtime render thread.
|
|
+target_compile_options(vDSP PRIVATE -O2)
|
|
make_fat(vDSP)
|
|
target_link_libraries(vDSP system)
|
|
install(TARGETS vDSP DESTINATION libexec/darling/usr/lib)
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/include/vDSP/vDSP.h b/src/frameworks/Accelerate/vecLib/vDSP/include/vDSP/vDSP.h
|
|
index c6b437c5b..88505eda4 100644
|
|
--- a/src/frameworks/Accelerate/vecLib/vDSP/include/vDSP/vDSP.h
|
|
+++ b/src/frameworks/Accelerate/vecLib/vDSP/include/vDSP/vDSP.h
|
|
@@ -24,6 +24,51 @@
|
|
typedef unsigned long vDSP_Length;
|
|
typedef long vDSP_Stride;
|
|
|
|
+typedef struct DSPComplex {
|
|
+ float real;
|
|
+ float imag;
|
|
+} DSPComplex;
|
|
+
|
|
+typedef struct DSPSplitComplex {
|
|
+ float *realp;
|
|
+ float *imagp;
|
|
+} DSPSplitComplex;
|
|
+
|
|
+typedef int FFTDirection;
|
|
+typedef int FFTRadix;
|
|
+
|
|
+enum {
|
|
+ kFFTDirection_Forward = 1,
|
|
+ kFFTDirection_Inverse = -1
|
|
+};
|
|
+
|
|
+enum {
|
|
+ kFFTRadix2 = 0,
|
|
+ kFFTRadix3 = 1,
|
|
+ kFFTRadix5 = 2
|
|
+};
|
|
+
|
|
+typedef struct OpaqueFFTSetup *FFTSetup;
|
|
+
|
|
+void vDSP_vadd(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_vsub(const float *__B, vDSP_Stride __IB, const float *__A, vDSP_Stride __IA, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_vmul(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_vsmul(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_vsadd(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_vsma(const float *__A, vDSP_Stride __IA, const float *__B, const float *__C, vDSP_Stride __IC, float *__D, vDSP_Stride __ID, vDSP_Length __N);
|
|
+void vDSP_svesq(const float *__A, vDSP_Stride __IA, float *__C, vDSP_Length __N);
|
|
+void vDSP_vclip(const float *__A, vDSP_Stride __IA, const float *__B, const float *__C, float *__D, vDSP_Stride __ID, vDSP_Length __N);
|
|
+void vDSP_maxmgv(const float *__A, vDSP_Stride __IA, float *__C, vDSP_Length __N);
|
|
+void vDSP_conv(const float *__A, vDSP_Stride __IA, const float *__F, vDSP_Stride __IF, float *__C, vDSP_Stride __IC, vDSP_Length __N, vDSP_Length __P);
|
|
+void vDSP_ctoz(const DSPComplex *__C, vDSP_Stride __IC, const DSPSplitComplex *__Z, vDSP_Stride __IZ, vDSP_Length __N);
|
|
+void vDSP_ztoc(const DSPSplitComplex *__Z, vDSP_Stride __IZ, DSPComplex *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_zvmul(const DSPSplitComplex *__A, vDSP_Stride __IA, const DSPSplitComplex *__B, vDSP_Stride __IB, const DSPSplitComplex *__C, vDSP_Stride __IC, vDSP_Length __N, int __Conjugate);
|
|
+void vDSP_deq22(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+void vDSP_deq22D(const double *__A, vDSP_Stride __IA, const double *__B, double *__C, vDSP_Stride __IC, vDSP_Length __N);
|
|
+FFTSetup vDSP_create_fftsetup(vDSP_Length __Log2n, FFTRadix __Radix);
|
|
+void vDSP_destroy_fftsetup(FFTSetup __setup);
|
|
+void vDSP_fft_zrip(FFTSetup __Setup, const DSPSplitComplex *__C, vDSP_Stride __IC, vDSP_Length __Log2N, FFTDirection __Direction);
|
|
+
|
|
void* vDSP_DCT_CreateSetup(void);
|
|
void* vDSP_DCT_Execute(void);
|
|
void* vDSP_DFT_CreateSetup(void);
|
|
@@ -63,17 +108,11 @@ void* vDSP_biquadm_SetTargetsDouble(void);
|
|
void* vDSP_biquadm_SetTargetsSingle(void);
|
|
void* vDSP_blkman_window(void);
|
|
void* vDSP_blkman_windowD(void);
|
|
-void* vDSP_conv(void);
|
|
void* vDSP_convD(void);
|
|
-void* vDSP_create_fftsetup(void);
|
|
void* vDSP_create_fftsetupD(void);
|
|
-void* vDSP_ctoz(void);
|
|
void* vDSP_ctozD(void);
|
|
-void* vDSP_deq22(void);
|
|
-void* vDSP_deq22D(void);
|
|
void* vDSP_desamp(void);
|
|
void* vDSP_desampD(void);
|
|
-void* vDSP_destroy_fftsetup(void);
|
|
void* vDSP_destroy_fftsetupD(void);
|
|
void* vDSP_distancesq(void);
|
|
void* vDSP_distancesqD(void);
|
|
@@ -117,7 +156,6 @@ void* vDSP_fft_zop(void);
|
|
void* vDSP_fft_zopD(void);
|
|
void* vDSP_fft_zopt(void);
|
|
void* vDSP_fft_zoptD(void);
|
|
-void* vDSP_fft_zrip(void);
|
|
void* vDSP_fft_zripD(void);
|
|
void* vDSP_fft_zript(void);
|
|
void* vDSP_fft_zriptD(void);
|
|
@@ -194,7 +232,6 @@ void* vDSP_sve_svesq(void);
|
|
void* vDSP_sve_svesqD(void);
|
|
void* vDSP_svemg(void);
|
|
void* vDSP_svemgD(void);
|
|
-void* vDSP_svesq(void);
|
|
void* vDSP_svesqD(void);
|
|
void* vDSP_svs(void);
|
|
void* vDSP_svsD(void);
|
|
@@ -203,7 +240,6 @@ void* vDSP_vaamD(void);
|
|
void* vDSP_vabs(void);
|
|
void* vDSP_vabsD(void);
|
|
void* vDSP_vabsi(void);
|
|
-void* vDSP_vadd(void);
|
|
void* vDSP_vaddD(void);
|
|
void* vDSP_vaddi(void);
|
|
void* vDSP_vaddsub(void);
|
|
@@ -216,7 +252,6 @@ void* vDSP_vasm(void);
|
|
void* vDSP_vasmD(void);
|
|
void* vDSP_vavlin(void);
|
|
void* vDSP_vavlinD(void);
|
|
-void* vDSP_vclip(void);
|
|
void* vDSP_vclipD(void);
|
|
void* vDSP_vclipc(void);
|
|
void* vDSP_vclipcD(void);
|
|
@@ -316,7 +351,6 @@ void* vDSP_vmsa(void);
|
|
void* vDSP_vmsaD(void);
|
|
void* vDSP_vmsb(void);
|
|
void* vDSP_vmsbD(void);
|
|
-void* vDSP_vmul(void);
|
|
void* vDSP_vmulD(void);
|
|
void* vDSP_vnabs(void);
|
|
void* vDSP_vnabsD(void);
|
|
@@ -350,7 +384,6 @@ void* vDSP_vrsum(void);
|
|
void* vDSP_vrsumD(void);
|
|
void* vDSP_vrvrs(void);
|
|
void* vDSP_vrvrsD(void);
|
|
-void* vDSP_vsadd(void);
|
|
void* vDSP_vsaddD(void);
|
|
void* vDSP_vsaddi(void);
|
|
void* vDSP_vsbm(void);
|
|
@@ -364,7 +397,6 @@ void* vDSP_vsdivD(void);
|
|
void* vDSP_vsdivi(void);
|
|
void* vDSP_vsimps(void);
|
|
void* vDSP_vsimpsD(void);
|
|
-void* vDSP_vsma(void);
|
|
void* vDSP_vsmaD(void);
|
|
void* vDSP_vsmfix24(void);
|
|
void* vDSP_vsmfixu24(void);
|
|
@@ -374,7 +406,6 @@ void* vDSP_vsmsb(void);
|
|
void* vDSP_vsmsbD(void);
|
|
void* vDSP_vsmsma(void);
|
|
void* vDSP_vsmsmaD(void);
|
|
-void* vDSP_vsmul(void);
|
|
void* vDSP_vsmulD(void);
|
|
void* vDSP_vsort(void);
|
|
void* vDSP_vsortD(void);
|
|
@@ -385,7 +416,6 @@ void* vDSP_vsq(void);
|
|
void* vDSP_vsqD(void);
|
|
void* vDSP_vssq(void);
|
|
void* vDSP_vssqD(void);
|
|
-void* vDSP_vsub(void);
|
|
void* vDSP_vsubD(void);
|
|
void* vDSP_vswap(void);
|
|
void* vDSP_vswapD(void);
|
|
@@ -439,7 +469,6 @@ void* vDSP_zrvmul(void);
|
|
void* vDSP_zrvmulD(void);
|
|
void* vDSP_zrvsub(void);
|
|
void* vDSP_zrvsubD(void);
|
|
-void* vDSP_ztoc(void);
|
|
void* vDSP_ztocD(void);
|
|
void* vDSP_ztrans(void);
|
|
void* vDSP_ztransD(void);
|
|
@@ -467,7 +496,6 @@ void* vDSP_zvmmaa(void);
|
|
void* vDSP_zvmmaaD(void);
|
|
void* vDSP_zvmov(void);
|
|
void* vDSP_zvmovD(void);
|
|
-void* vDSP_zvmul(void);
|
|
void* vDSP_zvmulD(void);
|
|
void* vDSP_zvneg(void);
|
|
void* vDSP_zvnegD(void);
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/src/arith.c b/src/frameworks/Accelerate/vecLib/vDSP/src/arith.c
|
|
new file mode 100644
|
|
index 000000000..52550fb96
|
|
--- /dev/null
|
|
+++ b/src/frameworks/Accelerate/vecLib/vDSP/src/arith.c
|
|
@@ -0,0 +1,226 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling Developers
|
|
+
|
|
+ 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 <vDSP/vDSP.h>
|
|
+
|
|
+void vDSP_vadd(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ if (__IA == 1 && __IB == 1 && __IC == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i] = __A[i] + __B[i];
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i * __IC] = __A[i * __IA] + __B[i * __IB];
|
|
+ }
|
|
+}
|
|
+
|
|
+// Apple's argument order: the subtrahend B comes first, C = A - B.
|
|
+void vDSP_vsub(const float *__B, vDSP_Stride __IB, const float *__A, vDSP_Stride __IA, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ if (__IA == 1 && __IB == 1 && __IC == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i] = __A[i] - __B[i];
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i * __IC] = __A[i * __IA] - __B[i * __IB];
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_vmul(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ if (__IA == 1 && __IB == 1 && __IC == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i] = __A[i] * __B[i];
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i * __IC] = __A[i * __IA] * __B[i * __IB];
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_vsmul(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ const float b = *__B;
|
|
+
|
|
+ if (__IA == 1 && __IC == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i] = __A[i] * b;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i * __IC] = __A[i * __IA] * b;
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_vsadd(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ const float b = *__B;
|
|
+
|
|
+ if (__IA == 1 && __IC == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i] = __A[i] + b;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __C[i * __IC] = __A[i * __IA] + b;
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_vsma(const float *__A, vDSP_Stride __IA, const float *__B, const float *__C, vDSP_Stride __IC, float *__D, vDSP_Stride __ID, vDSP_Length __N)
|
|
+{
|
|
+ const float b = *__B;
|
|
+
|
|
+ if (__IA == 1 && __IC == 1 && __ID == 1)
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __D[i] = __A[i] * b + __C[i];
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ __D[i * __ID] = __A[i * __IA] * b + __C[i * __IC];
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_svesq(const float *__A, vDSP_Stride __IA, float *__C, vDSP_Length __N)
|
|
+{
|
|
+ float sum = 0;
|
|
+
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ sum += __A[i * __IA] * __A[i * __IA];
|
|
+ *__C = sum;
|
|
+}
|
|
+
|
|
+void vDSP_vclip(const float *__A, vDSP_Stride __IA, const float *__B, const float *__C, float *__D, vDSP_Stride __ID, vDSP_Length __N)
|
|
+{
|
|
+ const float low = *__B, high = *__C;
|
|
+
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ {
|
|
+ float v = __A[i * __IA];
|
|
+ if (v < low)
|
|
+ v = low;
|
|
+ else if (v > high)
|
|
+ v = high;
|
|
+ __D[i * __ID] = v;
|
|
+ }
|
|
+}
|
|
+
|
|
+// Correlation, or convolution when F points at the filter's last tap with a negative stride.
|
|
+void vDSP_conv(const float *__A, vDSP_Stride __IA, const float *__F, vDSP_Stride __IF, float *__C, vDSP_Stride __IC, vDSP_Length __N, vDSP_Length __P)
|
|
+{
|
|
+ for (vDSP_Length n = 0; n < __N; n++)
|
|
+ {
|
|
+ const float *a = __A + n * __IA;
|
|
+ float sum = 0;
|
|
+
|
|
+ for (vDSP_Length p = 0; p < __P; p++)
|
|
+ sum += a[p * __IA] * __F[p * __IF];
|
|
+ __C[n * __IC] = sum;
|
|
+ }
|
|
+}
|
|
+
|
|
+// IC counts floats, so 2 is a contiguous DSPComplex array.
|
|
+void vDSP_ctoz(const DSPComplex *__C, vDSP_Stride __IC, const DSPSplitComplex *__Z, vDSP_Stride __IZ, vDSP_Length __N)
|
|
+{
|
|
+ const float *c = (const float *) __C;
|
|
+
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ {
|
|
+ __Z->realp[i * __IZ] = c[i * __IC];
|
|
+ __Z->imagp[i * __IZ] = c[i * __IC + 1];
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_ztoc(const DSPSplitComplex *__Z, vDSP_Stride __IZ, DSPComplex *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ float *c = (float *) __C;
|
|
+
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ {
|
|
+ c[i * __IC] = __Z->realp[i * __IZ];
|
|
+ c[i * __IC + 1] = __Z->imagp[i * __IZ];
|
|
+ }
|
|
+}
|
|
+
|
|
+// C may alias A or B. Conjugate is 1 for A * B and -1 for conj(A) * B.
|
|
+void vDSP_zvmul(const DSPSplitComplex *__A, vDSP_Stride __IA, const DSPSplitComplex *__B, vDSP_Stride __IB, const DSPSplitComplex *__C, vDSP_Stride __IC, vDSP_Length __N, int __Conjugate)
|
|
+{
|
|
+ const float sign = __Conjugate == -1 ? -1.0f : 1.0f;
|
|
+
|
|
+ for (vDSP_Length i = 0; i < __N; i++)
|
|
+ {
|
|
+ const float ar = __A->realp[i * __IA], ai = sign * __A->imagp[i * __IA];
|
|
+ const float br = __B->realp[i * __IB], bi = __B->imagp[i * __IB];
|
|
+
|
|
+ __C->realp[i * __IC] = ar * br - ai * bi;
|
|
+ __C->imagp[i * __IC] = ar * bi + ai * br;
|
|
+ }
|
|
+}
|
|
+
|
|
+// Two-pole two-zero filter. A and C hold N + 2 elements; C[0] and C[1] are the initial outputs.
|
|
+void vDSP_deq22D(const double *__A, vDSP_Stride __IA, const double *__B, double *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ const double b0 = __B[0], b1 = __B[1], b2 = __B[2], a1 = __B[3], a2 = __B[4];
|
|
+ double x1 = __A[__IA], x2 = __A[0];
|
|
+ double y1 = __C[__IC], y2 = __C[0];
|
|
+
|
|
+ for (vDSP_Length n = 2; n < __N + 2; n++)
|
|
+ {
|
|
+ const double x = __A[n * __IA];
|
|
+ const double y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
|
+
|
|
+ __C[n * __IC] = y;
|
|
+ x2 = x1;
|
|
+ x1 = x;
|
|
+ y2 = y1;
|
|
+ y1 = y;
|
|
+ }
|
|
+}
|
|
+
|
|
+void vDSP_deq22(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
+{
|
|
+ const float b0 = __B[0], b1 = __B[1], b2 = __B[2], a1 = __B[3], a2 = __B[4];
|
|
+ float x1 = __A[__IA], x2 = __A[0];
|
|
+ float y1 = __C[__IC], y2 = __C[0];
|
|
+
|
|
+ for (vDSP_Length n = 2; n < __N + 2; n++)
|
|
+ {
|
|
+ const float x = __A[n * __IA];
|
|
+ const float y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
|
+
|
|
+ __C[n * __IC] = y;
|
|
+ x2 = x1;
|
|
+ x1 = x;
|
|
+ y2 = y1;
|
|
+ y1 = y;
|
|
+ }
|
|
+}
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/src/basic.c b/src/frameworks/Accelerate/vecLib/vDSP/src/basic.c
|
|
deleted file mode 100644
|
|
index ecdd15b23..000000000
|
|
--- a/src/frameworks/Accelerate/vecLib/vDSP/src/basic.c
|
|
+++ /dev/null
|
|
@@ -1,36 +0,0 @@
|
|
-/*
|
|
- This file is part of Darling.
|
|
-
|
|
- Copyright (C) 2020 Lubos Dolezel
|
|
-
|
|
- 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 <vDSP/vDSP.h>
|
|
-
|
|
-void vDSP_vsadd(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N)
|
|
-{
|
|
- if (__IA == 1 && __IC == 1)
|
|
- {
|
|
- #pragma clang loop vectorize(enable)
|
|
- for (vDSP_Length i = 0; i < __N; i++)
|
|
- __C[i] = __A[i] + __B[i];
|
|
- }
|
|
- else
|
|
- {
|
|
- #pragma clang loop vectorize(enable)
|
|
- for (vDSP_Length i = 0; i < __N; i++)
|
|
- __C[i * __IC] = __A[i * __IA] + __B[i * __IA];
|
|
- }
|
|
-}
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/src/fft.c b/src/frameworks/Accelerate/vecLib/vDSP/src/fft.c
|
|
new file mode 100644
|
|
index 000000000..92873d796
|
|
--- /dev/null
|
|
+++ b/src/frameworks/Accelerate/vecLib/vDSP/src/fft.c
|
|
@@ -0,0 +1,162 @@
|
|
+/*
|
|
+ This file is part of Darling.
|
|
+
|
|
+ Copyright (C) 2026 Darling Developers
|
|
+
|
|
+ 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 <vDSP/vDSP.h>
|
|
+#include <stdlib.h>
|
|
+#include <math.h>
|
|
+
|
|
+// Setups are shared between threads, so only read-only twiddles live here.
|
|
+struct OpaqueFFTSetup
|
|
+{
|
|
+ vDSP_Length log2n;
|
|
+ float *cosine;
|
|
+ float *sine;
|
|
+};
|
|
+
|
|
+FFTSetup vDSP_create_fftsetup(vDSP_Length __Log2n, FFTRadix __Radix)
|
|
+{
|
|
+ if (__Radix != kFFTRadix2 || __Log2n > 30)
|
|
+ return NULL;
|
|
+
|
|
+ const size_t n = (size_t) 1 << __Log2n;
|
|
+ const size_t half = n > 1 ? n / 2 : 1;
|
|
+ struct OpaqueFFTSetup *setup = malloc(sizeof(*setup) + 2 * half * sizeof(float));
|
|
+
|
|
+ if (!setup)
|
|
+ return NULL;
|
|
+ setup->log2n = __Log2n;
|
|
+ setup->cosine = (float *) (setup + 1);
|
|
+ setup->sine = setup->cosine + half;
|
|
+ for (size_t j = 0; j < half; j++)
|
|
+ {
|
|
+ const double angle = 2.0 * M_PI * (double) j / (double) n;
|
|
+ setup->cosine[j] = cos(angle);
|
|
+ setup->sine[j] = sin(angle);
|
|
+ }
|
|
+ return setup;
|
|
+}
|
|
+
|
|
+void vDSP_destroy_fftsetup(FFTSetup __setup)
|
|
+{
|
|
+ free(__setup);
|
|
+}
|
|
+
|
|
+// In-place radix-2 complex FFT of 2^log2m points, e^-i for forward and e^+i for inverse, unscaled.
|
|
+static void complex_fft(const struct OpaqueFFTSetup *setup, float *re, float *im, vDSP_Stride stride, vDSP_Length log2m, int inverse)
|
|
+{
|
|
+ const size_t m = (size_t) 1 << log2m;
|
|
+ const size_t full = (size_t) 1 << setup->log2n;
|
|
+
|
|
+ for (size_t i = 1, j = 0; i < m; i++)
|
|
+ {
|
|
+ size_t bit = m >> 1;
|
|
+ for (; j & bit; bit >>= 1)
|
|
+ j ^= bit;
|
|
+ j ^= bit;
|
|
+ if (i < j)
|
|
+ {
|
|
+ float t = re[i * stride];
|
|
+ re[i * stride] = re[j * stride];
|
|
+ re[j * stride] = t;
|
|
+ t = im[i * stride];
|
|
+ im[i * stride] = im[j * stride];
|
|
+ im[j * stride] = t;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (size_t len = 2; len <= m; len <<= 1)
|
|
+ {
|
|
+ const size_t half = len >> 1;
|
|
+ const size_t step = full / len;
|
|
+
|
|
+ for (size_t base = 0; base < m; base += len)
|
|
+ {
|
|
+ for (size_t k = 0; k < half; k++)
|
|
+ {
|
|
+ const float wr = setup->cosine[k * step];
|
|
+ const float wi = inverse ? setup->sine[k * step] : -setup->sine[k * step];
|
|
+ const size_t a = (base + k) * stride, b = (base + k + half) * stride;
|
|
+ const float tr = re[b] * wr - im[b] * wi;
|
|
+ const float ti = re[b] * wi + im[b] * wr;
|
|
+
|
|
+ re[b] = re[a] - tr;
|
|
+ im[b] = im[a] - ti;
|
|
+ re[a] += tr;
|
|
+ im[a] += ti;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+// Real FFT on N = 2^Log2N points packed as N/2 complex values (even samples real, odd imaginary).
|
|
+// Forward returns twice the DFT with X[0] in realp[0] and X[N/2] in imagp[0]; inverse is unscaled,
|
|
+// so a round trip multiplies by 2N, as on macOS.
|
|
+void vDSP_fft_zrip(FFTSetup __Setup, const DSPSplitComplex *__C, vDSP_Stride __IC, vDSP_Length __Log2N, FFTDirection __Direction)
|
|
+{
|
|
+ if (!__Setup || __Log2N == 0 || __Log2N > __Setup->log2n)
|
|
+ return;
|
|
+
|
|
+ float *re = __C->realp, *im = __C->imagp;
|
|
+ const vDSP_Stride s = __IC;
|
|
+ const size_t m = (size_t) 1 << (__Log2N - 1);
|
|
+ const size_t step = ((size_t) 1 << __Setup->log2n) >> __Log2N;
|
|
+
|
|
+ if (__Direction != kFFTDirection_Inverse)
|
|
+ {
|
|
+ complex_fft(__Setup, re, im, s, __Log2N - 1, 0);
|
|
+
|
|
+ const float z0r = re[0], z0i = im[0];
|
|
+ re[0] = 2 * (z0r + z0i);
|
|
+ im[0] = 2 * (z0r - z0i);
|
|
+
|
|
+ for (size_t k = 1; k <= m / 2; k++)
|
|
+ {
|
|
+ const size_t j = m - k;
|
|
+ const float c = __Setup->cosine[k * step], sn = __Setup->sine[k * step];
|
|
+ const float ar = re[k * s], ai = im[k * s], br = re[j * s], bi = im[j * s];
|
|
+ const float sr = ar + br, si = ai - bi, dr = ar - br, di = ai + bi;
|
|
+
|
|
+ re[k * s] = sr + (c * di - sn * dr);
|
|
+ im[k * s] = si - (c * dr + sn * di);
|
|
+ re[j * s] = sr - (c * di - sn * dr);
|
|
+ im[j * s] = -si - (c * dr + sn * di);
|
|
+ }
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ const float p0r = re[0], p0i = im[0];
|
|
+ re[0] = p0r + p0i;
|
|
+ im[0] = p0r - p0i;
|
|
+
|
|
+ for (size_t k = 1; k <= m / 2; k++)
|
|
+ {
|
|
+ const size_t j = m - k;
|
|
+ const float c = __Setup->cosine[k * step], sn = __Setup->sine[k * step];
|
|
+ const float ar = re[k * s], ai = im[k * s], br = re[j * s], bi = im[j * s];
|
|
+ const float sr = ar + br, si = ai - bi, dr = ar - br, di = ai + bi;
|
|
+
|
|
+ re[k * s] = sr - (c * di + sn * dr);
|
|
+ im[k * s] = si + (c * dr - sn * di);
|
|
+ re[j * s] = sr + (c * di + sn * dr);
|
|
+ im[j * s] = -si + (c * dr - sn * di);
|
|
+ }
|
|
+
|
|
+ complex_fft(__Setup, re, im, s, __Log2N - 1, 1);
|
|
+ }
|
|
+}
|
|
diff --git a/src/frameworks/Accelerate/vecLib/vDSP/src/vDSP.c b/src/frameworks/Accelerate/vecLib/vDSP/src/vDSP.c
|
|
index 24c037abc..cc18546ce 100644
|
|
--- a/src/frameworks/Accelerate/vecLib/vDSP/src/vDSP.c
|
|
+++ b/src/frameworks/Accelerate/vecLib/vDSP/src/vDSP.c
|
|
@@ -263,54 +263,24 @@ void* vDSP_blkman_windowD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_conv(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_conv called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_convD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_convD called");
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_create_fftsetup(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_create_fftsetup called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_create_fftsetupD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_create_fftsetupD called");
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_ctoz(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_ctoz called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_ctozD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_ctozD called");
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_deq22(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_deq22 called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
-void* vDSP_deq22D(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_deq22D called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_desamp(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_desamp called");
|
|
@@ -323,12 +293,6 @@ void* vDSP_desampD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_destroy_fftsetup(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_destroy_fftsetup called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_destroy_fftsetupD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_destroy_fftsetupD called");
|
|
@@ -587,12 +551,6 @@ void* vDSP_fft_zoptD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_fft_zrip(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_fft_zrip called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_fft_zripD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_fft_zripD called");
|
|
@@ -1057,12 +1015,6 @@ void* vDSP_svemgD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_svesq(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_svesq called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_svesqD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_svesqD called");
|
|
@@ -1111,12 +1063,6 @@ void* vDSP_vabsi(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vadd(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vadd called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vaddD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vaddD called");
|
|
@@ -1189,12 +1135,6 @@ void* vDSP_vavlinD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vclip(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vclip called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vclipD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vclipD called");
|
|
@@ -1789,12 +1729,6 @@ void* vDSP_vmsbD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vmul(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vmul called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vmulD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vmulD called");
|
|
@@ -1993,14 +1927,6 @@ void* vDSP_vrvrsD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-/*
|
|
-void* vDSP_vsadd(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vsadd called");
|
|
- return NULL;
|
|
-}
|
|
-*/
|
|
-
|
|
void* vDSP_vsaddD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vsaddD called");
|
|
@@ -2079,12 +2005,6 @@ void* vDSP_vsimpsD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vsma(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vsma called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vsmaD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vsmaD called");
|
|
@@ -2139,12 +2059,6 @@ void* vDSP_vsmsmaD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vsmul(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vsmul called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vsmulD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vsmulD called");
|
|
@@ -2205,12 +2119,6 @@ void* vDSP_vssqD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_vsub(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_vsub called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_vsubD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_vsubD called");
|
|
@@ -2529,12 +2437,6 @@ void* vDSP_zrvsubD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_ztoc(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_ztoc called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_ztocD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_ztocD called");
|
|
@@ -2697,12 +2599,6 @@ void* vDSP_zvmovD(void)
|
|
return NULL;
|
|
}
|
|
|
|
-void* vDSP_zvmul(void)
|
|
-{
|
|
- if (verbose) puts("STUB: vDSP_zvmul called");
|
|
- return NULL;
|
|
-}
|
|
-
|
|
void* vDSP_zvmulD(void)
|
|
{
|
|
if (verbose) puts("STUB: vDSP_zvmulD called");
|
|
diff --git a/tools/shutdown-user.sh b/tools/shutdown-user.sh
|
|
index 072a6e2..c804100 100755
|
|
--- a/tools/shutdown-user.sh
|
|
+++ b/tools/shutdown-user.sh
|
|
@@ -1,26 +1,40 @@
|
|
#!/bin/sh
|
|
|
|
-if [ "$(whoami)" != "root" ]
|
|
-then
|
|
- echo "$0 must be run as root, invoking sudo"
|
|
- exec sudo su -c "$0" "$@"
|
|
+# Rootless installs own only this workspace/prefix, never all launchd users.
|
|
+if [ -n "${DARLING_WORKSPACE:-}" ]; then
|
|
+ : "${DPREFIX:?rootless install requires an explicit prefix}"
|
|
+ exec python3 "$DARLING_WORKSPACE/scripts/nix-idle.py"
|
|
+fi
|
|
+
|
|
+if [ "$(whoami)" != "root" ]; then
|
|
+ echo "$0 must be run as root, invoking sudo"
|
|
+ exec sudo sh "$0" "$@"
|
|
fi
|
|
|
|
echo "Seeing if Darling is currently running"
|
|
|
|
-# Sometimes `pgrep launchd` doesn't work when `pgrep -f launchd` does. Not sure
|
|
-# if we want to include arguments (and capture rare, but possible false
|
|
-# positives).
|
|
-PID=$(pgrep -f launchd)
|
|
+running_pids() {
|
|
+ # Match the guest init itself, not commands mentioning launchd. Zombies
|
|
+ # cannot respond to shutdown and must be reaped by their parent.
|
|
+ ps -eo pid=,stat=,args= | awk '$2 !~ /^Z/ && $3 == "/sbin/launchd" { print $1 }'
|
|
+}
|
|
|
|
-while [ -n "$PID" ]
|
|
-do
|
|
- THISPID=$(echo $PID | head -n1)
|
|
- RUNNING_USER=$(ps -o uname= -p $THISPID | head -n1)
|
|
- echo "Darling currently running for $RUNNING_USER, shutting it down..."
|
|
- su --login "$RUNNING_USER" -c "darling shutdown"
|
|
- sleep 2
|
|
- PID=$(pgrep -f launchd)
|
|
+attempts=0
|
|
+while :; do
|
|
+ PID=$(running_pids | head -n1)
|
|
+ [ -n "$PID" ] || break
|
|
+ if [ "$attempts" -ge 5 ]; then
|
|
+ echo "Darling launchd PID $PID remains alive after shutdown; refusing to continue installation. Check for an orphaned container or a custom DPREFIX." >&2
|
|
+ exit 1
|
|
+ fi
|
|
+ RUNNING_USER=$(ps -o uname= -p "$PID" | head -n1 | tr -d '[:space:]')
|
|
+ # The process may have exited between enumeration and the owner query.
|
|
+ if [ -n "$RUNNING_USER" ]; then
|
|
+ echo "Darling currently running for $RUNNING_USER, shutting it down..."
|
|
+ timeout 15s su --login "$RUNNING_USER" -c "darling shutdown" || true
|
|
+ fi
|
|
+ attempts=$((attempts + 1))
|
|
+ sleep 2
|
|
done
|
|
|
|
echo "No instances running now"
|
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -204,8 +204,9 @@
|
|
InstallSymlink(private/etc ${CMAKE_INSTALL_PREFIX}/libexec/darling/etc)
|
|
InstallSymlink(private/var ${CMAKE_INSTALL_PREFIX}/libexec/darling/var)
|
|
|
|
-install(FILES etc/resolv.conf
|
|
- DESTINATION libexec/darling/private/etc)
|
|
+# Follow the host resolver, including local DNS proxies and network changes.
|
|
+# Prefix-local resolver overrides still take precedence over this base symlink.
|
|
+InstallSymlink(/Volumes/SystemRoot/etc/resolv.conf ${CMAKE_INSTALL_PREFIX}/libexec/darling/private/etc/resolv.conf)
|
|
InstallSymlink(/Volumes/SystemRoot/etc/machine-id ${CMAKE_INSTALL_PREFIX}/libexec/darling/private/etc/machine-id)
|
|
InstallSymlink(/Volumes/SystemRoot/etc/nsswitch.conf ${CMAKE_INSTALL_PREFIX}/libexec/darling/private/etc/nsswitch.conf)
|
|
|
|
@@ -236,7 +237,12 @@
|
|
install(DIRECTORY DESTINATION libexec/darling/Library/Preferences)
|
|
|
|
if(NOT DEBIAN_PACKAGING)
|
|
- install(CODE "execute_process(COMMAND bash ${DARLING_TOP_DIRECTORY}/tools/shutdown-user.sh)")
|
|
+ install(CODE "
|
|
+ execute_process(COMMAND bash \"${DARLING_TOP_DIRECTORY}/tools/shutdown-user.sh\" RESULT_VARIABLE DARLING_SHUTDOWN_RESULT)
|
|
+ if(NOT DARLING_SHUTDOWN_RESULT STREQUAL \"0\")
|
|
+ message(FATAL_ERROR \"Unable to stop old Darling containers; installation stopped\")
|
|
+ endif()
|
|
+ ")
|
|
endif(NOT DEBIAN_PACKAGING)
|
|
|
|
add_custom_target(uninstall
|
|
diff --git a/src/shellspawn/shellspawn-fds.h b/src/shellspawn/shellspawn-fds.h
|
|
new file mode 100644
|
|
index 0000000..1f3c473
|
|
--- /dev/null
|
|
+++ b/src/shellspawn/shellspawn-fds.h
|
|
@@ -0,0 +1,37 @@
|
|
+#ifndef SHELLSPAWN_FDS_H
|
|
+#define SHELLSPAWN_FDS_H
|
|
+#include <errno.h>
|
|
+#include <fcntl.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+// Preserve every source before replacing any standard descriptor: sources can
|
|
+// themselves be 0, 1 or 2 when the daemon was launched with closed stdio.
|
|
+static int shellspawn_remap_stdio(const int streams[3])
|
|
+{
|
|
+ int saved[3] = { -1, -1, -1 };
|
|
+ for (int i = 0; i < 3; ++i) {
|
|
+ saved[i] = fcntl(streams[i], F_DUPFD_CLOEXEC, 3);
|
|
+ if (saved[i] < 0) goto failure;
|
|
+ }
|
|
+ for (int i = 0; i < 3; ++i)
|
|
+ if (dup2(saved[i], i) < 0) goto failure;
|
|
+ for (int i = 0; i < 3; ++i) close(saved[i]);
|
|
+ return 0;
|
|
+failure: {
|
|
+ int error = errno;
|
|
+ for (int i = 0; i < 3; ++i) if (saved[i] >= 0) close(saved[i]);
|
|
+ errno = error;
|
|
+ return -1;
|
|
+}}
|
|
+
|
|
+static void shellspawn_close_streams(int streams[3])
|
|
+{
|
|
+ for (int i = 0; i < 3; ++i) {
|
|
+ int fd = streams[i];
|
|
+ streams[i] = -1;
|
|
+ for (int j = i + 1; j < 3; ++j)
|
|
+ if (streams[j] == fd) streams[j] = -1;
|
|
+ if (fd > STDERR_FILENO) close(fd);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
diff --git a/src/startup/mldr/elfcalls/threads.c b/src/startup/mldr/elfcalls/threads.c
|
|
index e789986..0d56b4e 100644
|
|
--- a/src/startup/mldr/elfcalls/threads.c
|
|
+++ b/src/startup/mldr/elfcalls/threads.c
|
|
@@ -387,8 +387,9 @@ int __darling_thread_rpc_socket(void) {
|
|
// this is the main thread
|
|
t_server_socket = __dserver_main_thread_socket_fd;
|
|
} else {
|
|
- // threads should already have a per-thread socket assigned when they're created
|
|
- abort();
|
|
+ // Native host-library threads do not have a Darling RPC connection.
|
|
+ // Let the caller report EPIPE, including the resize-signal bridge.
|
|
+ return -1;
|
|
}
|
|
}
|
|
return t_server_socket;
|
|
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPAInput.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPAInput.cpp
|
|
index c1fd1d402..777ca7c38 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPAInput.cpp
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareImplPAInput.cpp
|
|
@@ -31,15 +31,18 @@ OSStatus AudioHardwareImplPAInput::getPropertyData(const AudioObjectPropertyAddr
|
|
{
|
|
if (inAddress->mSelector == kAudioDevicePropertyStreamConfiguration)
|
|
{
|
|
+ // A capture device has its channels on the input scope only. Reporting them on the
|
|
+ // output scope (copied from the output device) made capture enumerators such as
|
|
+ // Vivox and Chromium see a playback-only device with no input channels.
|
|
size_t size = sizeof(AudioBufferList);
|
|
|
|
- if (inAddress->mScope == kAudioDevicePropertyScopeOutput)
|
|
+ if (inAddress->mScope == kAudioDevicePropertyScopeInput)
|
|
size += 1* sizeof(AudioBuffer);
|
|
|
|
// Number of returned buffers is the number of channels
|
|
if (AudioBufferList* abl = static_cast<AudioBufferList*>(outData); abl && *ioDataSize >= size)
|
|
{
|
|
- if (inAddress->mScope == kAudioDevicePropertyScopeOutput)
|
|
+ if (inAddress->mScope == kAudioDevicePropertyScopeInput)
|
|
{
|
|
abl->mNumberBuffers = 1;
|
|
abl->mBuffers[0].mNumberChannels = 2;
|
|
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.h b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.h
|
|
index 87db50d12..01a7b2a3e 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.h
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.h
|
|
@@ -21,6 +21,8 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#define AUDIOHARDWARESTREAMPAINPUT_H
|
|
#include "AudioHardwareStreamPA.h"
|
|
#include <fstream>
|
|
+#include <cstdint>
|
|
+#include <vector>
|
|
|
|
class AudioHardwareStreamPAInput : public AudioHardwareStreamPA
|
|
{
|
|
@@ -30,6 +32,12 @@ private:
|
|
void start() override;
|
|
static void paStreamReadCB(pa_stream* s, size_t length, void* self);
|
|
|
|
+ // Only touched on the PA queue.
|
|
+ uint64_t m_frames = 0;
|
|
+ uint64_t m_lastInputHost = 0;
|
|
+ size_t m_blockBytes = 0;
|
|
+ std::vector<uint8_t> m_scratch;
|
|
+
|
|
// std::ofstream m_dump;
|
|
};
|
|
|