mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-27 19:54:38 +00:00
With __GL_THREADED_OPTIMIZATIONS=1, NVIDIA 580 (the last branch for Pascal GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY on every thread that has a context current. A native EGL program reproduces it outside Darling. In the Riot client the main thread loses EGL after its first window frame, so CGLCreateContext fails with error 10004 and the game window stays black. Default the launcher to 0; setting the variable restores 1. CGLDestroyWindow also returns early for the NULL window CGLGetWindow gives when eglCreateWindowSurface fails, instead of passing EGL_NO_SURFACE on. Co-Authored-By: Claude Opus 5.5 <[email protected]>
10728 lines
405 KiB
Diff
10728 lines
405 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..739b70011 100644
|
|
--- a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
|
|
+++ b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAInput.cpp
|
|
@@ -25,6 +25,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
AudioHardwareStreamPAInput::AudioHardwareStreamPAInput(AudioHardwareImplPA* hw, AudioDeviceIOProc callback, void* clientData)
|
|
: AudioHardwareStreamPA(hw, callback, clientData)
|
|
{
|
|
+ initialize();
|
|
// m_dump.open("/tmp/dump.raw", std::ios_base::out | std::ios_base::binary);
|
|
}
|
|
|
|
@@ -32,7 +33,6 @@ void AudioHardwareStreamPAInput::paStreamReadCB(pa_stream* s, size_t length, voi
|
|
{
|
|
AudioHardwareStreamPAInput* This = static_cast<AudioHardwareStreamPAInput*>(self);
|
|
|
|
- std::unique_lock<std::mutex> l(This->m_stopMutex);
|
|
|
|
if (!This->m_running)
|
|
return;
|
|
@@ -74,6 +74,7 @@ void AudioHardwareStreamPAInput::paStreamReadCB(pa_stream* s, size_t length, voi
|
|
// std::cout << "AudioHardwareStreamPAInput::paStreamReadCB(): bytes=" << nbytes << std::endl;
|
|
OSStatus status = This->m_callback(This->m_hw->id(), &fake, abl, &fake, nullptr, nullptr, This->m_clientData);
|
|
|
|
+ if (!This->m_running) return;
|
|
pa_stream_drop(This->m_stream);
|
|
|
|
if (status != noErr)
|
|
diff --git a/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp b/src/CoreAudio/CoreAudio/pulse/AudioHardwareStreamPAOutput.cpp
|
|
index 278630f02..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..9321edcdf 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);
|
|
@@ -42,6 +52,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 +90,7 @@ bool AUHAL::CanScheduleParameters() const
|
|
|
|
bool AUHAL::StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element)
|
|
{
|
|
- return !m_running && IsInitialized();
|
|
+ return !m_running;
|
|
}
|
|
|
|
OSStatus AUHAL::Version()
|
|
@@ -76,19 +117,26 @@ OSStatus AUHAL::Render(AudioUnitRenderActionFlags& ioActionFlags, const AudioTim
|
|
|
|
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)
|
|
{
|
|
@@ -111,20 +159,42 @@ 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: {
|
|
+ AudioObjectPropertyAddress p={inID,kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
|
|
+ return AudioObjectSetPropertyData(m_outputDevice,&p,0,nullptr,inDataSize,inData);
|
|
+ }
|
|
case kAudioOutputUnitProperty_SetInputCallback:
|
|
{
|
|
ca_require(inDataSize == sizeof(AURenderCallbackStruct), InvalidPropertyValue);
|
|
@@ -176,15 +246,23 @@ 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);
|
|
@@ -210,13 +288,22 @@ 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: {
|
|
+ AudioObjectPropertyAddress p={inID,kAudioObjectPropertyScopeOutput,kAudioObjectPropertyElementMaster};
|
|
+ UInt32 size=sizeof(UInt32);
|
|
+ return AudioObjectGetPropertyData(m_outputDevice,&p,0,nullptr,&size,outData);
|
|
+ }
|
|
case kAudioOutputUnitProperty_SetInputCallback:
|
|
{
|
|
memcpy(outData, &m_outputAvailableCb, sizeof(m_outputAvailableCb));
|
|
@@ -228,12 +315,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;
|
|
@@ -277,9 +364,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 +457,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);
|
|
diff --git a/src/CoreAudio/CoreAudioComponent/AUHAL.h b/src/CoreAudio/CoreAudioComponent/AUHAL.h
|
|
index bc21bcbbb..e369f95d1 100644
|
|
--- a/src/CoreAudio/CoreAudioComponent/AUHAL.h
|
|
+++ b/src/CoreAudio/CoreAudioComponent/AUHAL.h
|
|
@@ -25,6 +25,7 @@ along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
#include <mutex>
|
|
#include <memory>
|
|
#include <stdint.h>
|
|
+#include <vector>
|
|
|
|
class AUHAL : public AUBase
|
|
{
|
|
@@ -32,6 +33,9 @@ public:
|
|
AUHAL(AudioComponentInstance inInstance, bool supportRecording = true);
|
|
~AUHAL();
|
|
|
|
+ UInt32 GetChannelLayoutTags(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayoutTag* tags) override;
|
|
+ UInt32 GetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,AudioChannelLayout* layout,Boolean& writable) override;
|
|
+ OSStatus SetAudioChannelLayout(AudioUnitScope scope,AudioUnitElement element,const AudioChannelLayout* layout) override;
|
|
bool CanScheduleParameters() const override;
|
|
bool StreamFormatWritable(AudioUnitScope scope, AudioUnitElement element) override;
|
|
OSStatus Version() override;
|
|
@@ -59,6 +63,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:
|
|
@@ -76,6 +81,24 @@ protected:
|
|
// TODO: Prepare for non-interleaved audio
|
|
std::unique_ptr<uint8_t[]> m_buffer;
|
|
size_t m_bufferSize = 0, m_bufferUsed = 0;
|
|
+
|
|
+ // Playback callbacks run serially on the device's PulseAudio queue.
|
|
+ // Keep planar conversion storage across callbacks and start/stop cycles.
|
|
+ std::vector<unsigned char> m_playbackSamples;
|
|
+ std::vector<unsigned char> m_playbackBufferList;
|
|
+
|
|
+ // 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 Vanguard module went to its
|
|
+ * error state (MvgState 5, status -101) on every launch (2 of 2, 2026-10-11) and the client could not start a match;
|
|
+ * with the old behaviour it connected again (state 0). */
|
|
+static bool callerIsLeagueGame()
|
|
+{
|
|
+ static const bool game = []{
|
|
+ const char* name = getprogname();
|
|
+ return name && strcmp(name, "LeagueofLegends") == 0;
|
|
+ }();
|
|
+ return game;
|
|
+}
|
|
+
|
|
OSStatus DefaultOutputAU::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32& outDataSize, Boolean& outWritable)
|
|
{
|
|
OSStatus status = AUHAL::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable);
|
|
|
|
- if (inID == kAudioOutputUnitProperty_CurrentDevice)
|
|
+ if (inID == kAudioOutputUnitProperty_CurrentDevice && !callerIsLeagueGame())
|
|
outWritable = false;
|
|
|
|
return status;
|
|
@@ -48,7 +66,9 @@ OSStatus DefaultOutputAU::SetProperty(AudioUnitPropertyID inID, AudioUnitScope i
|
|
switch (inID)
|
|
{
|
|
case kAudioOutputUnitProperty_CurrentDevice:
|
|
- return kAudioUnitErr_InvalidProperty;
|
|
+ if (!callerIsLeagueGame())
|
|
+ return kAudioUnitErr_InvalidProperty;
|
|
+ break;
|
|
}
|
|
return AUHAL::SetProperty(inID, inScope, inElement, inData, inDataSize);
|
|
}
|
|
diff --git a/src/CoreAudio/CoreAudioComponent/Info.plist b/src/CoreAudio/CoreAudioComponent/Info.plist
|
|
index da44a9f18..f658118d8 100644
|
|
--- a/src/CoreAudio/CoreAudioComponent/Info.plist
|
|
+++ b/src/CoreAudio/CoreAudioComponent/Info.plist
|
|
@@ -70,6 +70,24 @@
|
|
<key>version</key>
|
|
<integer>1</integer>
|
|
</dict>
|
|
+ <dict>
|
|
+ <key>description</key>
|
|
+ <string>Float PCM matrix mixer</string>
|
|
+ <key>factoryFunction</key>
|
|
+ <string>MatrixMixerAUFactory</string>
|
|
+ <key>manufacturer</key>
|
|
+ <string>appl</string>
|
|
+ <key>name</key>
|
|
+ <string>Darling: MatrixMixer</string>
|
|
+ <key>sandboxSafe</key>
|
|
+ <true/>
|
|
+ <key>subtype</key>
|
|
+ <string>mxmx</string>
|
|
+ <key>type</key>
|
|
+ <string>aumx</string>
|
|
+ <key>version</key>
|
|
+ <integer>1</integer>
|
|
+ </dict>
|
|
</array>
|
|
<key>CFBundleExecutable</key>
|
|
<string>CoreAudio</string>
|
|
diff --git a/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h b/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
|
|
index 102614203..9b80671a6 100644
|
|
--- a/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
|
|
+++ b/src/CoreAudio/include/CoreAudio/AudioHardwareBase.h
|
|
@@ -13,12 +13,12 @@ typedef UInt32 AudioObjectPropertySelector;
|
|
typedef UInt32 AudioObjectPropertyScope;
|
|
typedef UInt32 AudioObjectPropertyElement;
|
|
|
|
-struct AudioObjectPropertyAddress
|
|
+typedef struct AudioObjectPropertyAddress
|
|
{
|
|
AudioObjectPropertySelector mSelector;
|
|
AudioObjectPropertyScope mScope;
|
|
AudioObjectPropertyElement mElement;
|
|
-};
|
|
+} AudioObjectPropertyAddress;
|
|
|
|
enum
|
|
{
|
|
@@ -87,6 +87,14 @@ enum
|
|
};
|
|
|
|
|
|
+typedef AudioObjectID AudioStreamID;
|
|
+enum {
|
|
+ kAudioStreamPropertyDirection = 'sdir',
|
|
+ kAudioStreamPropertyTerminalType = 'term',
|
|
+ kAudioStreamPropertyVirtualFormat = 'sfmt',
|
|
+ kAudioStreamPropertyPhysicalFormat = 'pft ',
|
|
+};
|
|
+
|
|
// TODO: A lot more is missing
|
|
|
|
#ifdef __cplusplus
|
|
@@ -94,4 +102,3 @@ enum
|
|
#endif
|
|
|
|
#endif /* AUDIOHARDWAREBASE_H */
|
|
-
|
|
diff --git a/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h b/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
|
|
index cb2896957..b2c25fbd4 100644
|
|
--- a/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
|
|
+++ b/src/frameworks/AVFoundation/include/AVFoundation/AVCaptureDevice.h
|
|
@@ -18,7 +18,22 @@
|
|
*/
|
|
|
|
#include <Foundation/Foundation.h>
|
|
+#include <AVFoundation/AVMediaFormat.h>
|
|
+
|
|
+typedef NS_ENUM(NSInteger, AVAuthorizationStatus) {
|
|
+ AVAuthorizationStatusNotDetermined = 0,
|
|
+ AVAuthorizationStatusRestricted = 1,
|
|
+ AVAuthorizationStatusDenied = 2,
|
|
+ AVAuthorizationStatusAuthorized = 3,
|
|
+};
|
|
|
|
@interface AVCaptureDevice : NSObject
|
|
|
|
++ (NSArray *)devices;
|
|
++ (NSArray *)devicesWithMediaType:(AVMediaType)mediaType;
|
|
++ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType;
|
|
+
|
|
++ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType;
|
|
++ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler;
|
|
+
|
|
@end
|
|
diff --git a/src/frameworks/AVFoundation/src/AVCaptureDevice.m b/src/frameworks/AVFoundation/src/AVCaptureDevice.m
|
|
index cab525b91..2fcab003d 100644
|
|
--- a/src/frameworks/AVFoundation/src/AVCaptureDevice.m
|
|
+++ b/src/frameworks/AVFoundation/src/AVCaptureDevice.m
|
|
@@ -21,6 +21,37 @@
|
|
|
|
@implementation AVCaptureDevice
|
|
|
|
++ (NSArray *)devices {
|
|
+ return [NSArray array];
|
|
+}
|
|
+
|
|
++ (NSArray *)devicesWithMediaType:(AVMediaType)mediaType {
|
|
+ (void)mediaType;
|
|
+ return [self devices];
|
|
+}
|
|
+
|
|
++ (AVCaptureDevice *)defaultDeviceWithMediaType:(AVMediaType)mediaType {
|
|
+ (void)mediaType;
|
|
+ return nil;
|
|
+}
|
|
+
|
|
+
|
|
++ (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType
|
|
+{
|
|
+ // Darling has no camera/microphone capture or TCC consent store;
|
|
+ // report Denied so callers take their no-access path instead of
|
|
+ // raising an unrecognized-selector exception.
|
|
+ (void)mediaType;
|
|
+ return AVAuthorizationStatusDenied;
|
|
+}
|
|
+
|
|
++ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL))handler
|
|
+{
|
|
+ (void)mediaType;
|
|
+ if (handler)
|
|
+ handler(NO);
|
|
+}
|
|
+
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
|
|
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
}
|
|
diff --git a/src/frameworks/Carbon/HIToolbox/CMakeLists.txt b/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
|
|
index 3f0820484..7d310f61c 100644
|
|
--- a/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
|
|
+++ b/src/frameworks/Carbon/HIToolbox/CMakeLists.txt
|
|
@@ -19,6 +19,7 @@ add_framework(HIToolbox
|
|
|
|
SOURCES
|
|
src/Keyboards.cpp
|
|
+ src/SecureInput.c
|
|
src/Events.cpp
|
|
src/CarbonEventsCore.c
|
|
src/CarbonEvents.c
|
|
diff --git a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
|
|
index 8c37ce2a6..71ed125a5 100644
|
|
--- a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
|
|
+++ b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/Events.h
|
|
@@ -19,6 +19,7 @@ OSStatus SetEventParameter(EventRef a, EventParamName b, EventParamType c, UInt3
|
|
|
|
|
|
extern UInt8 LMGetKbdType(void);
|
|
+extern UInt8 LMGetKbdLast(void);
|
|
|
|
enum {
|
|
kVK_ANSI_A = 0x00,
|
|
diff --git a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
|
|
index 8d457c216..4b42c9af8 100644
|
|
--- a/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
|
|
+++ b/src/frameworks/Carbon/HIToolbox/include/HIToolbox/TextInputSources.h
|
|
@@ -1,6 +1,7 @@
|
|
#ifndef _Carbon_TextInputSources_H_
|
|
#define _Carbon_TextInputSources_H_
|
|
#include <CoreFoundation/CFString.h>
|
|
+#include <CoreFoundation/CFArray.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
@@ -25,6 +26,7 @@ extern TISInputSourceRef TISCopyCurrentKeyboardInputSource(void);
|
|
extern void* TISGetInputSourceProperty(TISInputSourceRef inputSourceRef, CFStringRef key);
|
|
|
|
extern TISInputSourceRef TISCopyCurrentASCIICapableKeyboardLayoutInputSource(void);
|
|
+extern CFArrayRef TISCreateASCIICapableInputSourceList(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
diff --git a/src/frameworks/Carbon/HIToolbox/src/Events.cpp b/src/frameworks/Carbon/HIToolbox/src/Events.cpp
|
|
index a37fc641f..98e29c97a 100644
|
|
--- a/src/frameworks/Carbon/HIToolbox/src/Events.cpp
|
|
+++ b/src/frameworks/Carbon/HIToolbox/src/Events.cpp
|
|
@@ -4,3 +4,10 @@ UInt8 LMGetKbdType(void)
|
|
{
|
|
return 0;
|
|
}
|
|
+
|
|
+UInt8 LMGetKbdLast(void)
|
|
+{
|
|
+ // Darling exposes one logical keyboard through its current XKB layout.
|
|
+ // Its last-used keyboard type is therefore the current keyboard type.
|
|
+ return LMGetKbdType();
|
|
+}
|
|
diff --git a/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm b/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
|
|
index 347cb530c..f73278a35 100644
|
|
--- a/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
|
|
+++ b/src/frameworks/Carbon/HIToolbox/src/TextInputSources.mm
|
|
@@ -85,3 +85,10 @@ TISInputSourceRef TISCopyCurrentASCIICapableKeyboardLayoutInputSource(void)
|
|
|
|
return NULL;
|
|
}
|
|
+
|
|
+CFArrayRef TISCreateASCIICapableInputSourceList(void)
|
|
+{
|
|
+ /* Until ASCII-capable layouts can be enumerated, return an owned empty
|
|
+ * list. A NULL list would crash callers that unconditionally CFRelease. */
|
|
+ return CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
|
|
+}
|
|
diff --git a/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h b/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
|
|
index 090de1df2..fe1f95f3b 100644
|
|
--- a/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
|
|
+++ b/src/frameworks/CoreLocation/include/CoreLocation/CLLocation.h
|
|
@@ -19,6 +19,15 @@
|
|
|
|
#include <Foundation/Foundation.h>
|
|
|
|
+typedef double CLLocationAccuracy;
|
|
+
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyBestForNavigation;
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyBest;
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
|
|
+extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;
|
|
+
|
|
@interface CLLocation : NSObject
|
|
|
|
@end
|
|
diff --git a/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h b/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
|
|
index 9e0fa55c4..993124e76 100644
|
|
--- a/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
|
|
+++ b/src/frameworks/CoreLocation/include/CoreLocation/CLLocationManager.h
|
|
@@ -20,5 +20,14 @@
|
|
#include <Foundation/Foundation.h>
|
|
|
|
@interface CLLocationManager : NSObject
|
|
+{
|
|
+ id _delegate;
|
|
+}
|
|
+
|
|
+/* darling-lol: real property so callers (Electron's device-permission
|
|
+ * path) get a proper accessor instead of falling through to the stub
|
|
+ * forwardInvocation, whose 0-arg signature raised
|
|
+ * `NSForwardSignatureError: invoked with 3 args, but 2 expected`. */
|
|
+@property (nonatomic, assign) id delegate;
|
|
|
|
@end
|
|
diff --git a/src/frameworks/CoreLocation/src/CLLocation.m b/src/frameworks/CoreLocation/src/CLLocation.m
|
|
index f396e00e4..5f3965705 100644
|
|
--- a/src/frameworks/CoreLocation/src/CLLocation.m
|
|
+++ b/src/frameworks/CoreLocation/src/CLLocation.m
|
|
@@ -32,3 +32,10 @@
|
|
}
|
|
|
|
@end
|
|
+
|
|
+const CLLocationAccuracy kCLLocationAccuracyBestForNavigation = -2.0;
|
|
+const CLLocationAccuracy kCLLocationAccuracyBest = -1.0;
|
|
+const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters = 10.0;
|
|
+const CLLocationAccuracy kCLLocationAccuracyHundredMeters = 100.0;
|
|
+const CLLocationAccuracy kCLLocationAccuracyKilometer = 1000.0;
|
|
+const CLLocationAccuracy kCLLocationAccuracyThreeKilometers = 3000.0;
|
|
diff --git a/src/frameworks/CoreLocation/src/CLLocationManager.m b/src/frameworks/CoreLocation/src/CLLocationManager.m
|
|
index 0b927c40e..d186494e7 100644
|
|
--- a/src/frameworks/CoreLocation/src/CLLocationManager.m
|
|
+++ b/src/frameworks/CoreLocation/src/CLLocationManager.m
|
|
@@ -21,6 +21,8 @@
|
|
|
|
@implementation CLLocationManager
|
|
|
|
+@synthesize delegate = _delegate;
|
|
+
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
{
|
|
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
|
|
diff --git a/src/frameworks/CoreMedia/src/CoreMedia.c b/src/frameworks/CoreMedia/src/CoreMedia.c
|
|
index 3d76e8103..4a975959c 100644
|
|
--- a/src/frameworks/CoreMedia/src/CoreMedia.c
|
|
+++ b/src/frameworks/CoreMedia/src/CoreMedia.c
|
|
@@ -26,6 +26,32 @@
|
|
const CFStringRef kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms = CFSTR("SampleDescriptionExtensionAtoms");
|
|
const CFStringRef kCMSampleAttachmentKey_NotSync = CFSTR("NotSync");
|
|
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_709_2 = CFSTR("ITU_R_709_2");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_240M_1995 = CFSTR("SMPTE_240M_1995");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_UseGamma = CFSTR("UseGamma");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2020 = CFSTR("ITU_R_2020");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_ITU_R_2100_HLG = CFSTR("ITU_R_2100_HLG");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_2084_PQ = CFSTR("SMPTE_ST_2084_PQ");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_SMPTE_ST_428_1 = CFSTR("SMPTE_ST_428_1");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_Linear = CFSTR("Linear");
|
|
+const CFStringRef kCMFormatDescriptionTransferFunction_sRGB = CFSTR("sRGB");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_709_2 = CFSTR("ITU_R_709_2");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_EBU_3213 = CFSTR("EBU_3213");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_SMPTE_C = CFSTR("SMPTE_C");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_ITU_R_2020 = CFSTR("ITU_R_2020");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_P3_D65 = CFSTR("P3_D65");
|
|
+const CFStringRef kCMFormatDescriptionColorPrimaries_DCI_P3 = CFSTR("DCI_P3");
|
|
+const CFStringRef kCMFormatDescriptionExtension_ContentLightLevelInfo = CFSTR("ContentLightLevelInfo");
|
|
+const CFStringRef kCMFormatDescriptionExtension_Depth = CFSTR("Depth");
|
|
+const CFStringRef kCMFormatDescriptionExtension_FormatName = CFSTR("FormatName");
|
|
+const CFStringRef kCMFormatDescriptionExtension_FullRangeVideo = CFSTR("FullRangeVideo");
|
|
+const CFStringRef kCMFormatDescriptionExtension_MasteringDisplayColorVolume = CFSTR("MasteringDisplayColorVolume");
|
|
+const CFStringRef kCMSampleAttachmentKey_IsDependedOnByOthers = CFSTR("IsDependedOnByOthers");
|
|
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_709_2 = CFSTR("ITU_R_709_2");
|
|
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4 = CFSTR("ITU_R_601_4");
|
|
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_SMPTE_240M_1995 = CFSTR("SMPTE_240M_1995");
|
|
+const CFStringRef kCMFormatDescriptionYCbCrMatrix_ITU_R_2020 = CFSTR("ITU_R_2020");
|
|
+
|
|
static int verbose = 0;
|
|
|
|
__attribute__((constructor))
|
|
diff --git a/src/frameworks/CoreServices/CMakeLists.txt b/src/frameworks/CoreServices/CMakeLists.txt
|
|
index 3fac3be55..aae2c7435 100644
|
|
--- a/src/frameworks/CoreServices/CMakeLists.txt
|
|
+++ b/src/frameworks/CoreServices/CMakeLists.txt
|
|
@@ -82,6 +82,7 @@ reexport(CoreServices Metadata ${Metadata_BUILD})
|
|
reexport(CoreServices SearchKit ${SearchKit_BUILD})
|
|
reexport(CoreServices SharedFileList ${SharedFileList_BUILD})
|
|
reexport(CoreServices OSServices ${OSServices_BUILD})
|
|
+reexport(CoreServices Foundation ${CMAKE_BINARY_DIR}/src/external/foundation/Foundation)
|
|
|
|
set_property(TARGET CoreServices APPEND_STRING PROPERTY
|
|
LINK_FLAGS " -Wl,-reexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/reexport.exp")
|
|
diff --git a/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp b/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
|
|
index 094a9483d..84810f6ab 100644
|
|
--- a/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
|
|
+++ b/src/frameworks/CoreServices/src/CarbonCore/UnicodeUtilities.cpp
|
|
@@ -212,8 +212,10 @@ OSStatus UCKeyTranslate(const UCKeyboardLayout *keyLayoutPtr, UInt16 virtualKeyC
|
|
const UCKeyToCharTableIndex* tableIndex = (UCKeyToCharTableIndex*) (((char*) keyLayoutPtr) + keyLayoutPtr->keyboardTypeList[0].keyToCharTableIndexOffset);
|
|
uint8_t tableNumber = modMaps->defaultTableNum;
|
|
|
|
- if ((modifierKeyState >> 8) < modMaps->modifiersCount)
|
|
- tableNumber = modMaps->tableNum[(modifierKeyState >> 8)];
|
|
+ // UCKeyTranslate receives the modifier byte after the caller shifts the
|
|
+ // Carbon EventRecord flags. Shifting again loses Shift and Option state.
|
|
+ if (modifierKeyState < modMaps->modifiersCount)
|
|
+ tableNumber = modMaps->tableNum[modifierKeyState];
|
|
|
|
UniChar c;
|
|
if (virtualKeyCode >= tableIndex->keyToCharTableSize || tableNumber >= tableIndex->keyToCharTableCount)
|
|
diff --git a/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m b/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
|
|
index cf9933a84..6651d50dc 100644
|
|
--- a/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
|
|
+++ b/src/frameworks/CoreServices/src/FSEvents/FSEventsImpl.m
|
|
@@ -23,6 +23,7 @@
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
+#include <stdio.h>
|
|
#include <darling/emulation/linux_premigration/ext/sys/inotify.h>
|
|
|
|
static dispatch_queue_t g_fsEventsQueue = NULL;
|
|
@@ -68,6 +69,24 @@ static void rlPerform(void* info)
|
|
for (NSString* path in _pathsToWatch)
|
|
{
|
|
int wd = inotify_add_watch(_fd, [path UTF8String], IN_ATTRIB | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
|
|
+ /* darling-lol: a path that doesn't exist yet (e.g. /etc/hosts or
|
|
+ * /etc/resolv.conf missing in the prefix) makes inotify_add_watch
|
|
+ * return -1. Apple's FSEvents tolerates unwatchable paths and the
|
|
+ * stream still starts; Chromium (dns_config_service_posix) treats
|
|
+ * a failed start as fatal for its DNS watcher. Skip failures
|
|
+ * instead of inserting a bogus -1 wd, and remember the path so a
|
|
+ * later event on the directory can still be reported. */
|
|
+ if (wd < 0)
|
|
+ {
|
|
+ if (getenv("DARLING_LOL_FSEVLOG") != NULL)
|
|
+ {
|
|
+ char b[512];
|
|
+ int n = snprintf(b, sizeof(b), "FSEV add_watch failed: %s (errno=%d)\n",
|
|
+ [path UTF8String], errno);
|
|
+ if (n > 0) write(2, b, n);
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
[wdmap setObject: path
|
|
forKey: [NSNumber numberWithInt: wd]];
|
|
}
|
|
diff --git a/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m b/src/frameworks/CoreServices/src/LaunchServices/LSOpen.m
|
|
index dd78f6df4..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..5b5712ea2 100644
|
|
--- a/src/frameworks/OpenGL/OpenGL.c
|
|
+++ b/src/frameworks/OpenGL/OpenGL.c
|
|
@@ -2,11 +2,22 @@
|
|
#include <stdio.h> // for fprintf(stderr, "unimplemented")
|
|
|
|
#include <pthread.h>
|
|
-
|
|
+#include <time.h>
|
|
+#include <fcntl.h>
|
|
+#include <unistd.h>
|
|
+#include <sys/mman.h>
|
|
+#include <sys/stat.h>
|
|
+#include <OpenGL/DarlingNativePresent.h>
|
|
+extern long __lolsys_syscall(long,long,long,long,long,long,int);
|
|
+
|
|
+#define GL_GLEXT_PROTOTYPES 1
|
|
#include <OpenGL/OpenGL.h>
|
|
+#include <OpenGL/glext.h>
|
|
#include <OpenGL/CGLInternal.h>
|
|
#include <CoreFoundation/CFDictionary.h>
|
|
+#include <IOSurface/IOSurface.h>
|
|
#include <pthread.h>
|
|
+#include <time.h>
|
|
|
|
// Try to get the right (generic) type definitions.
|
|
// In particular, we really want EGLNativeDisplayType to be void *,
|
|
@@ -22,8 +33,73 @@
|
|
#define APPLE
|
|
#define __APPLE__
|
|
|
|
+/* darling-lol M57bo: load the native EGL privately.
|
|
+ *
|
|
+ * OpenGL.framework is a load-time dependency of ANGLE's libGLESv2 (its GL
|
|
+ * backend). If the native EGL were linked here, its egl* symbols would land in
|
|
+ * libGLESv2's dependency symbol scope and satisfy Chromium's EGL lookups, so
|
|
+ * Chromium would pair Darling's Mesa EGL with ANGLE's GL - eglMakeCurrent would
|
|
+ * never make an ANGLE gl::Context current (GL_MAX_VERTEX_ATTRIBS = 0, no
|
|
+ * frames). Resolving EGL through a private RTLD_LOCAL dlopen keeps it out of
|
|
+ * dependents' scope, so Chromium falls back to ANGLE's own EGL. */
|
|
+#include <dlfcn.h>
|
|
+
|
|
+static EGLDisplay (*p_eglGetDisplay)(EGLNativeDisplayType);
|
|
+static EGLDisplay (*p_eglGetPlatformDisplay)(EGLenum, void *, const EGLAttrib *);
|
|
+static EGLBoolean (*p_eglInitialize)(EGLDisplay, EGLint *, EGLint *);
|
|
+static EGLBoolean (*p_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
|
|
+static EGLBoolean (*p_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
|
|
+static EGLBoolean (*p_eglChooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *);
|
|
+static EGLBoolean (*p_eglBindAPI)(EGLenum);
|
|
+static EGLSurface (*p_eglCreateWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *);
|
|
+static EGLBoolean (*p_eglDestroySurface)(EGLDisplay, EGLSurface);
|
|
+static EGLBoolean (*p_eglMakeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
|
|
+static EGLContext (*p_eglCreateContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *);
|
|
+static EGLBoolean (*p_eglDestroyContext)(EGLDisplay, EGLContext);
|
|
+static EGLBoolean (*p_eglSwapBuffers)(EGLDisplay, EGLSurface);
|
|
+static EGLBoolean (*p_eglSwapInterval)(EGLDisplay, EGLint);
|
|
+
|
|
+static int native_egl_loaded;
|
|
+static pthread_mutex_t native_egl_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
+
|
|
+static void ensure_native_egl(void) {
|
|
+ if (native_egl_loaded)
|
|
+ return;
|
|
+ pthread_mutex_lock(&native_egl_mutex);
|
|
+ if (!native_egl_loaded) {
|
|
+ void *h = dlopen("/usr/lib/native/libEGL.dylib", RTLD_NOW | RTLD_LOCAL);
|
|
+ if (h) {
|
|
+ p_eglGetDisplay = (EGLDisplay (*)(EGLNativeDisplayType)) dlsym(h, "eglGetDisplay");
|
|
+ p_eglGetPlatformDisplay = (EGLDisplay (*)(EGLenum, void *, const EGLAttrib *)) dlsym(h, "eglGetPlatformDisplay");
|
|
+ p_eglInitialize = (EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *)) dlsym(h, "eglInitialize");
|
|
+ p_eglQuerySurface=(EGLBoolean(*)(EGLDisplay,EGLSurface,EGLint,EGLint*))dlsym(h,"eglQuerySurface");
|
|
+ p_eglGetConfigAttrib=(EGLBoolean(*)(EGLDisplay,EGLConfig,EGLint,EGLint*))dlsym(h,"eglGetConfigAttrib");
|
|
+ p_eglChooseConfig = (EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *)) dlsym(h, "eglChooseConfig");
|
|
+ p_eglBindAPI = (EGLBoolean (*)(EGLenum)) dlsym(h, "eglBindAPI");
|
|
+ p_eglCreateWindowSurface = (EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *)) dlsym(h, "eglCreateWindowSurface");
|
|
+ p_eglDestroySurface = (EGLBoolean (*)(EGLDisplay, EGLSurface)) dlsym(h, "eglDestroySurface");
|
|
+ p_eglMakeCurrent = (EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext)) dlsym(h, "eglMakeCurrent");
|
|
+ p_eglCreateContext = (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *)) dlsym(h, "eglCreateContext");
|
|
+ p_eglDestroyContext = (EGLBoolean (*)(EGLDisplay, EGLContext)) dlsym(h, "eglDestroyContext");
|
|
+ p_eglSwapBuffers = (EGLBoolean (*)(EGLDisplay, EGLSurface)) dlsym(h, "eglSwapBuffers");
|
|
+ p_eglSwapInterval = (EGLBoolean (*)(EGLDisplay, EGLint)) dlsym(h, "eglSwapInterval");
|
|
+ native_egl_loaded = 1;
|
|
+ }
|
|
+ }
|
|
+ pthread_mutex_unlock(&native_egl_mutex);
|
|
+}
|
|
+
|
|
static EGLDisplay display;
|
|
|
|
+static EGLDisplay x11EGLDisplay(EGLNativeDisplayType native_display) {
|
|
+ /* CGS/AppKit supply Xlib Display pointers. Mesa's automatic platform
|
|
+ * selection can choose Wayland on a Wayland desktop and interpret the
|
|
+ * Xlib pointer as a wl_display, crashing during eglInitialize. */
|
|
+ if (p_eglGetPlatformDisplay)
|
|
+ return p_eglGetPlatformDisplay(0x31D5 /* EGL_PLATFORM_X11 */, native_display, NULL);
|
|
+ return p_eglGetDisplay(native_display);
|
|
+}
|
|
+
|
|
static EGLConfig config;
|
|
static int num_config;
|
|
|
|
@@ -48,10 +124,18 @@ struct _CGLContextObj {
|
|
GLuint retain_count;
|
|
pthread_mutex_t lock;
|
|
EGLContext egl_context;
|
|
+ EGLDisplay egl_display;
|
|
+ EGLConfig egl_config;
|
|
EGLSurface egl_surface;
|
|
// EGL has no function for getting the current swap interval,
|
|
// so we need to save the last set value. The default is 1.
|
|
int swap_interval;
|
|
+ EGLSurface native_present_surface;
|
|
+ EGLContext native_present_context;
|
|
+ DarlingNativePresentTarget *native_present_target;
|
|
+ uint32_t native_present_window;
|
|
+ uint64_t native_next_present_ns;
|
|
+ struct DarlingAsyncPresent *native_async;
|
|
};
|
|
|
|
struct _CGLPixelFormatObj {
|
|
@@ -59,6 +143,24 @@ struct _CGLPixelFormatObj {
|
|
CGLPixelFormatAttribute *attributes;
|
|
};
|
|
|
|
+typedef struct {
|
|
+ IOSurfaceID surfaceID;
|
|
+ CGLContextObj context;
|
|
+ GLenum target;
|
|
+ GLenum format;
|
|
+ GLenum type;
|
|
+ GLuint texture;
|
|
+ GLuint plane;
|
|
+ IOSurfaceRef surface; /* non-owning, used only to invalidate this binding */
|
|
+} CGLIOSurfaceBinding;
|
|
+
|
|
+static void stopAsyncPresent(CGLContextObj context);
|
|
+
|
|
+static CGLIOSurfaceBinding initialSurfaceBindings[256];
|
|
+static CGLIOSurfaceBinding *ioSurfaceBindings = initialSurfaceBindings;
|
|
+static unsigned ioSurfaceBindingCount, ioSurfaceBindingCapacity = 256;
|
|
+static pthread_mutex_t ioSurfaceBindingMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
+
|
|
static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
|
switch (attr) {
|
|
case kCGLPFAAuxBuffers:
|
|
@@ -69,6 +171,9 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
|
case kCGLPFAAccumSize:
|
|
case kCGLPFARendererID:
|
|
case kCGLPFADisplayMask:
|
|
+ case kCGLPFAOpenGLProfile:
|
|
+ case kCGLPFASampleBuffers:
|
|
+ case kCGLPFASamples:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
@@ -78,6 +183,7 @@ static inline int attribute_has_argument(CGLPixelFormatAttribute attr) {
|
|
__attribute__((constructor))
|
|
static void _CGLInitialize(void)
|
|
{
|
|
+ ensure_native_egl();
|
|
g_displays = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
|
|
}
|
|
|
|
@@ -93,41 +199,71 @@ static int attributes_count(const CGLPixelFormatAttribute *attrs) {
|
|
|
|
CGLError CGLRegisterNativeDisplay(void *native_display) {
|
|
|
|
- display = eglGetDisplay(native_display);
|
|
+ ensure_native_egl();
|
|
+ display = x11EGLDisplay(native_display);
|
|
|
|
if (display == EGL_NO_DISPLAY) {
|
|
return kCGLBadConnection;
|
|
}
|
|
|
|
- eglInitialize(display, NULL, NULL);
|
|
- eglChooseConfig(display, attribute_list, &config, 1, &num_config);
|
|
+ p_eglInitialize(display, NULL, NULL);
|
|
+ p_eglChooseConfig(display, attribute_list, &config, 1, &num_config);
|
|
|
|
- eglBindAPI(EGL_OPENGL_API);
|
|
+ p_eglBindAPI(EGL_OPENGL_API);
|
|
|
|
return kCGLNoError;
|
|
}
|
|
|
|
+/* darling-lol M57: processes that create a CGL context without first calling
|
|
+ * CGLRegisterNativeDisplay (e.g. Chromium's GPU process) previously got
|
|
+ * display==0/config==0 and CGLCreateContext always failed. Lazily initialise a
|
|
+ * default EGL display so context creation can proceed. */
|
|
+static void ensureDefaultDisplay(void) {
|
|
+ ensure_native_egl();
|
|
+ if (display != EGL_NO_DISPLAY && display != NULL && config != NULL)
|
|
+ return;
|
|
+
|
|
+ void *nativeDisplay=_CGSNativeDisplay(CGSMainConnectionID());
|
|
+ EGLDisplay disp = x11EGLDisplay(nativeDisplay?nativeDisplay:EGL_DEFAULT_DISPLAY);
|
|
+ if (disp == EGL_NO_DISPLAY) {
|
|
+ fprintf(stderr, "M58DG ensureDefaultDisplay: eglGetDisplay(DEFAULT)=NO_DISPLAY (p_eglGetDisplay=%p)\n", (void*) p_eglGetDisplay);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ EGLConfig cfg = NULL;
|
|
+ EGLBoolean m58dg_ini = p_eglInitialize(disp, NULL, NULL);
|
|
+ EGLBoolean m58dg_ch = p_eglChooseConfig(disp, attribute_list, &cfg, 1, &num_config);
|
|
+ fprintf(stderr, "M58DG ensureDefaultDisplay: disp=%p eglInitialize=%d eglChooseConfig=%d cfg=%p num_config=%d\n",
|
|
+ (void*) disp, (int) m58dg_ini, (int) m58dg_ch, (void*) cfg, num_config);
|
|
+ if (m58dg_ini && m58dg_ch && cfg != NULL) {
|
|
+ display = disp;
|
|
+ config = cfg;
|
|
+ p_eglBindAPI(EGL_OPENGL_API);
|
|
+ }
|
|
+}
|
|
+
|
|
static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
|
|
{
|
|
struct _CGLDisplay* rv;
|
|
|
|
+ ensure_native_egl();
|
|
pthread_mutex_lock(&g_displaysMutex);
|
|
rv = (struct _CGLDisplay*) CFDictionaryGetValue(g_displays, (const void*)(unsigned long) cid);
|
|
pthread_mutex_unlock(&g_displaysMutex);
|
|
|
|
if (!rv)
|
|
{
|
|
- EGLDisplay disp = eglGetDisplay(_CGSNativeDisplay(cid));
|
|
+ EGLDisplay disp = x11EGLDisplay(_CGSNativeDisplay(cid));
|
|
if (disp == EGL_NO_DISPLAY)
|
|
return NULL;
|
|
|
|
rv = (struct _CGLDisplay*) malloc(sizeof(*rv));
|
|
rv->display = disp;
|
|
|
|
- eglInitialize(rv->display, NULL, NULL);
|
|
- eglChooseConfig(rv->display, attribute_list, &rv->config, 1, &rv->num_config);
|
|
+ p_eglInitialize(rv->display, NULL, NULL);
|
|
+ p_eglChooseConfig(rv->display, attribute_list, &rv->config, 1, &rv->num_config);
|
|
|
|
- eglBindAPI(EGL_OPENGL_API);
|
|
+ p_eglBindAPI(EGL_OPENGL_API);
|
|
|
|
pthread_mutex_lock(&g_displaysMutex);
|
|
CFDictionaryAddValue(g_displays, (const void*)(unsigned long) cid, rv);
|
|
@@ -139,6 +275,8 @@ static struct _CGLDisplay* getCGLDisplay(CGSConnectionID cid)
|
|
|
|
CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, CGSSurfaceID sid)
|
|
{
|
|
+ if (!gl)
|
|
+ return kCGLBadContext;
|
|
struct _CGLDisplay* disp = getCGLDisplay(cid);
|
|
if (!disp)
|
|
return kCGLBadConnection;
|
|
@@ -152,7 +290,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
|
if (!window)
|
|
return kCGLBadWindow;
|
|
|
|
- gl->egl_surface = eglCreateWindowSurface(disp->display, disp->config, window, NULL);
|
|
+ gl->egl_surface = p_eglCreateWindowSurface(gl->egl_display, gl->egl_config, window, NULL);
|
|
if (gl->egl_surface == EGL_NO_SURFACE)
|
|
return kCGLBadState;
|
|
return kCGLNoError;
|
|
@@ -161,7 +299,7 @@ CGLError CGLSetSurface(CGLContextObj gl, CGSConnectionID cid, CGSWindowID wid, C
|
|
CGLWindowRef CGLGetWindow(void *native_window) {
|
|
|
|
EGLNativeWindowType window = (EGLNativeWindowType) native_window;
|
|
- EGLSurface surface = eglCreateWindowSurface(display, config, window, NULL);
|
|
+ EGLSurface surface = p_eglCreateWindowSurface(display, config, window, NULL);
|
|
|
|
if (surface == EGL_NO_SURFACE) {
|
|
return NULL;
|
|
@@ -171,14 +309,21 @@ CGLWindowRef CGLGetWindow(void *native_window) {
|
|
}
|
|
|
|
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 +343,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 +392,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 +422,10 @@ CGLError CGLDescribePixelFormat(
|
|
CGLPixelFormatAttribute attr,
|
|
GLint *value
|
|
) {
|
|
+ if (!format) return kCGLBadPixelFormat;
|
|
+ if (!value) return kCGLBadAddress;
|
|
+ if (sreen_num != 0) return kCGLBadValue;
|
|
+ if (attr == kCGLPFAVirtualScreenCount) { *value = 1; return kCGLNoError; }
|
|
for (int i = 0; format->attributes[i] != 0; i++) {
|
|
int has_arg = attribute_has_argument(format->attributes[i]);
|
|
|
|
@@ -304,24 +479,67 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) {
|
|
}
|
|
|
|
CGLError CGLCreateContext(CGLPixelFormatObj pixelFormat, CGLContextObj share, CGLContextObj *resultp) {
|
|
+ /* Cocotron window compositing requests the default format with NULL. */
|
|
+ if (!resultp)
|
|
+ return kCGLBadAddress;
|
|
+ *resultp = NULL;
|
|
+ ensureDefaultDisplay();
|
|
+ /* eglBindAPI is thread-local, even when the display is already initialized. */
|
|
+ if (!display || !config || !p_eglBindAPI || !p_eglBindAPI(EGL_OPENGL_API))
|
|
+ return kCGLBadConnection;
|
|
|
|
EGLContext egl_share = EGL_NO_CONTEXT;
|
|
+ EGLDisplay contextDisplay = share ? share->egl_display : display;
|
|
+ EGLConfig contextConfig = share ? share->egl_config : config;
|
|
if (share != NULL) {
|
|
egl_share = share->egl_context;
|
|
}
|
|
- EGLContext egl_context = eglCreateContext(display, config, egl_share, NULL);
|
|
+ GLint profile = 0;
|
|
+ if (pixelFormat)
|
|
+ CGLDescribePixelFormat(pixelFormat, 0, kCGLPFAOpenGLProfile, &profile);
|
|
+ EGLint contextAttributes[] = {
|
|
+ 0x3098 /* EGL_CONTEXT_MAJOR_VERSION */, 3,
|
|
+ 0x30FB /* EGL_CONTEXT_MINOR_VERSION */, 2,
|
|
+ 0x30FD /* EGL_CONTEXT_OPENGL_PROFILE_MASK */, 1 /* CORE_PROFILE_BIT */,
|
|
+ EGL_NONE
|
|
+ };
|
|
+ if (profile == 0x4100) {
|
|
+ contextAttributes[1] = 4;
|
|
+ contextAttributes[3] = 1;
|
|
+ } else if (profile && profile != 0x1000 && profile != 0x3200) {
|
|
+ return kCGLBadValue;
|
|
+ }
|
|
+ EGLContext egl_context = p_eglCreateContext(contextDisplay, contextConfig, egl_share,
|
|
+ profile == 0x3200 || profile == 0x4100 ? contextAttributes : NULL);
|
|
|
|
if (egl_context == EGL_NO_CONTEXT) {
|
|
+ fprintf(stderr, "M58DG CGLCreateContext FAILED: display=%p config=%p share=%p p_eglCreateContext=%p\n",
|
|
+ (void*) display, (void*) config, (void*) egl_share, (void*) p_eglCreateContext);
|
|
return kCGLBadContext;
|
|
}
|
|
|
|
+ {
|
|
+ static int m58dg_ok = 0;
|
|
+ if (m58dg_ok++ < 5)
|
|
+ fprintf(stderr, "M58DG CGLCreateContext OK display=%p config=%p ctx=%p\n",
|
|
+ (void*) display, (void*) config, (void*) egl_context);
|
|
+ }
|
|
+
|
|
CGLContextObj context = malloc(sizeof(struct _CGLContextObj));
|
|
|
|
context->retain_count = 1;
|
|
pthread_mutex_init(&(context->lock), NULL);
|
|
context->egl_context = egl_context;
|
|
+ context->egl_display = contextDisplay;
|
|
+ context->egl_config = contextConfig;
|
|
context->egl_surface = NULL;
|
|
context->swap_interval = 1;
|
|
+ context->native_present_surface=EGL_NO_SURFACE;
|
|
+ context->native_present_context=EGL_NO_CONTEXT;
|
|
+ context->native_present_target=NULL;
|
|
+ context->native_present_window=0;
|
|
+ context->native_next_present_ns=0;
|
|
+ context->native_async=NULL;
|
|
|
|
*resultp = context;
|
|
|
|
@@ -333,7 +551,7 @@ CGLContextObj CGLRetainContext(CGLContextObj context) {
|
|
return NULL;
|
|
}
|
|
|
|
- context->retain_count++;
|
|
+ __sync_add_and_fetch(&context->retain_count, 1);
|
|
return context;
|
|
}
|
|
|
|
@@ -342,11 +560,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 +578,11 @@ void CGLReleaseContext(CGLContextObj context) {
|
|
|
|
pthread_mutex_destroy(&(context->lock));
|
|
|
|
- eglDestroyContext(display, context->egl_context);
|
|
+ stopAsyncPresent(context);
|
|
+ if(context->native_present_context!=EGL_NO_CONTEXT)p_eglDestroyContext(context->egl_display,context->native_present_context);
|
|
+ if(context->native_present_surface!=EGL_NO_SURFACE)p_eglDestroySurface(context->egl_display,context->native_present_surface);
|
|
+ if(context->native_present_target)__lolsys_syscall((long)context->native_present_target,sizeof(DarlingNativePresentTarget),0,0,0,0,11);
|
|
+ p_eglDestroyContext(context->egl_display, context->egl_context);
|
|
|
|
free(context);
|
|
}
|
|
@@ -383,9 +611,330 @@ CGLError CGLUnlockContext(CGLContextObj context) {
|
|
return kCGLNoError;
|
|
}
|
|
|
|
+static int swapIntervalRequested;
|
|
+
|
|
CGLError CGLFlushDrawable(CGLContextObj context) {
|
|
- eglSwapBuffers(display,context->egl_surface);
|
|
- return kCGLNoError;
|
|
+ if (!context)
|
|
+ return kCGLBadContext;
|
|
+ /* darling-lol: EGL defaults to a swap interval of 1. When the window is covered or the compositor throttles it
|
|
+ * (Xwayland answers about once a second), every swap blocks that long and the game's frame rate collapses to ~1 fps.
|
|
+ * macOS apps get an unthrottled flush unless they ask for vsync through kCGLCPSwapInterval, which is handled in
|
|
+ * CGLSetParameter; apply "no vsync" once per context here, while it is current. */
|
|
+ static CGLContextObj swapIntervalContext;
|
|
+ if (!swapIntervalRequested && context != swapIntervalContext && p_eglSwapInterval && CGLGetCurrentContext() == context) {
|
|
+ p_eglSwapInterval(context->egl_display, 0);
|
|
+ swapIntervalContext = context;
|
|
+ }
|
|
+ return p_eglSwapBuffers(context->egl_display, context->egl_surface) ? kCGLNoError : kCGLBadState;
|
|
+}
|
|
+
|
|
+static GLenum textureBindingEnum(GLenum target) {
|
|
+ switch (target) {
|
|
+ case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D;
|
|
+#ifdef GL_TEXTURE_RECTANGLE
|
|
+ case GL_TEXTURE_RECTANGLE: return GL_TEXTURE_BINDING_RECTANGLE;
|
|
+#endif
|
|
+ default: return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* macOS exposes IOSurfaces to CGL as texture storage. Mesa/EGL does not have
|
|
+ * that CGL entry point, but Chromium only needs the pixels to reach the bound
|
|
+ * texture. Upload from the shared IOSurface mapping synchronously; this also
|
|
+ * preserves the cross-process IOSurface transport implemented by our shim. */
|
|
+static uint64_t nativeMonotonicNS(void);
|
|
+static uint32_t nativeUploadCount, nativeUploadMicroseconds;
|
|
+CGLError CGLTexImageIOSurface2D(CGLContextObj context, GLenum target,
|
|
+ GLenum internalFormat, GLsizei width, GLsizei height, GLenum format,
|
|
+ GLenum type, IOSurfaceRef ioSurface, GLuint plane)
|
|
+{
|
|
+ if (!context)
|
|
+ return kCGLBadContext;
|
|
+ if (!ioSurface)
|
|
+ return kCGLBadAddress;
|
|
+
|
|
+ void *pixels = IOSurfaceGetBaseAddressOfPlane(ioSurface, plane);
|
|
+ size_t bytesPerRow = IOSurfaceGetBytesPerRowOfPlane(ioSurface, plane);
|
|
+ size_t bytesPerElement = IOSurfaceGetBytesPerElementOfPlane(ioSurface, plane);
|
|
+ if (!pixels || !bytesPerRow || !bytesPerElement)
|
|
+ return kCGLBadValue;
|
|
+
|
|
+ CGLContextObj oldContext = CGLGetCurrentContext();
|
|
+ if (oldContext != context) {
|
|
+ CGLError result = CGLSetCurrentContext(context);
|
|
+ if (result != kCGLNoError)
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ uint64_t uploadStarted = nativeMonotonicNS();
|
|
+ GLint oldRowLength = 0;
|
|
+ GLint oldAlignment = 0;
|
|
+ GLint boundTexture = 0;
|
|
+ /* ANGLE can leave an unrelated probe error pending before calling this
|
|
+ * CGL API. CGLTexImageIOSurface2D reports only its own operation. */
|
|
+ while (glGetError() != GL_NO_ERROR) {}
|
|
+ glGetIntegerv(GL_UNPACK_ROW_LENGTH, &oldRowLength);
|
|
+ glGetIntegerv(GL_UNPACK_ALIGNMENT, &oldAlignment);
|
|
+ GLenum bindingEnum = textureBindingEnum(target);
|
|
+ if (bindingEnum)
|
|
+ glGetIntegerv(bindingEnum, &boundTexture);
|
|
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)(bytesPerRow / bytesPerElement));
|
|
+ GLint allocatedWidth=0,allocatedHeight=0,allocatedFormat=0;
|
|
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_WIDTH,&allocatedWidth);
|
|
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_HEIGHT,&allocatedHeight);
|
|
+ glGetTexLevelParameteriv(target,0,GL_TEXTURE_INTERNAL_FORMAT,&allocatedFormat);
|
|
+ if(allocatedWidth==width && allocatedHeight==height && allocatedFormat==(GLint)internalFormat)
|
|
+ glTexSubImage2D(target,0,0,0,width,height,format,type,pixels);
|
|
+ else
|
|
+ glTexImage2D(target, 0, (GLint)internalFormat, width, height, 0,
|
|
+ format, type, pixels);
|
|
+ GLenum error = glGetError();
|
|
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, oldRowLength);
|
|
+ glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlignment);
|
|
+
|
|
+ if (error == GL_NO_ERROR && boundTexture != 0) {
|
|
+ CGLIOSurfaceBinding binding = {
|
|
+ IOSurfaceGetID(ioSurface), context, target, format, type,
|
|
+ (GLuint)boundTexture, plane, ioSurface
|
|
+ };
|
|
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
|
|
+ unsigned slot = ioSurfaceBindingCount;
|
|
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ++i) {
|
|
+ if (ioSurfaceBindings[i].surfaceID == binding.surfaceID) {
|
|
+ slot = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (slot == ioSurfaceBindingCapacity) {
|
|
+ unsigned capacity = ioSurfaceBindingCapacity * 2;
|
|
+ CGLIOSurfaceBinding *larger = malloc(capacity * sizeof(*larger));
|
|
+ if (larger) {
|
|
+ memcpy(larger, ioSurfaceBindings, ioSurfaceBindingCount * sizeof(*larger));
|
|
+ if (ioSurfaceBindings != initialSurfaceBindings) free(ioSurfaceBindings);
|
|
+ ioSurfaceBindings = larger;
|
|
+ ioSurfaceBindingCapacity = capacity;
|
|
+ }
|
|
+ }
|
|
+ if (slot < ioSurfaceBindingCapacity) {
|
|
+ ioSurfaceBindings[slot] = binding;
|
|
+ if (slot == ioSurfaceBindingCount) ++ioSurfaceBindingCount;
|
|
+ } else error = GL_OUT_OF_MEMORY;
|
|
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
|
|
+ }
|
|
+
|
|
+ CGLError restore = kCGLNoError;
|
|
+ if (oldContext != context)
|
|
+ restore = CGLSetCurrentContext(oldContext);
|
|
+ if (error != GL_NO_ERROR)
|
|
+ fprintf(stderr, "CGLTexImageIOSurface2D glTexImage2D error=0x%x target=0x%x format=0x%x type=0x%x %dx%d\n",
|
|
+ error, target, format, type, width, height);
|
|
+ __atomic_fetch_add(&nativeUploadCount,1,__ATOMIC_RELAXED);
|
|
+ __atomic_fetch_add(&nativeUploadMicroseconds,(uint32_t)((nativeMonotonicNS()-uploadStarted)/1000),__ATOMIC_RELAXED);
|
|
+ return error == GL_NO_ERROR ? restore : kCGLBadState;
|
|
+}
|
|
+
|
|
+/* Called before IOSurface storage is destroyed; no owning surface reference. */
|
|
+void CGLForgetIOSurface(IOSurfaceRef surface) {
|
|
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
|
|
+ for (unsigned i = 0; i < ioSurfaceBindingCount; ) {
|
|
+ if (ioSurfaceBindings[i].surface == surface)
|
|
+ ioSurfaceBindings[i] = ioSurfaceBindings[--ioSurfaceBindingCount];
|
|
+ else ++i;
|
|
+ }
|
|
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
|
|
+}
|
|
+
|
|
+#include "CGLAsyncPresent.inc"
|
|
+
|
|
+/* Present a flattened compositor texture directly. The native transport
|
|
+ * acknowledges suspension of CPU writes before the first GL swap. Fallback
|
|
+ * leaves the existing readback path available and restores all GL bindings. */
|
|
+CGL_EXPORT int CGLPresentIOSurface(IOSurfaceRef surface) {
|
|
+ if(!surface || !getenv("RCLOL_GPU_DIRECT_PRESENT") || IOSurfaceGetPlaneCount(surface)>1 ||
|
|
+ IOSurfaceGetBytesPerElement(surface)!=4)return 0;
|
|
+ size_t width=IOSurfaceGetWidth(surface),height=IOSurfaceGetHeight(surface);
|
|
+ if(width<256 || height<256)return 0;
|
|
+ CGLIOSurfaceBinding binding={0};IOSurfaceID id=IOSurfaceGetID(surface);
|
|
+ pthread_mutex_lock(&ioSurfaceBindingMutex);
|
|
+ for(unsigned i=0;i<ioSurfaceBindingCount;++i)if(ioSurfaceBindings[i].surfaceID==id){
|
|
+ binding=ioSurfaceBindings[i];CGLRetainContext(binding.context);break;
|
|
+ }
|
|
+ pthread_mutex_unlock(&ioSurfaceBindingMutex);
|
|
+ if(!binding.context)return 0;
|
|
+ CGLContextObj ctx=binding.context;
|
|
+ if(CGLGetCurrentContext()!=ctx){CGLReleaseContext(ctx);return 0;}
|
|
+ if(!ctx->native_present_target){
|
|
+ char path[128];snprintf(path,sizeof(path),"/dev/shm/darling-ca-gpu-target-%u-%zu-%zu",getuid(),width,height);
|
|
+ int fd=(int)__lolsys_syscall((long)path,2,0,0,0,0,2);if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT target_open=%d uid=%u size=%zux%zu\n",fd,getuid(),width,height);if(fd>=0){
|
|
+ struct stat metadata;
|
|
+ long mapped=-1;
|
|
+ if(fstat(fd,&metadata)==0 && metadata.st_size>=sizeof(DarlingNativePresentTarget) && metadata.st_uid==getuid())
|
|
+ mapped=__lolsys_syscall(0,sizeof(DarlingNativePresentTarget),3,1,fd,0,9);
|
|
+ if(mapped>0 || mapped < -4095)ctx->native_present_target=(void*)mapped;__lolsys_syscall(fd,0,0,0,0,0,3);
|
|
+ }
|
|
+ }
|
|
+ DarlingNativePresentTarget *target=ctx->native_present_target;
|
|
+ /* A Chromium context renders several windows. An unrelated surface must
|
|
+ * use readback without stopping the active window's async presenter. */
|
|
+ if(target && __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)==DARLING_NATIVE_PRESENT_MAGIC &&
|
|
+ target->target && (target->width!=width || target->height!=height)) {
|
|
+ CGLReleaseContext(ctx);return 0;
|
|
+ }
|
|
+ if(!target || __atomic_load_n(&target->magic,__ATOMIC_ACQUIRE)!=DARLING_NATIVE_PRESENT_MAGIC ||
|
|
+ !target->target){
|
|
+ stopAsyncPresent(ctx);
|
|
+ if(target){__lolsys_syscall((long)target,sizeof(DarlingNativePresentTarget),0,0,0,0,11);ctx->native_present_target=NULL;ctx->native_present_window=0;}
|
|
+ CGLReleaseContext(ctx);return 0;
|
|
+ }
|
|
+ if(ctx->native_present_window!=target->target){
|
|
+ stopAsyncPresent(ctx);
|
|
+ if(ctx->native_present_context!=EGL_NO_CONTEXT)p_eglDestroyContext(ctx->egl_display,ctx->native_present_context);
|
|
+ if(ctx->native_present_surface!=EGL_NO_SURFACE)p_eglDestroySurface(ctx->egl_display,ctx->native_present_surface);
|
|
+ ctx->native_present_surface=EGL_NO_SURFACE;ctx->native_present_context=EGL_NO_CONTEXT;
|
|
+ EGLint attrs[]={EGL_SURFACE_TYPE,EGL_WINDOW_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_BIT,EGL_NONE};
|
|
+ EGLConfig *candidates=NULL,cfg=NULL;EGLint count=0,selectedVisual=0;
|
|
+ if(p_eglChooseConfig(ctx->egl_display,attrs,NULL,0,&count) && count>0 && count<=65536)
|
|
+ candidates=calloc(count,sizeof(*candidates));
|
|
+ if(candidates && p_eglChooseConfig(ctx->egl_display,attrs,candidates,count,&count) && p_eglGetConfigAttrib){
|
|
+ for(EGLint n=0;n<count;++n){EGLint visual=0;
|
|
+ if(p_eglGetConfigAttrib(ctx->egl_display,candidates[n],EGL_NATIVE_VISUAL_ID,&visual) && visual==(EGLint)target->visual){cfg=candidates[n];selectedVisual=visual;break;}
|
|
+ }
|
|
+ }
|
|
+ free(candidates);
|
|
+ if(cfg){
|
|
+ ctx->native_present_surface=p_eglCreateWindowSurface(ctx->egl_display,cfg,(EGLNativeWindowType)(uintptr_t)target->target,NULL);
|
|
+ EGLint contextAttrs[]={0x3098,3,0x30FB,2,0x30FD,1,EGL_NONE};
|
|
+ ctx->native_present_context=p_eglCreateContext(ctx->egl_display,cfg,ctx->egl_context,contextAttrs);
|
|
+ }
|
|
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT selected_visual=%d requested_visual=%u\n",selectedVisual,target->visual);
|
|
+ EGLint actualWidth=0,actualHeight=0;
|
|
+ if(ctx->native_present_surface!=EGL_NO_SURFACE){p_eglQuerySurface(ctx->egl_display,ctx->native_present_surface,EGL_WIDTH,&actualWidth);p_eglQuerySurface(ctx->egl_display,ctx->native_present_surface,EGL_HEIGHT,&actualHeight);}
|
|
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT native_size=%dx%d requested=%zux%zu\n",actualWidth,actualHeight,width,height);
|
|
+ ctx->native_present_window=target->target;
|
|
+ if(getenv("RCLOL_GPU_DIRECT_TRACE"))fprintf(stderr,"GPU_DIRECT surface_created=%d shared_context=%d visual=%u target=%u\n",ctx->native_present_surface!=EGL_NO_SURFACE,ctx->native_present_context!=EGL_NO_CONTEXT,target->visual,target->target);
|
|
+ }
|
|
+ if(ctx->native_present_surface==EGL_NO_SURFACE || ctx->native_present_context==EGL_NO_CONTEXT){CGLReleaseContext(ctx);return 0;}
|
|
+ uint32_t ready=__atomic_load_n(&target->ready,__ATOMIC_ACQUIRE);
|
|
+ if(!ready){__atomic_store_n(&target->ready,1,__ATOMIC_RELEASE);
|
|
+ for(unsigned n=0;n<40 && __atomic_load_n(&target->ready,__ATOMIC_ACQUIRE)==1;++n)usleep(500);
|
|
+ if(__atomic_load_n(&target->ready,__ATOMIC_ACQUIRE)!=2){__atomic_store_n(&target->ready,0,__ATOMIC_RELEASE);CGLReleaseContext(ctx);return 0;}
|
|
+ }
|
|
+ /* Bound the producer independently of async display cadence when testing
|
|
+ * unthrottled begin frames. Keep waits on the owning GPU thread and use
|
|
+ * absolute monotonic deadlines; async staging remains bounded to three. */
|
|
+ if(getenv("RCLOL_GPU_DIRECT_PACE")){
|
|
+ struct timespec now;
|
|
+ if(__lolsys_syscall(1,(long)&now,0,0,0,0,228)==0){
|
|
+ uint64_t current=(uint64_t)now.tv_sec*1000000000ull+now.tv_nsec;
|
|
+ uint64_t deadline=ctx->native_next_present_ns;
|
|
+ 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) {
|
|
@@ -394,8 +943,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c
|
|
|
|
if (parameter == kCGLCPSwapInterval)
|
|
{
|
|
+ swapIntervalRequested = 1;
|
|
GLint v = *value;
|
|
- EGLBoolean success = eglSwapInterval(display, v);
|
|
+ EGLBoolean success = p_eglSwapInterval(display, v);
|
|
if (success)
|
|
context->swap_interval = v;
|
|
return success ? kCGLNoError : kCGLBadValue;
|
|
@@ -417,14 +967,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G
|
|
return kCGLNoError;
|
|
}
|
|
|
|
-CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, long rend_num, CGLRendererProperty prop, long *value) {
|
|
- return kCGLNoError;
|
|
+CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, GLint rend_num, CGLRendererProperty prop, GLint *value) {
|
|
+ if (!value) return kCGLBadAddress;
|
|
+ *value = 0;
|
|
+ return kCGLBadRendererInfo;
|
|
}
|
|
|
|
-CGLError CGLQueryRendererInfo(unsigned long display_mask, CGLRendererInfoObj *rend, long *nrend) {
|
|
- return kCGLNoError;
|
|
+CGLError CGLQueryRendererInfo(GLuint display_mask, CGLRendererInfoObj *rend, GLint *nrend) {
|
|
+ if (!rend || !nrend) return kCGLBadAddress;
|
|
+ *rend = NULL;
|
|
+ *nrend = 0;
|
|
+ return kCGLBadRendererInfo;
|
|
}
|
|
|
|
CGLError CGLDestroyRendererInfo(CGLRendererInfoObj rend) {
|
|
return kCGLNoError;
|
|
-}
|
|
\ No newline at end of file
|
|
+}
|
|
diff --git a/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h b/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
|
|
index 7d5eda01a..803ac02d3 100644
|
|
--- a/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
|
|
+++ b/src/frameworks/OpenGL/include/OpenGL/CGLTypes.h
|
|
@@ -64,6 +64,7 @@ enum {
|
|
kCGLPFARemotePBuffer = 91,
|
|
kCGLPFAAllowOfflineRenderers = 96,
|
|
kCGLPFAAcceleratedCompute = 97,
|
|
+ kCGLPFAOpenGLProfile = 99,
|
|
kCGLPFAVirtualScreenCount = 128,
|
|
};
|
|
|
|
diff --git a/src/frameworks/OpenGL/include/OpenGL/OpenGL.h b/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
|
|
index a700038d7..7bb98bf4b 100644
|
|
--- a/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
|
|
+++ b/src/frameworks/OpenGL/include/OpenGL/OpenGL.h
|
|
@@ -8,6 +8,7 @@
|
|
#endif
|
|
|
|
#include <OpenGL/CGLTypes.h>
|
|
+#include <IOSurface/IOSurfaceRef.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
@@ -27,6 +28,8 @@ CGL_EXPORT CGLError CGLUnlockContext(CGLContextObj context);
|
|
CGL_EXPORT CGLError CGLSetCurrentContext(CGLContextObj ctx);
|
|
CGL_EXPORT CGLError CGLSetFullScreen(CGLContextObj ctx);
|
|
CGL_EXPORT CGLContextObj CGLGetCurrentContext(void);
|
|
+CGL_EXPORT CGLError CGLGetVirtualScreen(CGLContextObj ctx, GLint *screen);
|
|
+CGL_EXPORT CGLError CGLSetVirtualScreen(CGLContextObj ctx, GLint screen);
|
|
|
|
CGL_EXPORT CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value);
|
|
CGL_EXPORT CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, GLint *value);
|
|
@@ -40,9 +43,9 @@ CGL_EXPORT CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pixelFormat);
|
|
CGL_EXPORT GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat);
|
|
CGL_EXPORT CGLError CGLDescribePixelFormat(CGLPixelFormatObj pixelFormat, GLint screenNumber, CGLPixelFormatAttribute attribute, GLint *valuep);
|
|
|
|
-CGL_EXPORT CGLError CGLQueryRendererInfo(unsigned long display_mask, CGLRendererInfoObj *rend, long *nrend);
|
|
+CGL_EXPORT CGLError CGLQueryRendererInfo(GLuint display_mask, CGLRendererInfoObj *rend, GLint *nrend);
|
|
CGL_EXPORT CGLError CGLDestroyRendererInfo(CGLRendererInfoObj rend);
|
|
-CGL_EXPORT CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, long rend_num, CGLRendererProperty prop, long *value);
|
|
+CGL_EXPORT CGLError CGLDescribeRenderer(CGLRendererInfoObj rend, GLint rend_num, CGLRendererProperty prop, GLint *value);
|
|
|
|
CGL_EXPORT CGLError CGLCreatePBuffer(GLsizei width, GLsizei height, GLenum target, GLenum internalFormat, GLint maxDetail, CGLPBufferObj *pbuffer);
|
|
CGL_EXPORT CGLError CGLDescribePBuffer(CGLPBufferObj pbuffer, GLsizei *width, GLsizei *height, GLenum *target, GLenum *internalFormat, GLint *mipmap);
|
|
@@ -53,6 +56,13 @@ CGL_EXPORT CGLError CGLDestroyPBuffer(CGLPBufferObj pbuffer);
|
|
CGL_EXPORT CGLError CGLGetPBuffer(CGLContextObj context, CGLPBufferObj *pbuffer, GLenum *face, GLint *level, GLint *screen);
|
|
CGL_EXPORT CGLError CGLSetPBuffer(CGLContextObj context, CGLPBufferObj pbuffer, GLenum face, GLint level, GLint screen);
|
|
CGL_EXPORT CGLError CGLTexImagePBuffer(CGLContextObj context, CGLPBufferObj pbuffer, GLenum sourceBuffer);
|
|
+CGL_EXPORT CGLError CGLTexImageIOSurface2D(CGLContextObj context, GLenum target,
|
|
+ GLenum internalFormat, GLsizei width, GLsizei height, GLenum format,
|
|
+ GLenum type, IOSurfaceRef ioSurface, GLuint plane);
|
|
+/* Darling extension used by QuartzCore's remote-layer fallback. Real CGL
|
|
+ * texture storage is bidirectionally backed by IOSurface; our Mesa fallback
|
|
+ * must explicitly copy rendered texture pixels back to the surface. */
|
|
+CGL_EXPORT CGLError CGLReadbackIOSurface(IOSurfaceRef ioSurface);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
diff --git a/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h b/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
|
|
index 68242ee34..a9108c9c1 100644
|
|
--- a/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
|
|
+++ b/src/frameworks/VideoToolbox/include/VideoToolbox/VTCompressionProperties.h
|
|
@@ -34,4 +34,10 @@ extern CFStringRef const kVTCompressionPropertyKey_ExpectedFrameRate;
|
|
|
|
extern CFStringRef const kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder;
|
|
|
|
+extern CFStringRef const kVTCompressionPropertyKey_BaseLayerFrameRateFraction;
|
|
+extern CFStringRef const kVTCompressionPropertyKey_ColorPrimaries;
|
|
+extern CFStringRef const kVTCompressionPropertyKey_TransferFunction;
|
|
+extern CFStringRef const kVTCompressionPropertyKey_YCbCrMatrix;
|
|
+extern CFStringRef const kVTVideoEncoderSpecification_EnableLowLatencyRateControl;
|
|
+
|
|
#endif
|
|
diff --git a/src/frameworks/VideoToolbox/src/VTCompressionProperties.c b/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
|
|
index 9e363904e..774557a64 100644
|
|
--- a/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
|
|
+++ b/src/frameworks/VideoToolbox/src/VTCompressionProperties.c
|
|
@@ -30,3 +30,9 @@ const CFStringRef kVTCompressionPropertyKey_MaxFrameDelayCount = CFSTR("MaxFrame
|
|
const CFStringRef kVTCompressionPropertyKey_ExpectedFrameRate = CFSTR("ExpectedFrameRate");
|
|
|
|
const CFStringRef kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder = CFSTR("RequireHardwareAcceleratedVideoEncoder");
|
|
+
|
|
+const CFStringRef kVTCompressionPropertyKey_BaseLayerFrameRateFraction = CFSTR("BaseLayerFrameRateFraction");
|
|
+const CFStringRef kVTCompressionPropertyKey_ColorPrimaries = CFSTR("ColorPrimaries");
|
|
+const CFStringRef kVTCompressionPropertyKey_TransferFunction = CFSTR("TransferFunction");
|
|
+const CFStringRef kVTCompressionPropertyKey_YCbCrMatrix = CFSTR("YCbCrMatrix");
|
|
+const CFStringRef kVTVideoEncoderSpecification_EnableLowLatencyRateControl = CFSTR("EnableLowLatencyRateControl");
|
|
diff --git a/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h b/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
|
|
index 62d827aa2..15383581b 100644
|
|
--- a/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
|
|
+++ b/src/frameworks/Vision/include/Vision/VNDetectBarcodesRequest.h
|
|
@@ -19,6 +19,26 @@
|
|
|
|
#include <Foundation/Foundation.h>
|
|
|
|
+typedef NSString *VNBarcodeSymbology NS_TYPED_ENUM;
|
|
+
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyAztec;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode128;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39Checksum;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCII;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCIIChecksum;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode93;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyCode93i;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyDataMatrix;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyEAN13;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyEAN8;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyI2of5;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyI2of5Checksum;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyITF14;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyPDF417;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyQR;
|
|
+extern VNBarcodeSymbology const VNBarcodeSymbologyUPCE;
|
|
+
|
|
@interface VNDetectBarcodesRequest : NSObject
|
|
|
|
@end
|
|
diff --git a/src/frameworks/Vision/src/VNDetectBarcodesRequest.m b/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
|
|
index 9a489b386..9fe56cc3f 100644
|
|
--- a/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
|
|
+++ b/src/frameworks/Vision/src/VNDetectBarcodesRequest.m
|
|
@@ -19,6 +19,24 @@
|
|
|
|
#import <Vision/VNDetectBarcodesRequest.h>
|
|
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyAztec = @"org.iso.Aztec";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode128 = @"org.iso.Code128";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode39 = @"org.iso.Code39";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode39Checksum = @"org.iso.Code39Checksum";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCII = @"org.iso.Code39FullASCII";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode39FullASCIIChecksum = @"org.iso.Code39FullASCIIChecksum";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode93 = @"org.iso.Code93";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyCode93i = @"org.iso.Code93i";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyDataMatrix = @"org.iso.DataMatrix";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyEAN13 = @"org.gs1.EAN-13";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyEAN8 = @"org.gs1.EAN-8";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyI2of5 = @"org.iso.I2of5";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyI2of5Checksum = @"org.iso.I2of5Checksum";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyITF14 = @"org.iso.ITF14";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyPDF417 = @"org.iso.PDF417";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyQR = @"org.iso.QRCode";
|
|
+VNBarcodeSymbology const VNBarcodeSymbologyUPCE = @"org.gs1.UPC-E";
|
|
+
|
|
@implementation VNDetectBarcodesRequest
|
|
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
|
diff --git a/src/libsandbox/src/sandbox.c b/src/libsandbox/src/sandbox.c
|
|
index 5af5298aa..fb59ee5d1 100644
|
|
--- a/src/libsandbox/src/sandbox.c
|
|
+++ b/src/libsandbox/src/sandbox.c
|
|
@@ -21,6 +21,8 @@
|
|
#include <sandbox/sandbox.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
+#include <stdint.h>
|
|
+#include <string.h>
|
|
|
|
static int verbose = 0;
|
|
|
|
@@ -29,63 +31,105 @@ static void initme(void) {
|
|
verbose = getenv("STUB_VERBOSE") != NULL;
|
|
}
|
|
|
|
-void* sandbox_apply(void)
|
|
+/* darling-lol: SeatbeltExtensions token family. Chromium's seatbelt client
|
|
+ * issues/consumes/releases these around sandboxed child setup; without
|
|
+ * enforcement we hand out dummy nonzero tokens and accept everything.
|
|
+ * (These live in libsandbox.dylib on macOS, hence here and not in
|
|
+ * libsystem_sandbox.) */
|
|
+static int64_t sandbox_extension_next_token = 1;
|
|
+
|
|
+int64_t sandbox_extension_issue_file(const char *path, uint32_t flags)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_apply called");
|
|
- return NULL;
|
|
+ if (verbose) puts("STUB: sandbox_extension_issue_file called");
|
|
+ (void)path; (void)flags;
|
|
+ return sandbox_extension_next_token++;
|
|
}
|
|
|
|
-void* sandbox_apply_container(void)
|
|
+int64_t sandbox_extension_issue_mach(const char *name, uint32_t flags)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_apply_container called");
|
|
- return NULL;
|
|
+ if (verbose) puts("STUB: sandbox_extension_issue_mach called");
|
|
+ (void)name; (void)flags;
|
|
+ return sandbox_extension_next_token++;
|
|
}
|
|
|
|
-void* sandbox_compile_entitlements(void)
|
|
+int sandbox_extension_consume(int64_t token)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_compile_entitlements called");
|
|
- return NULL;
|
|
+ if (verbose) puts("STUB: sandbox_extension_consume called");
|
|
+ (void)token;
|
|
+ return 0;
|
|
}
|
|
|
|
-void* sandbox_compile_file(void)
|
|
+int sandbox_extension_release(int64_t token)
|
|
+{
|
|
+ if (verbose) puts("STUB: sandbox_extension_release called");
|
|
+ (void)token;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* darling-lol: compile/apply family. No seatbelt compiler exists, so
|
|
+ * "compiling" a profile yields a dummy handle and applying succeeds.
|
|
+ * Signatures match Apple's (the old stubs had wrong shapes). */
|
|
+
|
|
+void* sandbox_compile_string(const char *profile, char **errorbuf)
|
|
+{
|
|
+ if (verbose) puts("STUB: sandbox_compile_string called");
|
|
+ if (errorbuf != NULL) *errorbuf = NULL;
|
|
+ (void)profile;
|
|
+ return strdup("darling-sandbox-stub");
|
|
+}
|
|
+
|
|
+void* sandbox_compile_file(const char *path, char **errorbuf)
|
|
{
|
|
if (verbose) puts("STUB: sandbox_compile_file called");
|
|
- return NULL;
|
|
+ if (errorbuf != NULL) *errorbuf = NULL;
|
|
+ (void)path;
|
|
+ return strdup("darling-sandbox-stub");
|
|
}
|
|
|
|
-void* sandbox_compile_named(void)
|
|
+void* sandbox_compile_named(const char *name, char **errorbuf)
|
|
{
|
|
if (verbose) puts("STUB: sandbox_compile_named called");
|
|
- return NULL;
|
|
+ if (errorbuf != NULL) *errorbuf = NULL;
|
|
+ (void)name;
|
|
+ return strdup("darling-sandbox-stub");
|
|
}
|
|
|
|
-void* sandbox_compile_string(void)
|
|
+void* sandbox_compile_entitlements(void *entitlements, char **errorbuf)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_compile_string called");
|
|
- return NULL;
|
|
+ if (verbose) puts("STUB: sandbox_compile_entitlements called");
|
|
+ if (errorbuf != NULL) *errorbuf = NULL;
|
|
+ (void)entitlements;
|
|
+ return strdup("darling-sandbox-stub");
|
|
}
|
|
|
|
-void* sandbox_container_paths_iterate_items(void)
|
|
+int sandbox_apply(void *handle)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_container_paths_iterate_items called");
|
|
- return NULL;
|
|
+ if (verbose) puts("STUB: sandbox_apply called");
|
|
+ (void)handle;
|
|
+ return 0;
|
|
}
|
|
|
|
-void* sandbox_create_params(void)
|
|
+void sandbox_free_profile(void *handle)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_create_params called");
|
|
+ if (verbose) puts("STUB: sandbox_free_profile called");
|
|
+ free(handle);
|
|
+}
|
|
+
|
|
+void* sandbox_apply_container(void)
|
|
+{
|
|
+ if (verbose) puts("STUB: sandbox_apply_container called");
|
|
return NULL;
|
|
}
|
|
|
|
-void* sandbox_free_params(void)
|
|
+void* sandbox_container_paths_iterate_items(void)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_free_params called");
|
|
+ if (verbose) puts("STUB: sandbox_container_paths_iterate_items called");
|
|
return NULL;
|
|
}
|
|
|
|
-void* sandbox_free_profile(void)
|
|
+void* sandbox_create_params(void)
|
|
{
|
|
- if (verbose) puts("STUB: sandbox_free_profile called");
|
|
+ if (verbose) puts("STUB: sandbox_create_params called");
|
|
return NULL;
|
|
}
|
|
|
|
diff --git a/src/libsystem_coreservices/dirhelper.c b/src/libsystem_coreservices/dirhelper.c
|
|
index f22f8213d..e7fbdc92e 100644
|
|
--- a/src/libsystem_coreservices/dirhelper.c
|
|
+++ b/src/libsystem_coreservices/dirhelper.c
|
|
@@ -1,4 +1,5 @@
|
|
#include <errno.h>
|
|
+#include <limits.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
@@ -44,8 +45,18 @@ char* _dirhelper(int which, char* p, size_t limit)
|
|
case DIRHELPER_USER_LOCAL:
|
|
{
|
|
const char* home;
|
|
+ // Darling: guest home is /Users/$USER even though the host
|
|
+ // HOME points at the Linux home; trustd needs the guest path
|
|
+ // for its cache locations.
|
|
+ const char *user = getenv("USER");
|
|
+ char home_buf[PATH_MAX];
|
|
|
|
- home = getenv("HOME");
|
|
+ if (user != NULL) {
|
|
+ snprintf(home_buf, sizeof(home_buf), "/Users/%s", user);
|
|
+ home = home_buf;
|
|
+ } else {
|
|
+ home = getenv("HOME");
|
|
+ }
|
|
if (home == NULL)
|
|
home = "";
|
|
|
|
@@ -64,8 +75,15 @@ char* _dirhelper(int which, char* p, size_t limit)
|
|
case DIRHELPER_USER_LOCAL_CACHE:
|
|
{
|
|
const char* home;
|
|
+ const char *user = getenv("USER");
|
|
+ char home_buf[PATH_MAX];
|
|
|
|
- home = getenv("HOME");
|
|
+ if (user != NULL) {
|
|
+ snprintf(home_buf, sizeof(home_buf), "/Users/%s", user);
|
|
+ home = home_buf;
|
|
+ } else {
|
|
+ home = getenv("HOME");
|
|
+ }
|
|
if (home != NULL)
|
|
snprintf(p, limit, "%s/.cache/%s%s", home, dir_suffix ? dir_suffix : "", dir_suffix ? "/" : "");
|
|
else
|
|
diff --git a/src/sandbox/sandbox.c b/src/sandbox/sandbox.c
|
|
index f48cceb9e..9afa967db 100644
|
|
--- a/src/sandbox/sandbox.c
|
|
+++ b/src/sandbox/sandbox.c
|
|
@@ -10,7 +10,13 @@ const enum sandbox_filter_type SANDBOX_CHECK_NO_REPORT = SANDBOX_FILTER_NONE;
|
|
|
|
int sandbox_init(const char *profile, uint64_t flags, char **errorbuf)
|
|
{
|
|
- *errorbuf = strdup("Not implemented");
|
|
+ /* darling-lol: no seatbelt enforcement exists, so init trivially
|
|
+ * succeeds. Must tolerate errorbuf==NULL (Chromium passes NULL) and
|
|
+ * report no error on success; the old code crashed on NULL and left a
|
|
+ * bogus "Not implemented" error on success. */
|
|
+ if (errorbuf != NULL) {
|
|
+ *errorbuf = NULL;
|
|
+ }
|
|
return 0;
|
|
}
|
|
|
|
@@ -51,18 +57,36 @@ void sandbox_free_error(char *errorbuf)
|
|
|
|
int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf)
|
|
{
|
|
- *errorbuf = strdup("Not implemented");
|
|
+ /* darling-lol: see sandbox_init. */
|
|
+ if (errorbuf != NULL) {
|
|
+ *errorbuf = NULL;
|
|
+ }
|
|
return 0;
|
|
}
|
|
|
|
int sandbox_init_with_extensions(const char *profile, uint64_t flags, const char *const extensions[], char **errorbuf)
|
|
{
|
|
- *errorbuf = strdup("Not implemented");
|
|
+ /* darling-lol: see sandbox_init. Extensions are accepted and ignored. */
|
|
+ if (errorbuf != NULL) {
|
|
+ *errorbuf = NULL;
|
|
+ }
|
|
return 0;
|
|
}
|
|
|
|
int sandbox_check(pid_t pid, const char *operation, enum sandbox_filter_type type, ...)
|
|
{
|
|
+ /* darling-lol: operation==NULL is the canonical "am I sandboxed?"
|
|
+ * probe (Chromium: sandbox_check(getpid(), NULL, SANDBOX_FILTER_NONE)
|
|
+ * "returns 1 if sandboxed", and ContentMainRunnerImpl CHECKs
|
|
+ * Seatbelt::IsSandboxed() in every sandboxed child -- a 0 here
|
|
+ * suicided all helpers/GPU with a silent UD2). Since init always
|
|
+ * "succeeds", consistently report sandboxed (1). Any other query
|
|
+ * keeps 0 (allowed), as nothing is enforced. */
|
|
+ (void)pid;
|
|
+ if (operation == NULL) {
|
|
+ return 1;
|
|
+ }
|
|
+ (void)type;
|
|
return 0;
|
|
}
|
|
|
|
@@ -84,42 +108,56 @@ int sandbox_unsuspend(void)
|
|
|
|
int sandbox_issue_extension(const char *path, char **ext_token)
|
|
{
|
|
- return -1;
|
|
+ /* darling-lol: no enforcement; hand out a dummy token callers can
|
|
+ * consume/release. */
|
|
+ if (ext_token != NULL) {
|
|
+ *ext_token = strdup("darling-sandbox-stub");
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_issue_fs_extension(const char *path, uint64_t flags, char **ext_token)
|
|
{
|
|
- return -1;
|
|
+ if (ext_token != NULL) {
|
|
+ *ext_token = strdup("darling-sandbox-stub");
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_issue_fs_rw_extension(const char *path, char **ext_token)
|
|
{
|
|
- return -1;
|
|
+ if (ext_token != NULL) {
|
|
+ *ext_token = strdup("darling-sandbox-stub");
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_issue_mach_extension(const char *name, char **ext_token)
|
|
{
|
|
- return -1;
|
|
+ if (ext_token != NULL) {
|
|
+ *ext_token = strdup("darling-sandbox-stub");
|
|
+ }
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_consume_extension(const char *path, const char *ext_token)
|
|
{
|
|
- return -1;
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_consume_fs_extension(const char *ext_token, char **path)
|
|
{
|
|
- return -1;
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_consume_mach_extension(const char *ext_token, char **name)
|
|
{
|
|
- return -1;
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_release_fs_extension(const char *ext_token)
|
|
{
|
|
- return -1;
|
|
+ return 0;
|
|
}
|
|
|
|
int sandbox_container_path_for_pid(pid_t pid, char *buffer, size_t bufsize)
|
|
@@ -129,7 +167,9 @@ int sandbox_container_path_for_pid(pid_t pid, char *buffer, size_t bufsize)
|
|
|
|
int sandbox_wakeup_daemon(char **errorbuf)
|
|
{
|
|
- *errorbuf = strdup("Not implemented");
|
|
+ if (errorbuf != NULL) {
|
|
+ *errorbuf = NULL;
|
|
+ }
|
|
return -1;
|
|
}
|
|
|
|
diff --git a/src/sandbox/sandbox.h b/src/sandbox/sandbox.h
|
|
index 6fa2d088c..655554284 100644
|
|
--- a/src/sandbox/sandbox.h
|
|
+++ b/src/sandbox/sandbox.h
|
|
@@ -161,6 +161,14 @@ int sandbox_note(const char *note);
|
|
int sandbox_suspend(pid_t pid);
|
|
int sandbox_unsuspend(void);
|
|
|
|
+/* darling-lol: SeatbeltExtensions token family (used by Chromium).
|
|
+ * No enforcement exists, so issue dummy nonzero tokens and accept
|
|
+ * consume/release as no-ops. */
|
|
+int64_t sandbox_extension_issue_file(const char *path, uint32_t flags);
|
|
+int64_t sandbox_extension_issue_mach(const char *name, uint32_t flags);
|
|
+int sandbox_extension_consume(int64_t token);
|
|
+int sandbox_extension_release(int64_t token);
|
|
+
|
|
int sandbox_issue_extension(const char *path, char **ext_token);
|
|
int sandbox_issue_fs_extension(const char *path, uint64_t flags, char **ext_token);
|
|
int sandbox_issue_fs_rw_extension(const char *path, char **ext_token);
|
|
diff --git a/src/shellspawn/shellspawn.c b/src/shellspawn/shellspawn.c
|
|
index 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
|
|
index 8077b7e..0556080 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -236,7 +236,12 @@ InstallSymlink(/Volumes/SystemRoot/usr/share/zoneinfo ${CMAKE_INSTALL_PREFIX}/li
|
|
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;
|