diff --git a/Manager/src/cmdparser.cpp b/Manager/src/cmdparser.cpp index 688e48b..b9772a0 100644 --- a/Manager/src/cmdparser.cpp +++ b/Manager/src/cmdparser.cpp @@ -63,10 +63,6 @@ namespace AkVCam { size_t maxFlagsValueLength(const std::vector &flags); size_t maxStringLength(const StringVector &strings); size_t maxStringLength(const WStringVector &strings); - std::string fill(const std::string &str, size_t maxSize); - std::wstring fill(const std::wstring &str, size_t maxSize); - std::string join(const StringVector &strings, - const std::string &separator); CmdParserCommand *parserCommand(const std::string &command); const CmdParserFlags *parserFlag(const std::vector &cmdFlags, const std::string &flag); @@ -82,16 +78,20 @@ namespace AkVCam { int showDevices(const StringMap &flags, const StringVector &args); int addDevice(const StringMap &flags, const StringVector &args); int removeDevice(const StringMap &flags, const StringVector &args); + int removeDevices(const StringMap &flags, const StringVector &args); int showDeviceType(const StringMap &flags, const StringVector &args); int showDeviceDescription(const StringMap &flags, const StringVector &args); + int setDeviceDescription(const StringMap &flags, + const StringVector &args); int showSupportedFormats(const StringMap &flags, const StringVector &args); int showFormats(const StringMap &flags, const StringVector &args); int addFormat(const StringMap &flags, const StringVector &args); int removeFormat(const StringMap &flags, const StringVector &args); - int update(const StringMap &flags, const StringVector &args); + int removeFormats(const StringMap &flags, const StringVector &args); + int update(const StringMap &flags, const StringVector &args); int loadSettings(const StringMap &flags, const StringVector &args); int showConnections(const StringMap &flags, const StringVector &args); @@ -99,9 +99,11 @@ namespace AkVCam { const StringVector &args); int disconnectDevices(const StringMap &flags, const StringVector &args); - int showOptions(const StringMap &flags, const StringVector &args); - int readOption(const StringMap &flags, const StringVector &args); - int writeOption(const StringMap &flags, const StringVector &args); + int showBroadcasters(const StringMap &flags, + const StringVector &args); + int showControls(const StringMap &flags, const StringVector &args); + int readControl(const StringMap &flags, const StringVector &args); + int writeControl(const StringMap &flags, const StringVector &args); int showClients(const StringMap &flags, const StringVector &args); }; @@ -137,14 +139,22 @@ AkVCam::CmdParser::CmdParser() "DEVICE", "Remove a device.", AKVCAM_BIND_FUNC(CmdParserPrivate::removeDevice)); - this->addCommand("device-type", + this->addCommand("remove-devices", + "", + "Remove all devices.", + AKVCAM_BIND_FUNC(CmdParserPrivate::removeDevices)); + this->addCommand("type", "DEVICE", "Show device type.", AKVCAM_BIND_FUNC(CmdParserPrivate::showDeviceType)); - this->addCommand("device-description", + this->addCommand("description", "DEVICE", "Show device description.", AKVCAM_BIND_FUNC(CmdParserPrivate::showDeviceDescription)); + this->addCommand("set-description", + "DEVICE DESCRIPTION", + "Set device description.", + AKVCAM_BIND_FUNC(CmdParserPrivate::setDeviceDescription)); this->addCommand("supported-formats", "", "Show supported formats.", @@ -171,6 +181,10 @@ AkVCam::CmdParser::CmdParser() "DEVICE INDEX", "Remove device format.", AKVCAM_BIND_FUNC(CmdParserPrivate::removeFormat)); + this->addCommand("remove-formats", + "DEVICE", + "Remove all device formats.", + AKVCAM_BIND_FUNC(CmdParserPrivate::removeFormats)); this->addCommand("update", "", "Update devices.", @@ -180,29 +194,33 @@ AkVCam::CmdParser::CmdParser() "Create devices from a setting file.", AKVCAM_BIND_FUNC(CmdParserPrivate::loadSettings)); this->addCommand("connections", - "[DEVICE]", + "DEVICE", "Show device connections.", AKVCAM_BIND_FUNC(CmdParserPrivate::showConnections)); this->addCommand("connect", - "OUTPUT_DEVICE INPUTDEVICE [INPUT_DEVICE ...]", + "INPUT_DEVICE OUTPUTDEVICE [OUTPUT_DEVICE ...]", "Connect devices.", AKVCAM_BIND_FUNC(CmdParserPrivate::connectDevices)); this->addCommand("disconnect", - "OUTPUT_DEVICE INPUTDEVICE", + "INPUT_DEVICE OUTPUTDEVICE", "Disconnect devices.", AKVCAM_BIND_FUNC(CmdParserPrivate::disconnectDevices)); - this->addCommand("options", + this->addCommand("broadcasters", "DEVICE", - "Show device options.", - AKVCAM_BIND_FUNC(CmdParserPrivate::showOptions)); - this->addCommand("get-option", - "DEVICE OPTION", - "Read device option.", - AKVCAM_BIND_FUNC(CmdParserPrivate::readOption)); - this->addCommand("set-option", - "DEVICE OPTION VALUE", - "Write device option value.", - AKVCAM_BIND_FUNC(CmdParserPrivate::writeOption)); + "Show devices that sending frames to the device.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showBroadcasters)); + this->addCommand("controls", + "DEVICE", + "Show device controls.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showControls)); + this->addCommand("get-control", + "DEVICE CONTROL", + "Read device control.", + AKVCAM_BIND_FUNC(CmdParserPrivate::readControl)); + this->addCommand("set-control", + "DEVICE CONTROL VALUE", + "Write device control value.", + AKVCAM_BIND_FUNC(CmdParserPrivate::writeControl)); this->addCommand("clients", "", "Show clients using the camera.", @@ -380,13 +398,13 @@ void AkVCam::CmdParserPrivate::printFlags(const std::vector &cmd auto maxFlagsValueLen = this->maxFlagsValueLength(cmdFlags); for (auto &flag: cmdFlags) { - auto allFlags = this->join(flag.flags, ", "); + auto allFlags = join(flag.flags, ", "); std::cout << std::string(spaces.data(), indent) - << this->fill(allFlags, maxFlagsLen); + << fill(allFlags, maxFlagsLen); if (maxFlagsValueLen > 0) { std::cout << " " - << this->fill(flag.value, maxFlagsValueLen); + << fill(flag.value, maxFlagsValueLen); } std::cout << " " @@ -420,7 +438,7 @@ size_t AkVCam::CmdParserPrivate::maxFlagsLength(const std::vectorjoin(flag.flags, ", ").size(), length); + length = std::max(join(flag.flags, ", ").size(), length); return length; } @@ -455,43 +473,6 @@ size_t AkVCam::CmdParserPrivate::maxStringLength(const WStringVector &strings) return length; } -std::string AkVCam::CmdParserPrivate::fill(const std::string &str, - size_t maxSize) -{ - std::stringstream ss; - std::vector spaces(maxSize, ' '); - ss << str << std::string(spaces.data(), maxSize - str.size()); - - return ss.str(); -} - -std::wstring AkVCam::CmdParserPrivate::fill(const std::wstring &str, size_t maxSize) -{ - std::wstringstream ss; - std::vector spaces(maxSize, ' '); - ss << str << std::wstring(spaces.data(), maxSize - str.size()); - - return ss.str(); -} - -std::string AkVCam::CmdParserPrivate::join(const StringVector &strings, - const std::string &separator) -{ - std::stringstream ss; - bool append = false; - - for (auto &str: strings) { - if (append) - ss << separator; - else - append = true; - - ss << str; - } - - return ss.str(); -} - AkVCam::CmdParserCommand *AkVCam::CmdParserPrivate::parserCommand(const std::string &command) { for (auto &cmd: this->m_commands) @@ -604,9 +585,9 @@ int AkVCam::CmdParserPrivate::showHelp(const StringMap &flags, continue; std::cout << " " - << this->fill(cmd.command, maxCmdLen) + << fill(cmd.command, maxCmdLen) << " " - << this->fill(cmd.arguments, maxArgsLen) + << fill(cmd.arguments, maxArgsLen) << " " << cmd.helpString << std::endl; @@ -628,8 +609,13 @@ int AkVCam::CmdParserPrivate::showDevices(const StringMap &flags, UNUSED(flags); UNUSED(args); + auto devices = this->m_ipcBridge.devices(); + + if (devices.empty()) + return 0; + if (this->m_parseable) { - for (auto &device: this->m_ipcBridge.listDevices()) + for (auto &device: devices) std::cout << device << std::endl; } else { StringVector devicesColumn; @@ -640,7 +626,7 @@ int AkVCam::CmdParserPrivate::showDevices(const StringMap &flags, typesColumn.push_back("Type"); descriptionsColumn.push_back(L"Description"); - for (auto &device: this->m_ipcBridge.listDevices()) { + for (auto &device: devices) { devicesColumn.push_back(device); typesColumn.push_back(this->m_ipcBridge.deviceType(device) == IpcBridge::DeviceTypeInput? "Input": @@ -652,11 +638,11 @@ int AkVCam::CmdParserPrivate::showDevices(const StringMap &flags, auto typesColumnSize = this->maxStringLength(typesColumn); auto descriptionsColumnSize = this->maxStringLength(descriptionsColumn); - std::cout << this->fill("Device", devicesColumnSize) + std::cout << fill("Device", devicesColumnSize) << " | " - << this->fill("Type", typesColumnSize) + << fill("Type", typesColumnSize) << " | " - << this->fill("Description", descriptionsColumnSize) + << fill("Description", descriptionsColumnSize) << std::endl; std::cout << std::string("-") * (devicesColumnSize @@ -664,17 +650,17 @@ int AkVCam::CmdParserPrivate::showDevices(const StringMap &flags, + descriptionsColumnSize + 6) << std::endl; - for (auto &device: this->m_ipcBridge.listDevices()) { + for (auto &device: devices) { std::string type = this->m_ipcBridge.deviceType(device) == IpcBridge::DeviceTypeInput? "Input": "Output"; - std::cout << this->fill(device, devicesColumnSize) + std::cout << fill(device, devicesColumnSize) << " | " - << this->fill(type, typesColumnSize) + << fill(type, typesColumnSize) << " | "; - std::wcout << this->fill(this->m_ipcBridge.description(device), - descriptionsColumnSize) + std::wcout << fill(this->m_ipcBridge.description(device), + descriptionsColumnSize) << std::endl; } } @@ -724,11 +710,11 @@ int AkVCam::CmdParserPrivate::removeDevice(const StringMap &flags, return -1; } - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto it = std::find(devices.begin(), devices.end(), args[1]); if (it == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "Device doesn't exists." << std::endl; return -1; } @@ -738,6 +724,19 @@ int AkVCam::CmdParserPrivate::removeDevice(const StringMap &flags, return 0; } +int AkVCam::CmdParserPrivate::removeDevices(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + UNUSED(flags); + UNUSED(args); + auto devices = this->m_ipcBridge.devices(); + + for (auto &device: devices) + this->m_ipcBridge.removeDevice(device); + + return 0; +} + int AkVCam::CmdParserPrivate::showDeviceType(const StringMap &flags, const StringVector &args) { @@ -749,11 +748,11 @@ int AkVCam::CmdParserPrivate::showDeviceType(const StringMap &flags, return -1; } - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto it = std::find(devices.begin(), devices.end(), args[1]); if (it == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "device doesn't exists." << std::endl; return -1; } @@ -777,11 +776,11 @@ int AkVCam::CmdParserPrivate::showDeviceDescription(const StringMap &flags, return -1; } - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto it = std::find(devices.begin(), devices.end(), args[1]); if (it == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "Device doesn't exists." << std::endl; return -1; } @@ -791,6 +790,33 @@ int AkVCam::CmdParserPrivate::showDeviceDescription(const StringMap &flags, return 0; } +int AkVCam::CmdParserPrivate::setDeviceDescription(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + UNUSED(flags); + + if (args.size() < 3) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto deviceId = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto dit = std::find(devices.begin(), devices.end(), args[1]); + + if (dit == devices.end()) { + std::cerr << "device doesn't exists." << std::endl; + + return -1; + } + + std::wstring_convert> cv; + this->m_ipcBridge.setDescription(deviceId, cv.from_bytes(args[2])); + + return 0; +} + int AkVCam::CmdParserPrivate::showSupportedFormats(const StringMap &flags, const StringVector &args) { @@ -828,11 +854,11 @@ int AkVCam::CmdParserPrivate::showFormats(const StringMap &flags, return -1; } - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto it = std::find(devices.begin(), devices.end(), args[1]); if (it == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "device doesn't exists." << std::endl; return -1; } @@ -883,11 +909,11 @@ int AkVCam::CmdParserPrivate::addFormat(const StringMap &flags, } auto deviceId = args[1]; - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto dit = std::find(devices.begin(), devices.end(), args[1]); if (dit == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "device doesn't exists." << std::endl; return -1; } @@ -962,6 +988,8 @@ int AkVCam::CmdParserPrivate::addFormat(const StringMap &flags, int AkVCam::CmdParserPrivate::removeFormat(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + if (args.size() < 3) { std::cerr << "Not enough arguments." << std::endl; @@ -969,11 +997,11 @@ int AkVCam::CmdParserPrivate::removeFormat(const StringMap &flags, } auto deviceId = args[1]; - auto devices = this->m_ipcBridge.listDevices(); + auto devices = this->m_ipcBridge.devices(); auto dit = std::find(devices.begin(), devices.end(), args[1]); if (dit == devices.end()) { - std::cerr << "Device not doesn't exists." << std::endl; + std::cerr << "device doesn't exists." << std::endl; return -1; } @@ -1000,9 +1028,39 @@ int AkVCam::CmdParserPrivate::removeFormat(const StringMap &flags, return 0; } +int AkVCam::CmdParserPrivate::removeFormats(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + UNUSED(flags); + + if (args.size() < 2) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto deviceId = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto dit = std::find(devices.begin(), devices.end(), args[1]); + + if (dit == devices.end()) { + std::cerr << "device doesn't exists." << std::endl; + + return -1; + } + + this->m_ipcBridge.setFormats(deviceId, {}); + + return 0; +} + int AkVCam::CmdParserPrivate::update(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + UNUSED(args); + this->m_ipcBridge.update(); + return 0; } @@ -1015,34 +1073,188 @@ int AkVCam::CmdParserPrivate::loadSettings(const AkVCam::StringMap &flags, int AkVCam::CmdParserPrivate::showConnections(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + + if (args.size() < 2) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto deviceId = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto dit = std::find(devices.begin(), devices.end(), args[1]); + + if (dit == devices.end()) { + std::cerr << "device doesn't exists." << std::endl; + + return -1; + } + + for (auto &connectedDevice: this->m_ipcBridge.connections(deviceId)) + std::cout << connectedDevice << std::endl; + return 0; } int AkVCam::CmdParserPrivate::connectDevices(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + + if (args.size() < 3) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto inputDevice = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto it = std::find(devices.begin(), devices.end(), inputDevice); + + if (it == devices.end()) { + std::cerr << inputDevice << " doesn't exists." << std::endl; + + return -1; + } + + if (this->m_ipcBridge.deviceType(inputDevice) != IpcBridge::DeviceTypeInput) { + std::cerr << inputDevice << " is not an input." << std::endl; + + return -1; + } + + auto outputDevices = this->m_ipcBridge.connections(inputDevice); + + for (size_t i = 2; i < args.size(); i++) { + auto &outputDevice = args[i]; + auto it = std::find(devices.begin(), devices.end(), outputDevice); + + if (it == devices.end()) { + std::cerr << outputDevice << " doesn't exists." << std::endl; + + return -1; + } + + if (this->m_ipcBridge.deviceType(outputDevice) != IpcBridge::DeviceTypeOutput) { + std::cerr << outputDevice << " is not an output." << std::endl; + + return -1; + } + + auto cit = std::find(outputDevices.begin(), + outputDevices.end(), + outputDevice); + + if (cit == outputDevices.end()) + outputDevices.push_back(outputDevice); + } + + this->m_ipcBridge.setConnections(inputDevice, outputDevices); + return 0; } int AkVCam::CmdParserPrivate::disconnectDevices(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + + if (args.size() < 3) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto inputDevice = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto it = std::find(devices.begin(), devices.end(), inputDevice); + + if (it == devices.end()) { + std::cerr << inputDevice << " doesn't exists." << std::endl; + + return -1; + } + + if (this->m_ipcBridge.deviceType(inputDevice) != IpcBridge::DeviceTypeInput) { + std::cerr << inputDevice << " is not an input." << std::endl; + + return -1; + } + + auto outputDevices = this->m_ipcBridge.connections(inputDevice); + auto &outputDevice = args[2]; + auto dit = std::find(devices.begin(), devices.end(), outputDevice); + + if (dit == devices.end()) { + std::cerr << outputDevice << " doesn't exists." << std::endl; + + return -1; + } + + if (this->m_ipcBridge.deviceType(outputDevice) != IpcBridge::DeviceTypeOutput) { + std::cerr << outputDevice << " is not an output." << std::endl; + + return -1; + } + + auto cit = std::find(outputDevices.begin(), + outputDevices.end(), + outputDevice); + + if (cit == outputDevices.end()) + outputDevices.push_back(outputDevice); + + this->m_ipcBridge.setConnections(inputDevice, outputDevices); + return 0; } -int AkVCam::CmdParserPrivate::showOptions(const StringMap &flags, +int AkVCam::CmdParserPrivate::showBroadcasters(const AkVCam::StringMap &flags, + const AkVCam::StringVector &args) +{ + UNUSED(flags); + + if (args.size() < 2) { + std::cerr << "Not enough arguments." << std::endl; + + return -1; + } + + auto ouputDevice = args[1]; + auto devices = this->m_ipcBridge.devices(); + auto it = std::find(devices.begin(), devices.end(), ouputDevice); + + if (it == devices.end()) { + std::cerr << ouputDevice << " doesn't exists." << std::endl; + + return -1; + } + + if (this->m_ipcBridge.deviceType(ouputDevice) != IpcBridge::DeviceTypeOutput) { + std::cerr << ouputDevice << " is not an output." << std::endl; + + return -1; + } + + std::cout << this->m_ipcBridge.broadcaster(ouputDevice) << std::endl; + + return 0; +} + +int AkVCam::CmdParserPrivate::showControls(const StringMap &flags, const StringVector &args) { return 0; } -int AkVCam::CmdParserPrivate::readOption(const StringMap &flags, +int AkVCam::CmdParserPrivate::readControl(const StringMap &flags, const StringVector &args) { return 0; } -int AkVCam::CmdParserPrivate::writeOption(const StringMap &flags, +int AkVCam::CmdParserPrivate::writeControl(const StringMap &flags, const StringVector &args) { return 0; @@ -1051,6 +1263,50 @@ int AkVCam::CmdParserPrivate::writeOption(const StringMap &flags, int AkVCam::CmdParserPrivate::showClients(const StringMap &flags, const StringVector &args) { + UNUSED(flags); + UNUSED(args); + + auto clients = this->m_ipcBridge.clientsPids(); + + if (clients.empty()) + return 0; + + if (this->m_parseable) { + for (auto &pid: clients) + std::cout << pid + << " " + << this->m_ipcBridge.clientExe(pid) + << std::endl; + } else { + StringVector pidsColumn; + StringVector exesColumn; + + pidsColumn.push_back("Pid"); + exesColumn.push_back("Executable"); + + for (auto &pid: clients) { + pidsColumn.push_back(std::to_string(pid)); + exesColumn.push_back(this->m_ipcBridge.clientExe(pid)); + } + + auto pidsColumnSize = this->maxStringLength(pidsColumn); + auto exesColumnSize = this->maxStringLength(exesColumn); + + std::cout << fill("Pid", pidsColumnSize) + << " | " + << fill("Executable", exesColumnSize) + << std::endl; + std::cout << std::string("-") + * (pidsColumnSize + exesColumnSize + 4) + << std::endl; + + for (auto &pid: clients) + std::cout << fill(std::to_string(pid), pidsColumnSize) + << " | " + << fill(this->m_ipcBridge.clientExe(pid), exesColumnSize) + << std::endl; + } + return 0; } diff --git a/VCamUtils/src/ipcbridge.h b/VCamUtils/src/ipcbridge.h index 3e1090d..8cab86c 100644 --- a/VCamUtils/src/ipcbridge.h +++ b/VCamUtils/src/ipcbridge.h @@ -121,10 +121,12 @@ namespace AkVCam void unregisterPeer(); // List available devices. - std::vector listDevices() const; + std::vector devices() const; // Return human readable description of the device. std::wstring description(const std::string &deviceId) const; + void setDescription(const std::string &deviceId, + const std::wstring &description); // Output pixel formats supported by the driver. std::vector supportedPixelFormats(DeviceType type) const; @@ -134,6 +136,8 @@ namespace AkVCam // Return supported formats for the device. std::vector formats(const std::string &deviceId) const; + void setFormats(const std::string &deviceId, + const std::vector &formats); // Return return the status of the device. std::string broadcaster(const std::string &deviceId) const; @@ -172,26 +176,10 @@ namespace AkVCam const VideoFormat &format, int index=-1); void removeFormat(const std::string &deviceId, int index); - - // Create a device definition. - std::string deviceCreate(const std::wstring &description, - const std::vector &formats, - DeviceType type); - - // Edit a device definition. - bool deviceEdit(const std::string &deviceId, - const std::wstring &description, - const std::vector &formats); - - // Change device description. - bool changeDescription(const std::string &deviceId, - const std::wstring &description); - - // Remove a device definition. - bool deviceDestroy(const std::string &deviceId); - - // Remove all device definitions. - bool destroyAllDevices(); + void update(); + std::vector connections(const std::string &deviceId); + void setConnections(const std::string &deviceId, + const std::vector &connectedDevices); void updateDevices(); diff --git a/VCamUtils/src/utils.cpp b/VCamUtils/src/utils.cpp index cb118a1..97c8fe8 100644 --- a/VCamUtils/src/utils.cpp +++ b/VCamUtils/src/utils.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "utils.h" @@ -157,6 +158,39 @@ std::wstring AkVCam::trimmed(const std::wstring &str) return str.substr(size_t(left), strippedLen); } +std::string AkVCam::fill(const std::string &str, size_t maxSize) +{ + std::stringstream ss; + std::vector spaces(maxSize, ' '); + ss << str << std::string(spaces.data(), maxSize - str.size()); + + return ss.str(); +} + +std::wstring AkVCam::fill(const std::wstring &str, size_t maxSize) +{ + std::wstringstream ss; + std::vector spaces(maxSize, ' '); + ss << str << std::wstring(spaces.data(), maxSize - str.size()); + + return ss.str(); +} + +std::string AkVCam::join(const std::vector &strs, + const std::string &separator) +{ + std::stringstream ss; + + for (size_t i = 0; i < strs.size(); i++) { + if (i > 0) + ss << separator; + + ss << strs[i]; + } + + return ss.str(); +} + std::vector AkVCam::split(const std::string &str, char separator) { if (str.empty()) diff --git a/VCamUtils/src/utils.h b/VCamUtils/src/utils.h index d117978..76e1003 100644 --- a/VCamUtils/src/utils.h +++ b/VCamUtils/src/utils.h @@ -153,6 +153,10 @@ namespace AkVCam bool isEqualFile(const std::wstring &file1, const std::wstring &file2); std::string trimmed(const std::string &str); std::wstring trimmed(const std::wstring &str); + std::string fill(const std::string &str, size_t maxSize); + std::wstring fill(const std::wstring &str, size_t maxSize); + std::string join(const std::vector &strs, + const std::string &separator); std::vector split(const std::string &str, char separator); } diff --git a/cmio/Assistant/src/main.cpp b/cmio/Assistant/src/main.cpp index 6190b00..8f87b76 100644 --- a/cmio/Assistant/src/main.cpp +++ b/cmio/Assistant/src/main.cpp @@ -33,6 +33,7 @@ int main(int argc, char **argv) auto loglevel = AkVCam::Preferences::readInt("loglevel", AKVCAM_LOGLEVEL_DEFAULT); AkVCam::Logger::setLogLevel(loglevel); + AkLogDebug() << "Creating Service: " << CMIO_ASSISTANT_NAME << std::endl; auto server = xpc_connection_create_mach_service(CMIO_ASSISTANT_NAME, NULL, @@ -50,6 +51,8 @@ int main(int argc, char **argv) break; } + AkLogDebug() << "Setting up handler" << std::endl; + xpc_connection_set_event_handler(server, ^(xpc_object_t event) { auto type = xpc_get_type(event); @@ -70,7 +73,9 @@ int main(int argc, char **argv) xpc_connection_resume(client); }); + AkLogDebug() << "Resuming connection" << std::endl; xpc_connection_resume(server); + AkLogDebug() << "Running loop" << std::endl; CFRunLoopRun(); xpc_release(server); diff --git a/cmio/PlatformUtils/src/preferences.cpp b/cmio/PlatformUtils/src/preferences.cpp index 3497dca..b63a012 100644 --- a/cmio/PlatformUtils/src/preferences.cpp +++ b/cmio/PlatformUtils/src/preferences.cpp @@ -103,6 +103,13 @@ void AkVCam::Preferences::write(const std::string &key, double value) CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID); } +void AkVCam::Preferences::write(const std::string &key, + std::vector &value) +{ + AkLogFunction(); + write(key, join(value, ",")); +} + std::shared_ptr AkVCam::Preferences::read(const std::string &key) { AkLogFunction(); @@ -418,7 +425,7 @@ std::string AkVCam::Preferences::createDevicePath() int AkVCam::Preferences::cameraFromPath(const std::string &path) { for (size_t i = 0; i < camerasCount(); i++) - if (cameraPath(i) == path && !cameraFormats(i).empty()) + if (cameraPath(i) == path) return int(i); return -1; @@ -438,23 +445,26 @@ bool AkVCam::Preferences::cameraIsInput(size_t cameraIndex) return readBool("cameras." + std::to_string(cameraIndex) + ".isinput"); } -bool AkVCam::Preferences::cameraIsInput(const std::string &path) -{ - auto cameraIndex = cameraFromPath(path); - - if (cameraIndex < 0) - return {}; - - return cameraIsInput(cameraIndex); -} - std::wstring AkVCam::Preferences::cameraDescription(size_t cameraIndex) { + if (cameraIndex >= camerasCount()) + return {}; + return readWString("cameras." + std::to_string(cameraIndex) + ".description"); } +void AkVCam::Preferences::cameraSetDescription(size_t cameraIndex, + const std::wstring &description) +{ + if (cameraIndex >= camerasCount()) + return; + + write("cameras." + std::to_string(cameraIndex) + ".description", + description); +} + std::string AkVCam::Preferences::cameraPath(size_t cameraIndex) { return readString("cameras." @@ -501,6 +511,35 @@ std::vector AkVCam::Preferences::cameraFormats(size_t camer return formats; } +void AkVCam::Preferences::cameraSetFormats(size_t cameraIndex, + const std::vector &formats) +{ + AkLogFunction(); + + if (cameraIndex >= camerasCount()) + return; + + write("cameras." + + std::to_string(cameraIndex) + + ".formats", + int(formats.size())); + + for (size_t i = 0; i < formats.size(); i++) { + auto &format = formats[i]; + auto prefix = "cameras." + + std::to_string(cameraIndex) + + ".formats." + + std::to_string(i); + auto formatStr = VideoFormat::stringFromFourcc(format.fourcc()); + write(prefix + ".format", formatStr); + write(prefix + ".width", format.width()); + write(prefix + ".height", format.height()); + write(prefix + ".fps", format.minimumFrameRate().toString()); + } + + sync(); +} + void AkVCam::Preferences::cameraAddFormat(size_t cameraIndex, const AkVCam::VideoFormat &format, int index) @@ -537,7 +576,7 @@ void AkVCam::Preferences::cameraRemoveFormat(size_t cameraIndex, int index) { AkLogFunction(); - if (cameraIndex < 0 || !cameraIsInput(cameraIndex)) + if (!cameraIsInput(cameraIndex)) return; auto formats = cameraFormats(cameraIndex); @@ -571,32 +610,71 @@ void AkVCam::Preferences::cameraRemoveFormat(size_t cameraIndex, int index) std::vector AkVCam::Preferences::cameraConnections(size_t cameraIndex) { AkLogFunction(); - - if (cameraIndex < 0 || !cameraIsInput(cameraIndex)) - return {}; - std::vector cameraConnections; - auto connections = readStringList("cameras." - + std::to_string(cameraIndex) - + ".connections"); - for (auto &connection: connections) { - if (connection.empty()) - continue; + if (cameraIsInput(cameraIndex)) { + auto connections = readStringList("cameras." + + std::to_string(cameraIndex) + + ".connections"); - size_t pos = 0; - auto outputIndex = std::stoi(connection, &pos); + for (auto &connection: connections) { + if (connection.empty()) + continue; - if (pos == connection.size()) - cameraConnections.push_back(cameraPath(outputIndex)); + size_t pos = 0; + auto outputIndex = std::stoi(connection, &pos); + + if (pos == connection.size()) + cameraConnections.push_back(cameraPath(outputIndex)); + } + } else { + for (size_t i = 0; i < camerasCount(); i++) + if (cameraIsInput(i)) { + auto connections = readStringList("cameras." + + std::to_string(i) + + ".connections"); + + for (auto &connection: connections) { + if (connection.empty()) + continue; + + size_t pos = 0; + auto outputIndex = std::stoi(connection, &pos); + + if (pos == connection.size() && size_t(outputIndex) == cameraIndex) + cameraConnections.push_back(cameraPath(i)); + } + } } return cameraConnections; } -std::vector AkVCam::Preferences::cameraConnections(const std::string &path) +void AkVCam::Preferences::cameraSetConnections(size_t cameraIndex, + const std::vector &connectedDevices) { AkLogFunction(); - return cameraConnections(cameraFromPath(path)); + if (!cameraIsInput(cameraIndex)) + return; + + std::vector connections; + + for (auto &connection: connectedDevices) { + if (connection.empty()) + continue; + + auto outputIndex = cameraFromPath(connection); + + if (outputIndex < 0) + continue; + + if (cameraIsInput(outputIndex)) + continue; + + connections.push_back(std::to_string(outputIndex)); + } + + write("cameras." + std::to_string(cameraIndex) + ".connections", + connections); } diff --git a/cmio/PlatformUtils/src/preferences.h b/cmio/PlatformUtils/src/preferences.h index e266642..508eb01 100644 --- a/cmio/PlatformUtils/src/preferences.h +++ b/cmio/PlatformUtils/src/preferences.h @@ -39,6 +39,7 @@ namespace AkVCam void write(const std::string &key, const std::wstring &value); void write(const std::string &key, int value); void write(const std::string &key, double value); + void write(const std::string &key, std::vector &value); std::shared_ptr read(const std::string &key); std::string readString(const std::string &key, const std::string &defaultValue={}); @@ -69,18 +70,24 @@ namespace AkVCam int cameraFromPath(const std::string &path); bool cameraExists(const std::string &path); bool cameraIsInput(size_t cameraIndex); - bool cameraIsInput(const std::string &path); std::wstring cameraDescription(size_t cameraIndex); + void cameraSetDescription(size_t cameraIndex, + const std::wstring &description); std::string cameraPath(size_t cameraIndex); size_t formatsCount(size_t cameraIndex); VideoFormat cameraFormat(size_t cameraIndex, size_t formatIndex); std::vector cameraFormats(size_t cameraIndex); + void cameraSetFormats(size_t cameraIndex, + const std::vector &formats); + void cameraFormats(size_t cameraIndex, + const std::vector &formats); void cameraAddFormat(size_t cameraIndex, const VideoFormat &format, int index); void cameraRemoveFormat(size_t cameraIndex, int index); std::vector cameraConnections(size_t cameraIndex); - std::vector cameraConnections(const std::string &path); + void cameraSetConnections(size_t cameraIndex, + const std::vector &connectedDevices); } } diff --git a/cmio/PlatformUtils/src/utils.cpp b/cmio/PlatformUtils/src/utils.cpp index c5168fd..9c8eef1 100644 --- a/cmio/PlatformUtils/src/utils.cpp +++ b/cmio/PlatformUtils/src/utils.cpp @@ -82,11 +82,20 @@ std::string AkVCam::stringFromCFType(CFTypeRef cfType) if (data) return std::string(data, len); - auto cstr = new char[len]; - CFStringGetCString(CFStringRef(cfType), - cstr, - CFIndex(len), - kCFStringEncodingUTF8); + auto maxLen = + CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1; + auto cstr = new char[maxLen]; + memset(cstr, 0, maxLen); + + if (!CFStringGetCString(CFStringRef(cfType), + cstr, + maxLen, + kCFStringEncodingUTF8)) { + delete [] cstr; + + return {}; + } + std::string str(cstr, len); delete [] cstr; diff --git a/cmio/VCamIPC/src/ipcbridge.mm b/cmio/VCamIPC/src/ipcbridge.mm index eac4c09..e528f3d 100644 --- a/cmio/VCamIPC/src/ipcbridge.mm +++ b/cmio/VCamIPC/src/ipcbridge.mm @@ -323,38 +323,18 @@ void AkVCam::IpcBridge::unregisterPeer() this->d->m_portName.clear(); } -std::vector AkVCam::IpcBridge::listDevices() const +std::vector AkVCam::IpcBridge::devices() const { AkLogFunction(); - - if (!this->d->m_serverMessagePort) - return {}; - - auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICES); - auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, - dictionary); - xpc_release(dictionary); - auto replyType = xpc_get_type(reply); - - if (replyType != XPC_TYPE_DICTIONARY) { - xpc_release(reply); - - return {}; - } - - auto devicesList = xpc_dictionary_get_array(reply, "devices"); + auto nCameras = Preferences::camerasCount(); std::vector devices; - - for (size_t i = 0; i < xpc_array_get_count(devicesList); i++) - devices.push_back(xpc_array_get_string(devicesList, i)); - - xpc_release(reply); - AkLogInfo() << "Devices:" << std::endl; - for (auto &device: devices) - AkLogInfo() << " " << device << std::endl; + for (size_t i = 0; i < nCameras; i++) { + auto deviceId = Preferences::cameraPath(i); + devices.push_back(deviceId); + AkLogInfo() << " " << deviceId << std::endl; + } return devices; } @@ -362,37 +342,24 @@ std::vector AkVCam::IpcBridge::listDevices() const std::wstring AkVCam::IpcBridge::description(const std::string &deviceId) const { AkLogFunction(); + auto cameraIndex = Preferences::cameraFromPath(deviceId); - if (!this->d->m_serverMessagePort) - return {}; + return Preferences::cameraDescription(cameraIndex); +} - auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESCRIPTION); - xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); - auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, - dictionary); - xpc_release(dictionary); - auto replyType = xpc_get_type(reply); +void AkVCam::IpcBridge::setDescription(const std::string &deviceId, + const std::wstring &description) +{ + AkLogFunction(); + auto cameraIndex = Preferences::cameraFromPath(deviceId); - if (replyType != XPC_TYPE_DICTIONARY) { - xpc_release(reply); - - return {}; - } - - size_t len = 0; - auto data = reinterpret_cast(xpc_dictionary_get_data(reply, - "description", - &len)); - std::wstring description(data, len / sizeof(wchar_t)); - xpc_release(reply); - - return description; + return Preferences::cameraSetDescription(cameraIndex, description); } std::vector AkVCam::IpcBridge::supportedPixelFormats(DeviceType type) const { - UNUSED(type); + if (type == DeviceTypeInput) + return {PixelFormatRGB24}; return { PixelFormatRGB32, @@ -404,48 +371,25 @@ std::vector AkVCam::IpcBridge::supportedPixelFormats(Device AkVCam::PixelFormat AkVCam::IpcBridge::defaultPixelFormat(DeviceType type) const { - UNUSED(type); - - return PixelFormatYUY2; + return type == DeviceTypeInput? + PixelFormatRGB24: + PixelFormatYUY2; } std::vector AkVCam::IpcBridge::formats(const std::string &deviceId) const { AkLogFunction(); + auto cameraIndex = Preferences::cameraFromPath(deviceId); - if (!this->d->m_serverMessagePort) - return {}; + return Preferences::cameraFormats(cameraIndex); +} - auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_FORMATS); - xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); - auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, - dictionary); - xpc_release(dictionary); - auto replyType = xpc_get_type(reply); +void AkVCam::IpcBridge::setFormats(const std::string &deviceId, + const std::vector &formats) +{ + auto cameraIndex = Preferences::cameraFromPath(deviceId); - if (replyType != XPC_TYPE_DICTIONARY) { - xpc_release(reply); - - return {}; - } - - auto formatsList = xpc_dictionary_get_array(reply, "formats"); - std::vector formats; - - for (size_t i = 0; i < xpc_array_get_count(formatsList); i++) { - auto format = xpc_array_get_dictionary(formatsList, i); - auto fourcc = FourCC(xpc_dictionary_get_uint64(format, "fourcc")); - auto width = int(xpc_dictionary_get_int64(format, "width")); - auto height = int(xpc_dictionary_get_int64(format, "height")); - auto fps = Fraction(xpc_dictionary_get_string(format, "fps")); - - formats.push_back(VideoFormat(fourcc, width, height, {fps})); - } - - xpc_release(reply); - - return formats; + return Preferences::cameraSetFormats(cameraIndex, formats); } std::string AkVCam::IpcBridge::broadcaster(const std::string &deviceId) const @@ -699,7 +643,9 @@ std::string AkVCam::IpcBridge::clientExe(uint64_t pid) const AkVCam::IpcBridge::DeviceType AkVCam::IpcBridge::deviceType(const std::string &deviceId) { - return Preferences::cameraIsInput(deviceId)? + auto cameraIndex = Preferences::cameraFromPath(deviceId); + + return Preferences::cameraIsInput(cameraIndex)? DeviceTypeInput: DeviceTypeOutput; } @@ -732,124 +678,31 @@ void AkVCam::IpcBridge::removeFormat(const std::string &deviceId, int index) index); } -std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, - const std::vector &formats, - DeviceType type) -{ - AkLogFunction(); - - if (!this->d->m_serverMessagePort || !this->d->m_messagePort) { - this->d->m_error = L"Can't register peer"; - - return {}; - } - - auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_CREATE); - xpc_dictionary_set_string(dictionary, "port", this->d->m_portName.c_str()); - xpc_dictionary_set_data(dictionary, - "description", - description.c_str(), - description.size() * sizeof(wchar_t)); - xpc_dictionary_set_bool(dictionary, "isinput", type == DeviceTypeInput); - auto formatsList = xpc_array_create(nullptr, 0); - - for (auto &format: formats) { - auto dictFormat = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_uint64(dictFormat, "fourcc", format.fourcc()); - xpc_dictionary_set_int64(dictFormat, "width", format.width()); - xpc_dictionary_set_int64(dictFormat, "height", format.height()); - xpc_dictionary_set_string(dictFormat, "fps", format.minimumFrameRate().toString().c_str()); - xpc_array_append_value(formatsList, dictFormat); - } - - xpc_dictionary_set_value(dictionary, "formats", formatsList); - - auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, - dictionary); - xpc_release(dictionary); - auto replyType = xpc_get_type(reply); - - if (replyType != XPC_TYPE_DICTIONARY) { - this->d->m_error = L"Can't set virtual camera formats"; - xpc_release(reply); - - return {}; - } - - std::string deviceId(xpc_dictionary_get_string(reply, "device")); - xpc_release(reply); - - return deviceId; -} - -bool AkVCam::IpcBridge::deviceEdit(const std::string &deviceId, - const std::wstring &description, - const std::vector &formats) -{ - AkLogFunction(); - - auto type = this->deviceType(deviceId); - this->deviceDestroy(deviceId); - - if (this->deviceCreate(description.empty()? - L"AvKys Virtual Camera": - description, - formats, - type).empty()) - return false; - - return true; -} - -bool AkVCam::IpcBridge::changeDescription(const std::string &deviceId, - const std::wstring &description) -{ - AkLogFunction(); - - auto formats = this->formats(deviceId); - - if (formats.empty()) - return false; - - auto type = this->deviceType(deviceId); - this->deviceDestroy(deviceId); - - if (this->deviceCreate(description.empty()? - L"AvKys Virtual Camera": - description, - formats, - type).empty()) - return false; - - return true; -} - -bool AkVCam::IpcBridge::deviceDestroy(const std::string &deviceId) +void AkVCam::IpcBridge::update() { AkLogFunction(); if (!this->d->m_serverMessagePort) - return false; + return; auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY); - xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); - xpc_connection_send_message(this->d->m_serverMessagePort, - dictionary); + xpc_dictionary_set_int64(dictionary, + "message", + AKVCAM_ASSISTANT_MSG_DEVICE_UPDATE); + xpc_connection_send_message(this->d->m_serverMessagePort, dictionary); xpc_release(dictionary); - - return true; } -bool AkVCam::IpcBridge::destroyAllDevices() +std::vector AkVCam::IpcBridge::connections(const std::string &deviceId) { - AkLogFunction(); + return Preferences::cameraConnections(Preferences::cameraFromPath(deviceId)); +} - for (auto &device: this->listDevices()) - this->deviceDestroy(device); - - return true; +void AkVCam::IpcBridge::setConnections(const std::string &deviceId, + const std::vector &connectedDevices) +{ + Preferences::cameraSetConnections(Preferences::cameraFromPath(deviceId), + connectedDevices); } void AkVCam::IpcBridge::updateDevices() diff --git a/cmio/VirtualCamera/src/plugininterface.cpp b/cmio/VirtualCamera/src/plugininterface.cpp index 821e80a..da35316 100644 --- a/cmio/VirtualCamera/src/plugininterface.cpp +++ b/cmio/VirtualCamera/src/plugininterface.cpp @@ -234,7 +234,7 @@ OSStatus AkVCam::PluginInterface::InitializeWithObjectID(CMIOObjectID objectID) this->m_objectID = objectID; - for (auto deviceId: this->d->m_ipcBridge.listDevices()) + for (auto &deviceId: this->d->m_ipcBridge.devices()) this->deviceAdded(this, deviceId); return kCMIOHardwareNoError; @@ -301,7 +301,7 @@ void AkVCam::PluginInterface::devicesUpdated(void *userData, void *unused) for (auto &deviceId: devices) self->destroyDevice(deviceId); - for (auto &deviceId: self->d->m_ipcBridge.listDevices()) { + for (auto &deviceId: self->d->m_ipcBridge.devices()) { auto description = self->d->m_ipcBridge.description(deviceId); auto formats = self->d->m_ipcBridge.formats(deviceId); auto type = self->d->m_ipcBridge.deviceType(deviceId); @@ -315,7 +315,8 @@ void AkVCam::PluginInterface::frameReady(void *userData, { AkLogFunction(); auto self = reinterpret_cast(userData); - auto connections = Preferences::cameraConnections(deviceId); + auto cameraIndex = Preferences::cameraFromPath(deviceId); + auto connections = Preferences::cameraConnections(cameraIndex); for (auto device: self->m_devices) if (std::find(connections.begin(), diff --git a/cmio/cmio.pri b/cmio/cmio.pri index 9587e1e..b1ff441 100644 --- a/cmio/cmio.pri +++ b/cmio/cmio.pri @@ -19,7 +19,7 @@ isEmpty(CMIO_PLUGINS_DAL_PATH): CMIO_PLUGINS_DAL_PATH = /Library/CoreMediaIO/Plug-Ins/DAL isEmpty(CMIO_DAEMONS_PATH): - CMIO_DAEMONS_PATH = ~/Library/LaunchAgents + CMIO_DAEMONS_PATH = /Library/LaunchAgents isEmpty(CMIO_PLUGIN_NAME): CMIO_PLUGIN_NAME = AkVirtualCamera isEmpty(CMIO_PLUGIN_ASSISTANT_NAME): diff --git a/dshow/VCamIPC/src/ipcbridge.cpp b/dshow/VCamIPC/src/ipcbridge.cpp index 95f581a..d25e629 100644 --- a/dshow/VCamIPC/src/ipcbridge.cpp +++ b/dshow/VCamIPC/src/ipcbridge.cpp @@ -303,7 +303,7 @@ void AkVCam::IpcBridge::unregisterPeer() this->d->m_portName.clear(); } -std::vector AkVCam::IpcBridge::listDevices() const +std::vector AkVCam::IpcBridge::devices() const { AkLogFunction(); std::vector devices; @@ -649,7 +649,7 @@ bool AkVCam::IpcBridge::needsRestart(Operation operation) const { return operation == OperationDestroyAll || (operation == OperationDestroy - && this->listDevices().size() == 1); + && this->devices().size() == 1); } bool AkVCam::IpcBridge::canApply(AkVCam::IpcBridge::Operation operation) const diff --git a/ports/deploy/installscript.mac.qs b/ports/deploy/installscript.mac.qs index 3acfdfe..58b8b97 100644 --- a/ports/deploy/installscript.mac.qs +++ b/ports/deploy/installscript.mac.qs @@ -23,6 +23,15 @@ Component.prototype.createOperations = function() "rm", "-rf", "/Library/CoreMediaIO/Plug-Ins/DAL/@Name@.plugin"); + + component.addElevatedOperation("Execute", + "chmod", + "+x", + "@TargetDir@/@Name@.plugin/Contents/Resources/AkVCamAssistant"); + component.addElevatedOperation("Execute", + "chmod", + "+x", + "@TargetDir@/@Name@.plugin/Contents/Resources/AkVCamManager"); } // Create a symlink to the plugin.