From bde41facc9d77f7a3450c3880c7d5809b43b4cd8 Mon Sep 17 00:00:00 2001 From: Gonzalo Exequiel Pedone Date: Sat, 11 Jul 2020 14:22:23 -0300 Subject: [PATCH] Manager command line options more or less defined. Working on input device support for Mac. --- Manager/Manager.pro | 6 +- Manager/src/cmdparser.cpp | 647 +++++++++++++++++++++ Manager/src/cmdparser.h | 58 ++ Manager/src/main.cpp | 4 +- VCamUtils/src/ipcbridge.h | 24 +- VCamUtils/src/utils.cpp | 22 + VCamUtils/src/utils.h | 1 + cmio/Assistant/src/assistant.cpp | 161 ++--- cmio/Assistant/src/assistantglobals.h | 6 +- cmio/PlatformUtils/src/preferences.cpp | 89 ++- cmio/PlatformUtils/src/preferences.h | 15 +- cmio/VCamIPC/src/ipcbridge.mm | 322 +--------- cmio/VirtualCamera/src/plugininterface.cpp | 26 +- cmio/VirtualCamera/src/plugininterface.h | 3 +- cmio/VirtualCamera/src/queue.h | 2 +- cmio/VirtualCamera/src/stream.cpp | 143 ++++- cmio/VirtualCamera/src/stream.h | 7 + dshow/VCamIPC/src/ipcbridge.cpp | 3 +- 18 files changed, 1128 insertions(+), 411 deletions(-) create mode 100644 Manager/src/cmdparser.cpp create mode 100644 Manager/src/cmdparser.h diff --git a/Manager/Manager.pro b/Manager/Manager.pro index ece2ca5..409606d 100644 --- a/Manager/Manager.pro +++ b/Manager/Manager.pro @@ -36,8 +36,12 @@ CONFIG -= qt TARGET = AkVCamManager +HEADERS = \ + src/cmdparser.h + SOURCES = \ - src/main.cpp + src/main.cpp \ + src/cmdparser.cpp INCLUDEPATH += \ .. \ diff --git a/Manager/src/cmdparser.cpp b/Manager/src/cmdparser.cpp new file mode 100644 index 0000000..d99f74f --- /dev/null +++ b/Manager/src/cmdparser.cpp @@ -0,0 +1,647 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include + +#include "cmdparser.h" + +#define AKVCAM_BIND_FUNC(member) \ + std::bind(&member, this->d, std::placeholders::_1, std::placeholders::_2) + +namespace AkVCam { + struct CmdParserFlags + { + StringVector flags; + std::string value; + std::string helpString; + }; + + struct CmdParserCommand + { + std::string command; + std::string arguments; + std::string helpString; + ProgramOptionsFunc func; + std::vector flags; + }; + + class CmdParserPrivate + { + public: + std::vector m_commands; + + std::string basename(const std::string &path); + void printFlags(const std::vector &cmdFlags, + size_t indent); + size_t maxCommandLength(); + size_t maxArgumentsLength(); + size_t maxFlagsLength(const std::vector &flags); + size_t maxFlagsValueLength(const std::vector &flags); + std::string fill(const std::string &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); + bool containsFlag(const StringMap &flags, + const std::string &command, + const std::string &flagAlias); + int defaultHandler(const StringMap &flags, + const StringVector &args); + int showHelp(const StringMap &flags, const StringVector &args); + 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 showDeviceType(const StringMap &flags, + const StringVector &args); + int showDeviceDescription(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 showConnections(const StringMap &flags, + const StringVector &args); + int connectDevices(const StringMap &flags, + 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 showClients(const StringMap &flags, const StringVector &args); + }; +} + +AkVCam::CmdParser::CmdParser() +{ + this->d = new CmdParserPrivate(); + this->d->m_commands.push_back({}); + this->setDefaultFuntion(AKVCAM_BIND_FUNC(CmdParserPrivate::defaultHandler)); + this->addFlags("", + {"-h", "--help"}, + "Show help."); + this->addFlags("", + {"-p", "--parseable"}, + "Show parseable output."); + this->addCommand("devices", + "", + "List devices.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showDevices)); + this->addCommand("add-device", + "DESCRIPTION", + "Add a new device.", + AKVCAM_BIND_FUNC(CmdParserPrivate::addDevice)); + this->addFlags("add-device", + {"-i", "--input"}, + "Add an input device."); + this->addFlags("add-device", + {"-o", "--output"}, + "Add an output device."); + this->addCommand("remove-device", + "DEVICE", + "Remove a device.", + AKVCAM_BIND_FUNC(CmdParserPrivate::removeDevice)); + this->addCommand("device-type", + "DEVICE", + "Show device type.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showDeviceType)); + this->addCommand("device-description", + "DEVICE", + "Show device description.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showDeviceDescription)); + this->addCommand("supported-formats", + "", + "Show supported formats.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showSupportedFormats)); + this->addFlags("supported-formats", + {"-i", "--input"}, + "Show supported input formats."); + this->addFlags("supported-formats", + {"-o", "--output"}, + "Show supported output formats."); + this->addCommand("formats", + "DEVICE", + "Show device formats.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showFormats)); + this->addCommand("add-format", + "DEVICE FORMAT WIDTH HEIGHT FPS", + "Add a new device format.", + AKVCAM_BIND_FUNC(CmdParserPrivate::addFormat)); + this->addFlags("add-format", + {"-i", "--index"}, + "INDEX", + "Add format at INDEX."); + this->addCommand("remove-format", + "INDEX", + "Remove device format.", + AKVCAM_BIND_FUNC(CmdParserPrivate::removeFormat)); + this->addCommand("update", + "", + "Update devices.", + AKVCAM_BIND_FUNC(CmdParserPrivate::update)); + this->addCommand("connections", + "[DEVICE]", + "Show device connections.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showConnections)); + this->addCommand("connect", + "OUTPUT_DEVICE INPUTDEVICE [INPUT_DEVICE ...]", + "Connect devices.", + AKVCAM_BIND_FUNC(CmdParserPrivate::connectDevices)); + this->addCommand("disconnect", + "OUTPUT_DEVICE INPUTDEVICE", + "Disconnect devices.", + AKVCAM_BIND_FUNC(CmdParserPrivate::disconnectDevices)); + this->addCommand("options", + "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)); + this->addCommand("clients", + "", + "Show clients using the camera.", + AKVCAM_BIND_FUNC(CmdParserPrivate::showClients)); +} + +AkVCam::CmdParser::~CmdParser() +{ + delete this->d; +} + +int AkVCam::CmdParser::parse(int argc, char **argv) +{ + auto program = this->d->basename(argv[0]); + auto command = &this->d->m_commands[0]; + StringMap flags; + StringVector arguments {program}; + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg[0] == '-') { + auto flag = this->d->parserFlag(command->flags, arg); + + if (!flag) { + if (command->command.empty()) + std::cout << "Invalid option '" + << arg + << "'" + << std::endl; + else + std::cout << "Invalid option '" + << arg << "' for '" + << command->command + << "'" + << std::endl; + + return -1; + } + + std::string value; + + if (!flag->value.empty()) { + auto next = i + 1; + + if (next < argc) { + value = argv[next]; + i++; + } + } + + flags[arg] = value; + } else { + if (command->command.empty()) { + if (!flags.empty()) { + auto result = command->func(flags, {program}); + + if (result < 0) + return result; + + flags.clear(); + } + + auto cmd = this->d->parserCommand(arg); + + if (cmd) { + command = cmd; + flags.clear(); + } else { + std::cout << "Unknown command '" << arg << "'" << std::endl; + + return -1; + } + } else { + arguments.push_back(arg); + } + } + } + + return command->func(flags, arguments); +} + +void AkVCam::CmdParser::setDefaultFuntion(const ProgramOptionsFunc &func) +{ + this->d->m_commands[0].func = func; +} + +void AkVCam::CmdParser::addCommand(const std::string &command, + const std::string &arguments, + const std::string &helpString, + const ProgramOptionsFunc &func) +{ + auto it = + std::find_if(this->d->m_commands.begin(), + this->d->m_commands.end(), + [&command] (const CmdParserCommand &cmd) -> bool { + return cmd.command == command; + }); + + if (it == this->d->m_commands.end()) { + this->d->m_commands.push_back({command, + arguments, + helpString, + func, + {}}); + } else { + it->command = command; + it->arguments = arguments; + it->helpString = helpString; + it->func = func; + } +} + +void AkVCam::CmdParser::addFlags(const std::string &command, + const StringVector &flags, + const std::string &value, + const std::string &helpString) +{ + auto it = + std::find_if(this->d->m_commands.begin(), + this->d->m_commands.end(), + [&command] (const CmdParserCommand &cmd) -> bool { + return cmd.command == command; + }); + + if (it == this->d->m_commands.end()) + return; + + it->flags.push_back({flags, value, helpString}); +} + +void AkVCam::CmdParser::addFlags(const std::string &command, + const StringVector &flags, + const std::string &helpString) +{ + this->addFlags(command, flags, "", helpString); +} + +std::string AkVCam::CmdParserPrivate::basename(const std::string &path) +{ + auto rit = + std::find_if(path.rbegin(), + path.rend(), + [] (char c) -> bool { + return c == '/' || c == '\\'; + }); + + auto program = + rit == path.rend()? + path: + path.substr(path.size() + (path.rbegin() - rit)); + + auto it = + std::find_if(program.begin(), + program.end(), + [] (char c) -> bool { + return c == '.'; + }); + + program = + it == path.end()? + program: + program.substr(0, it - program.begin()); + + return program; +} + +void AkVCam::CmdParserPrivate::printFlags(const std::vector &cmdFlags, + size_t indent) +{ + std::vector spaces(indent, ' '); + auto maxFlagsLen = this->maxFlagsLength(cmdFlags); + auto maxFlagsValueLen = this->maxFlagsValueLength(cmdFlags); + + for (auto &flag: cmdFlags) { + auto allFlags = this->join(flag.flags, ", "); + std::cout << std::string(spaces.data(), indent) + << this->fill(allFlags, maxFlagsLen); + + if (maxFlagsValueLen > 0) { + std::cout << " " + << this->fill(flag.value, maxFlagsValueLen); + } + + std::cout << " " + << flag.helpString + << std::endl; + } +} + +size_t AkVCam::CmdParserPrivate::maxCommandLength() +{ + size_t length = 0; + + for (auto &cmd: this->m_commands) + length = std::max(cmd.command.size(), length); + + return length; +} + +size_t AkVCam::CmdParserPrivate::maxArgumentsLength() +{ + size_t length = 0; + + for (auto &cmd: this->m_commands) + length = std::max(cmd.arguments.size(), length); + + return length; +} + +size_t AkVCam::CmdParserPrivate::maxFlagsLength(const std::vector &flags) +{ + size_t length = 0; + + for (auto &flag: flags) + length = std::max(this->join(flag.flags, ", ").size(), length); + + return length; +} + +size_t AkVCam::CmdParserPrivate::maxFlagsValueLength(const std::vector &flags) +{ + size_t length = 0; + + for (auto &flag: flags) + length = std::max(flag.value.size(), length); + + 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::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) + if (cmd.command == command) + return &cmd; + + return nullptr; +} + +const AkVCam::CmdParserFlags *AkVCam::CmdParserPrivate::parserFlag(const std::vector &cmdFlags, + const std::string &flag) +{ + for (auto &flags: cmdFlags) + for (auto &f: flags.flags) + if (f == flag) + return &flags; + + return nullptr; +} + +bool AkVCam::CmdParserPrivate::containsFlag(const StringMap &flags, + const std::string &command, + const std::string &flagAlias) +{ + for (auto &cmd: this->m_commands) + if (cmd.command == command) { + for (auto &flag: cmd.flags) { + auto it = std::find(flag.flags.begin(), + flag.flags.end(), + flagAlias); + + if (it != flag.flags.end()) { + for (auto &f1: flags) + for (auto &f2: flag.flags) + if (f1.first == f2) + return true; + + return false; + } + } + + return false; + } + + return false; +} + +int AkVCam::CmdParserPrivate::defaultHandler(const StringMap &flags, + const StringVector &args) +{ + if (flags.empty() || this->containsFlag(flags, "", "-h")) + return this->showHelp(flags, args); + + return 0; +} + +int AkVCam::CmdParserPrivate::showHelp(const StringMap &flags, + const StringVector &args) +{ + UNUSED(flags) + + std::cout << args[0] + << " [OPTIONS...] COMMAND [COMMAND_OPTIONS...] ..." + << std::endl; + std::cout << std::endl; + std::cout << "AkVirtualCamera virtual device manager." << std::endl; + std::cout << std::endl; + std::cout << "General Options:" << std::endl; + std::cout << std::endl; + this->printFlags(this->m_commands[0].flags, 4); + std::cout << std::endl; + std::cout << "Commands:" << std::endl; + std::cout << std::endl; + + auto maxCmdLen = this->maxCommandLength(); + auto maxArgsLen = this->maxArgumentsLength(); + + for (auto &cmd: this->m_commands) { + if (cmd.command.empty()) + continue; + + std::cout << " " + << this->fill(cmd.command, maxCmdLen) + << " " + << this->fill(cmd.arguments, maxArgsLen) + << " " + << cmd.helpString << std::endl; + + if (!cmd.flags.empty()) + std::cout << std::endl; + + this->printFlags(cmd.flags, 8); + + if (!cmd.flags.empty()) + std::cout << std::endl; + } + + return 0; +} + +int AkVCam::CmdParserPrivate::showDevices(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::addDevice(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::removeDevice(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showDeviceType(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showDeviceDescription(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showSupportedFormats(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showFormats(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::addFormat(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::removeFormat(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::update(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showConnections(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::connectDevices(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::disconnectDevices(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showOptions(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::readOption(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::writeOption(const StringMap &flags, + const StringVector &args) +{ + return 0; +} + +int AkVCam::CmdParserPrivate::showClients(const StringMap &flags, + const StringVector &args) +{ + return 0; +} diff --git a/Manager/src/cmdparser.h b/Manager/src/cmdparser.h new file mode 100644 index 0000000..f3c5dfb --- /dev/null +++ b/Manager/src/cmdparser.h @@ -0,0 +1,58 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef CMDPARSER_H +#define CMDPARSER_H + +#include +#include +#include + +namespace AkVCam { + class CmdParserPrivate; + using StringVector = std::vector; + using StringMap = std::map; + using ProgramOptionsFunc = std::function; + + class CmdParser + { + public: + CmdParser(); + ~CmdParser(); + int parse(int argc, char **argv); + void setDefaultFuntion(const ProgramOptionsFunc &func); + void addCommand(const std::string &command, + const std::string &arguments, + const std::string &helpString, + const ProgramOptionsFunc &func); + void addFlags(const std::string &command, + const StringVector &flags, + const std::string &value, + const std::string &helpString); + void addFlags(const std::string &command, + const StringVector &flags, + const std::string &helpString); + + private: + CmdParserPrivate *d; + }; +} + +#endif // CMDPARSER_H diff --git a/Manager/src/main.cpp b/Manager/src/main.cpp index a05477b..89e05f4 100644 --- a/Manager/src/main.cpp +++ b/Manager/src/main.cpp @@ -17,9 +17,9 @@ * Web-Site: http://webcamoid.github.io/ */ -#include +#include "cmdparser.h" int main(int argc, char **argv) { - return 0; + return AkVCam::CmdParser().parse(argc, argv); } diff --git a/VCamUtils/src/ipcbridge.h b/VCamUtils/src/ipcbridge.h index 884c283..e2b0ca3 100644 --- a/VCamUtils/src/ipcbridge.h +++ b/VCamUtils/src/ipcbridge.h @@ -42,12 +42,10 @@ namespace AkVCam ServerStateGone }; - enum Operation + enum DeviceType { - OperationCreate, - OperationEdit, - OperationDestroy, - OperationDestroyAll + DeviceTypeOutput, + DeviceTypeInput }; AKVCAM_SIGNAL(ServerStateChanged, @@ -112,11 +110,11 @@ namespace AkVCam bool setRootMethod(const std::string &rootMethod); // Manage main service connection. - void connectService(bool asClient); + void connectService(); void disconnectService(); // Register the peer to the global server. - bool registerPeer(bool asClient); + bool registerPeer(); // Unregister the peer to the global server. void unregisterPeer(); @@ -163,18 +161,14 @@ namespace AkVCam // Returns client executable from PID. std::string clientExe(uint64_t pid) const; - // Returns 'true' if the application needs to be restarted before - // creating, editing, or removing the virtual devices. - bool needsRestart(Operation operation) const; - - // Can create, edit, or remove virtual devices? - bool canApply(Operation operation) const; - /* Server */ + DeviceType deviceType(const std::string &deviceId); + // Create a device definition. std::string deviceCreate(const std::wstring &description, - const std::vector &formats); + const std::vector &formats, + DeviceType type); // Edit a device definition. bool deviceEdit(const std::string &deviceId, diff --git a/VCamUtils/src/utils.cpp b/VCamUtils/src/utils.cpp index 89ed214..cb118a1 100644 --- a/VCamUtils/src/utils.cpp +++ b/VCamUtils/src/utils.cpp @@ -156,3 +156,25 @@ std::wstring AkVCam::trimmed(const std::wstring &str) return str.substr(size_t(left), strippedLen); } + +std::vector AkVCam::split(const std::string &str, char separator) +{ + if (str.empty()) + return {}; + + std::vector elements; + std::string subStr; + + for (auto &c: str) + if (c == separator) { + elements.push_back(subStr); + subStr.clear(); + } else { + subStr += c; + } + + if (!subStr.empty() || *str.rbegin() == separator) + elements.push_back(subStr); + + return elements; +} diff --git a/VCamUtils/src/utils.h b/VCamUtils/src/utils.h index 730f40d..7921ee0 100644 --- a/VCamUtils/src/utils.h +++ b/VCamUtils/src/utils.h @@ -153,6 +153,7 @@ 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::vector split(const std::string &str, char separator); } #endif // AKVCAMUTILS_UTILS_H diff --git a/cmio/Assistant/src/assistant.cpp b/cmio/Assistant/src/assistant.cpp index 8449674..03f10dc 100644 --- a/cmio/Assistant/src/assistant.cpp +++ b/cmio/Assistant/src/assistant.cpp @@ -29,6 +29,7 @@ #include "PlatformUtils/src/utils.h" #include "VCamUtils/src/image/videoformat.h" #include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/ipcbridge.h" #include "VCamUtils/src/logger/logger.h" #define AKVCAM_BIND_FUNC(member) \ @@ -38,16 +39,20 @@ namespace AkVCam { + + struct AssistantDevice { std::wstring description; std::vector formats; + std::vector connections; std::string broadcaster; std::vector listeners; + IpcBridge::DeviceType type; bool horizontalMirror {false}; bool verticalMirror {false}; Scaling scaling {ScalingFast}; - AspectRatio aspectRatio {AspectRatioIgnore}; + AspectRatio aspectRatio {AspectRatioIgnore}; bool swapRgb {false}; }; @@ -57,7 +62,6 @@ namespace AkVCam class AssistantPrivate { public: - AssistantPeers m_servers; AssistantPeers m_clients; DeviceConfigs m_deviceConfigs; std::map m_messageHandlers; @@ -90,7 +94,7 @@ namespace AkVCam void listener(xpc_connection_t client, xpc_object_t event); void devices(xpc_connection_t client, xpc_object_t event); void description(xpc_connection_t client, xpc_object_t event); - void formats(xpc_connection_t client, xpc_object_t event); + void formats(xpc_connection_t client, xpc_object_t event); void broadcasting(xpc_connection_t client, xpc_object_t event); void mirroring(xpc_connection_t client, xpc_object_t event); void scaling(xpc_connection_t client, xpc_object_t event); @@ -98,6 +102,8 @@ namespace AkVCam void swapRgb(xpc_connection_t client, xpc_object_t event); void listenerAdd(xpc_connection_t client, xpc_object_t event); void listenerRemove(xpc_connection_t client, xpc_object_t event); + void connections(xpc_connection_t client, xpc_object_t event); + void setConnections(xpc_connection_t client, xpc_object_t event); }; } @@ -164,6 +170,8 @@ AkVCam::AssistantPrivate::AssistantPrivate() {AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO , AKVCAM_BIND_FUNC(AssistantPrivate::setAspectRatio) }, {AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB , AKVCAM_BIND_FUNC(AssistantPrivate::swapRgb) }, {AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB , AKVCAM_BIND_FUNC(AssistantPrivate::setSwapRgb) }, + {AKVCAM_ASSISTANT_MSG_CONNECTIONS , AKVCAM_BIND_FUNC(AssistantPrivate::connections) }, + {AKVCAM_ASSISTANT_MSG_SETCONNECTIONS , AKVCAM_BIND_FUNC(AssistantPrivate::setConnections) }, }; this->loadCameras(); @@ -172,19 +180,13 @@ AkVCam::AssistantPrivate::AssistantPrivate() AkVCam::AssistantPrivate::~AssistantPrivate() { - std::vector allPeers { - &this->m_clients, - &this->m_servers - }; - for (auto &device: this->m_deviceConfigs) { auto notification = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_int64(notification, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY); xpc_dictionary_set_string(notification, "device", device.first.c_str()); - for (auto peers: allPeers) - for (auto &peer: *peers) - xpc_connection_send_message(peer.second, notification); + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); xpc_release(notification); } @@ -254,11 +256,13 @@ void AkVCam::AssistantPrivate::loadCameras() AkLogFunction(); for (size_t i = 0; i < Preferences::camerasCount(); i++) { - this->m_deviceConfigs[Preferences::cameraPath(i)] = {}; - this->m_deviceConfigs[Preferences::cameraPath(i)].description = + auto path = Preferences::cameraPath(i); + this->m_deviceConfigs[path] = {}; + this->m_deviceConfigs[path].description = Preferences::cameraDescription(i); - this->m_deviceConfigs[Preferences::cameraPath(i)].formats = - Preferences::cameraFormats(i); + this->m_deviceConfigs[path].formats = Preferences::cameraFormats(i); + this->m_deviceConfigs[path].connections = + Preferences::cameraConnections(i); } } @@ -297,29 +301,22 @@ void AkVCam::AssistantPrivate::peerDied() { AkLogFunction(); - std::vector allPeers { - &this->m_clients, - &this->m_servers - }; + for (auto &client: this->m_clients) { + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_ISALIVE); + auto reply = xpc_connection_send_message_with_reply_sync(client.second, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + bool alive = false; - for (auto peers: allPeers) { - for (auto &peer: *peers) { - auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); - xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_ISALIVE); - auto reply = xpc_connection_send_message_with_reply_sync(peer.second, - dictionary); - xpc_release(dictionary); - auto replyType = xpc_get_type(reply); - bool alive = false; + if (replyType == XPC_TYPE_DICTIONARY) + alive = xpc_dictionary_get_bool(reply, "alive"); - if (replyType == XPC_TYPE_DICTIONARY) - alive = xpc_dictionary_get_bool(reply, "alive"); + xpc_release(reply); - xpc_release(reply); - - if (!alive) - this->removePortByName(peer.first); - } + if (!alive) + this->removePortByName(client.first); } } @@ -327,12 +324,8 @@ void AkVCam::AssistantPrivate::requestPort(xpc_connection_t client, xpc_object_t event) { AkLogFunction(); - - bool asClient = xpc_dictionary_get_bool(event, "client"); - std::string portName = asClient? - AKVCAM_ASSISTANT_CLIENT_NAME: - AKVCAM_ASSISTANT_SERVER_NAME; - portName += std::to_string(this->id()); + auto portName = AKVCAM_ASSISTANT_CLIENT_NAME + + std::to_string(this->id()); AkLogInfo() << "Returning Port: " << portName << std::endl; @@ -353,15 +346,9 @@ void AkVCam::AssistantPrivate::addPort(xpc_connection_t client, xpc_connection_set_event_handler(connection, ^(xpc_object_t) {}); xpc_connection_resume(connection); bool ok = true; - AssistantPeers *peers; - if (portName.find(AKVCAM_ASSISTANT_CLIENT_NAME) != std::string::npos) - peers = &this->m_clients; - else - peers = &this->m_servers; - - for (auto &peer: *peers) - if (peer.first == portName) { + for (auto &client: this->m_clients) + if (client.first == portName) { ok = false; break; @@ -369,7 +356,7 @@ void AkVCam::AssistantPrivate::addPort(xpc_connection_t client, if (ok) { AkLogInfo() << "Adding Peer: " << portName << std::endl; - (*peers)[portName] = connection; + this->m_clients[portName] = connection; this->stopTimer(); } @@ -384,30 +371,15 @@ void AkVCam::AssistantPrivate::removePortByName(const std::string &portName) AkLogFunction(); AkLogInfo() << "Port: " << portName << std::endl; - std::vector allPeers { - &this->m_clients, - &this->m_servers - }; + for (auto &peer: this->m_clients) + if (peer.first == portName) { + xpc_release(peer.second); + this->m_clients.erase(portName); - bool breakLoop = false; - - for (auto peers: allPeers) { - for (auto &peer: *peers) - if (peer.first == portName) { - xpc_release(peer.second); - peers->erase(portName); - breakLoop = true; - - break; - } - - if (breakLoop) break; - } + } - bool peersEmpty = this->m_servers.empty() && this->m_clients.empty(); - - if (peersEmpty) + if (this->m_clients.empty()) this->startTimer(); this->releaseDevicesFromPeer(portName); @@ -445,8 +417,12 @@ void AkVCam::AssistantPrivate::deviceCreate(xpc_connection_t client, formats.push_back(VideoFormat {fourcc, width, height, {frameRate}}); } - auto deviceId = Preferences::addCamera(description, formats); + auto type = xpc_dictionary_get_bool(event, "isinput")? + IpcBridge::DeviceTypeInput: + IpcBridge::DeviceTypeOutput; + auto deviceId = Preferences::addCamera(description, formats, type); this->m_deviceConfigs[deviceId] = {}; + this->m_deviceConfigs[deviceId].type = type; this->m_deviceConfigs[deviceId].description = description; this->m_deviceConfigs[deviceId].formats = formats; @@ -918,3 +894,44 @@ void AkVCam::AssistantPrivate::listenerRemove(xpc_connection_t client, xpc_connection_send_message(client, reply); xpc_release(reply); } + +void AkVCam::AssistantPrivate::connections(xpc_connection_t client, + xpc_object_t event) +{ + AkLogFunction(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto listeners = xpc_array_create(nullptr, 0); + + if (this->m_deviceConfigs.count(deviceId) > 0) + for (auto &listener: this->m_deviceConfigs[deviceId].connections) { + auto listenerObj = xpc_string_create(listener.c_str()); + xpc_array_append_value(listeners, listenerObj); + } + + AkLogInfo() << "Device: " << deviceId << std::endl; + AkLogInfo() << "Connections: " << xpc_array_get_count(listeners) << std::endl; + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_value(reply, "connections", listeners); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::setConnections(xpc_connection_t client, + xpc_object_t event) +{ + AkLogFunction(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto connectionsList = xpc_dictionary_get_array(event, "connections"); + std::vector connections; + + for (size_t i = 0; i < xpc_array_get_count(connectionsList); i++) + connections.push_back(xpc_array_get_string(connectionsList, i)); + + this->m_deviceConfigs[deviceId].connections = connections; + AkLogInfo() << "Device: " << deviceId << std::endl; + AkLogInfo() << "Connections: " << connections.size() << std::endl; + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", true); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} diff --git a/cmio/Assistant/src/assistantglobals.h b/cmio/Assistant/src/assistantglobals.h index 6eab6fb..4727424 100644 --- a/cmio/Assistant/src/assistantglobals.h +++ b/cmio/Assistant/src/assistantglobals.h @@ -24,7 +24,6 @@ #include #define AKVCAM_ASSISTANT_CLIENT_NAME "org.webcamoid.cmio.AkVCam.Client" -#define AKVCAM_ASSISTANT_SERVER_NAME "org.webcamoid.cmio.AkVCam.Server" // General messages #define AKVCAM_ASSISTANT_MSG_ISALIVE 0x000 @@ -60,6 +59,11 @@ #define AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB 0x408 #define AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB 0x409 +// Connections +#define AKVCAM_ASSISTANT_MSG_CONNECTIONS 0x500 +#define AKVCAM_ASSISTANT_MSG_SETCONNECTIONS 0x501 + + namespace AkVCam { using XpcMessage = std::function; diff --git a/cmio/PlatformUtils/src/preferences.cpp b/cmio/PlatformUtils/src/preferences.cpp index 0b55fba..5f026ff 100644 --- a/cmio/PlatformUtils/src/preferences.cpp +++ b/cmio/PlatformUtils/src/preferences.cpp @@ -191,6 +191,34 @@ double AkVCam::Preferences::readDouble(const std::string &key, return value; } +bool AkVCam::Preferences::readBool(const std::string &key, bool defaultValue) +{ + AkLogFunction(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = + CFBooleanRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + auto value = defaultValue; + + if (cfValue) { + value = CFBooleanGetValue(cfValue); + CFRelease(cfValue); + } + + return value; +} + +std::vector AkVCam::Preferences::readStringList(const std::string &key, + const std::vector &defaultValue) +{ + auto value = defaultValue; + + for (auto &str: split(readString(key), ',')) + value.push_back(trimmed(str)); + + return value; +} + void AkVCam::Preferences::deleteKey(const std::string &key) { AkLogFunction(); @@ -248,14 +276,16 @@ void AkVCam::Preferences::sync() } std::string AkVCam::Preferences::addCamera(const std::wstring &description, - const std::vector &formats) + const std::vector &formats, + IpcBridge::DeviceType type) { - return addCamera("", description, formats); + return addCamera("", description, formats, type); } std::string AkVCam::Preferences::addCamera(const std::string &path, const std::wstring &description, - const std::vector &formats) + const std::vector &formats, + IpcBridge::DeviceType type) { AkLogFunction(); @@ -265,11 +295,14 @@ std::string AkVCam::Preferences::addCamera(const std::string &path, auto path_ = path.empty()? createDevicePath(): path; int cameraIndex = readInt("cameras"); write("cameras", cameraIndex + 1); - write("cameras." + std::to_string(cameraIndex) + ".description", description); + write("cameras." + + std::to_string(cameraIndex) + + ".isinput", + type == IpcBridge::DeviceTypeInput); write("cameras." + std::to_string(cameraIndex) + ".path", @@ -376,6 +409,21 @@ bool AkVCam::Preferences::cameraExists(const std::string &path) return false; } +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) { return readWString("cameras." @@ -428,3 +476,36 @@ std::vector AkVCam::Preferences::cameraFormats(size_t camer return formats; } + +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; + + size_t pos = 0; + auto outputIndex = std::stoi(connection, &pos); + + if (pos == connection.size()) + cameraConnections.push_back(cameraPath(outputIndex)); + } + + return cameraConnections; +} + +std::vector AkVCam::Preferences::cameraConnections(const std::string &path) +{ + AkLogFunction(); + + return cameraConnections(cameraFromPath(path)); +} diff --git a/cmio/PlatformUtils/src/preferences.h b/cmio/PlatformUtils/src/preferences.h index 8f3362f..3abf709 100644 --- a/cmio/PlatformUtils/src/preferences.h +++ b/cmio/PlatformUtils/src/preferences.h @@ -24,6 +24,8 @@ #include #include +#include "VCamUtils/src/ipcbridge.h" + namespace AkVCam { class VideoFormat; @@ -44,26 +46,35 @@ namespace AkVCam const std::wstring &defaultValue={}); int readInt(const std::string &key, int defaultValue=0); double readDouble(const std::string &key, double defaultValue=0.0); + bool readBool(const std::string &key, bool defaultValue=false); + std::vector readStringList(const std::string &key, + const std::vector &defaultValue={}); void deleteKey(const std::string &key); void deleteAllKeys(const std::string &key); void move(const std::string &keyFrom, const std::string &keyTo); void moveAll(const std::string &keyFrom, const std::string &keyTo); void sync(); std::string addCamera(const std::wstring &description, - const std::vector &formats); + const std::vector &formats, + IpcBridge::DeviceType type); std::string addCamera(const std::string &path, const std::wstring &description, - const std::vector &formats); + const std::vector &formats, + IpcBridge::DeviceType type); void removeCamera(const std::string &path); size_t camerasCount(); std::string createDevicePath(); 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); 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); + std::vector cameraConnections(size_t cameraIndex); + std::vector cameraConnections(const std::string &path); } } diff --git a/cmio/VCamIPC/src/ipcbridge.mm b/cmio/VCamIPC/src/ipcbridge.mm index 988a78b..d0b30cd 100644 --- a/cmio/VCamIPC/src/ipcbridge.mm +++ b/cmio/VCamIPC/src/ipcbridge.mm @@ -30,6 +30,7 @@ #include #include "Assistant/src/assistantglobals.h" +#include "PlatformUtils/src/preferences.h" #include "VCamUtils/src/image/videoformat.h" #include "VCamUtils/src/image/videoframe.h" #include "VCamUtils/src/ipcbridge.h" @@ -52,8 +53,6 @@ namespace AkVCam std::vector m_broadcasting; std::map m_options; std::wstring m_error; - bool m_asClient; - bool m_uninstall; IpcBridgePrivate(IpcBridge *self=nullptr); ~IpcBridgePrivate(); @@ -86,16 +85,8 @@ namespace AkVCam std::wstring fileName(const std::wstring &path) const; bool mkpath(const std::string &path) const; bool rm(const std::string &path) const; - bool createDaemonPlist(const std::string &fileName) const; - bool loadDaemon(); - void unloadDaemon() const; - bool checkDaemon(); - void uninstallPlugin(); std::wstring locateDriverPath() const; - // Execute commands with elevated privileges. - int sudo(const std::vector ¶meters); - private: std::vector m_bridges; }; @@ -113,6 +104,7 @@ AkVCam::IpcBridge::IpcBridge() AkLogFunction(); this->d = new IpcBridgePrivate(this); ipcBridgePrivate().add(this); + this->registerPeer(); } AkVCam::IpcBridge::~IpcBridge() @@ -181,33 +173,29 @@ bool AkVCam::IpcBridge::setRootMethod(const std::string &rootMethod) return rootMethod == "osascript"; } -void AkVCam::IpcBridge::connectService(bool asClient) +void AkVCam::IpcBridge::connectService() { AkLogFunction(); - this->d->m_asClient = asClient; - this->registerPeer(asClient); + this->registerPeer(); } void AkVCam::IpcBridge::disconnectService() { AkLogFunction(); this->unregisterPeer(); - this->d->m_asClient = false; } -bool AkVCam::IpcBridge::registerPeer(bool asClient) +bool AkVCam::IpcBridge::registerPeer() { AkLogFunction(); - if (!asClient) { - std::string plistFile = - CMIO_DAEMONS_PATH "/" CMIO_ASSISTANT_NAME ".plist"; + std::string plistFile = + CMIO_DAEMONS_PATH "/" CMIO_ASSISTANT_NAME ".plist"; - auto daemon = replace(plistFile, "~", this->d->homePath()); + auto daemon = replace(plistFile, "~", this->d->homePath()); - if (!this->d->fileExists(daemon)) - return false; - } + if (!this->d->fileExists(daemon)) + return false; if (this->d->m_serverMessagePort) return true; @@ -236,7 +224,6 @@ bool AkVCam::IpcBridge::registerPeer(bool asClient) dictionary = xpc_dictionary_create(nullptr, nullptr, 0); xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_REQUEST_PORT); - xpc_dictionary_set_bool(dictionary, "client", asClient); reply = xpc_connection_send_message_with_reply_sync(serverMessagePort, dictionary); xpc_release(dictionary); @@ -705,34 +692,19 @@ std::string AkVCam::IpcBridge::clientExe(uint64_t pid) const return {path}; } -bool AkVCam::IpcBridge::needsRestart(Operation operation) const +AkVCam::IpcBridge::DeviceType AkVCam::IpcBridge::deviceType(const std::string &deviceId) { - return operation == OperationDestroyAll - || ((operation == OperationDestroy || operation == OperationEdit) - && this->listDevices().size() == 1); -} - -bool AkVCam::IpcBridge::canApply(AkVCam::IpcBridge::Operation operation) const -{ - return this->clientsPids().empty() && !this->needsRestart(operation); + return Preferences::cameraIsInput(deviceId)? + DeviceTypeInput: + DeviceTypeOutput; } std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, - const std::vector &formats) + const std::vector &formats, + DeviceType type) { AkLogFunction(); - if (!this->canApply(OperationCreate)) { - this->d->m_error = L"The driver is in use"; - - return {}; - } - - if (!this->d->checkDaemon()) - return {}; - - this->registerPeer(false); - if (!this->d->m_serverMessagePort || !this->d->m_messagePort) { this->d->m_error = L"Can't register peer"; @@ -746,6 +718,7 @@ std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, "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) { @@ -783,20 +756,14 @@ bool AkVCam::IpcBridge::deviceEdit(const std::string &deviceId, { AkLogFunction(); - if (!this->canApply(OperationEdit)) { - this->d->m_error = L"The driver is in use"; - - return false; - } - - this->d->m_uninstall = false; + auto type = this->deviceType(deviceId); this->deviceDestroy(deviceId); - this->d->m_uninstall = true; if (this->deviceCreate(description.empty()? L"AvKys Virtual Camera": description, - formats).empty()) + formats, + type).empty()) return false; return true; @@ -807,25 +774,19 @@ bool AkVCam::IpcBridge::changeDescription(const std::string &deviceId, { AkLogFunction(); - if (!this->canApply(OperationEdit)) { - this->d->m_error = L"The driver is in use"; - - return false; - } - auto formats = this->formats(deviceId); if (formats.empty()) return false; - this->d->m_uninstall = false; + auto type = this->deviceType(deviceId); this->deviceDestroy(deviceId); - this->d->m_uninstall = true; if (this->deviceCreate(description.empty()? L"AvKys Virtual Camera": description, - formats).empty()) + formats, + type).empty()) return false; return true; @@ -835,12 +796,6 @@ bool AkVCam::IpcBridge::deviceDestroy(const std::string &deviceId) { AkLogFunction(); - if (!this->canApply(OperationDestroy)) { - this->d->m_error = L"The driver is in use"; - - return false; - } - if (!this->d->m_serverMessagePort) return false; @@ -851,10 +806,6 @@ bool AkVCam::IpcBridge::deviceDestroy(const std::string &deviceId) dictionary); xpc_release(dictionary); - // If no devices are registered - if (this->d->m_uninstall && listDevices().empty()) - this->d->uninstallPlugin(); - return true; } @@ -862,12 +813,6 @@ bool AkVCam::IpcBridge::destroyAllDevices() { AkLogFunction(); - if (!this->canApply(OperationDestroyAll)) { - this->d->m_error = L"The driver is in use"; - - return false; - } - for (auto &device: this->listDevices()) this->deviceDestroy(device); @@ -1141,9 +1086,7 @@ bool AkVCam::IpcBridge::removeListener(const std::string &deviceId) AkVCam::IpcBridgePrivate::IpcBridgePrivate(IpcBridge *self): self(self), m_messagePort(nullptr), - m_serverMessagePort(nullptr), - m_asClient(false), - m_uninstall(true) + m_serverMessagePort(nullptr) { this->m_messageHandlers = { {AKVCAM_ASSISTANT_MSG_ISALIVE , AKVCAM_BIND_FUNC(IpcBridgePrivate::isAlive) }, @@ -1393,7 +1336,7 @@ void AkVCam::IpcBridgePrivate::connectionInterrupted() // Restart service for (auto bridge: this->m_bridges) - if (bridge->registerPeer(bridge->d->m_asClient)) { + if (bridge->registerPeer()) { AKVCAM_EMIT(bridge, ServerStateChanged, IpcBridge::ServerStateAvailable) @@ -1484,186 +1427,6 @@ bool AkVCam::IpcBridgePrivate::rm(const std::string &path) const return ok; } -bool AkVCam::IpcBridgePrivate::createDaemonPlist(const std::string &fileName) const -{ - AkLogFunction(); - std::fstream plistFile; - plistFile.open(fileName, std::ios_base::out); - - if (!plistFile.is_open()) - return false; - - plistFile << "" << std::endl - << "" - << std::endl - << "" << std::endl - << " " << std::endl - << " Label" << std::endl - << " " << CMIO_ASSISTANT_NAME - << "" << std::endl - << " ProgramArguments" << std::endl - << " " << std::endl - << " " << CMIO_PLUGINS_DAL_PATH - << "/" - << CMIO_PLUGIN_NAME - << ".plugin/Contents/Resources/" - << CMIO_PLUGIN_ASSISTANT_NAME - << "" << std::endl - << " --timeout" << std::endl - << " 300.0" << std::endl - << " " << std::endl - << " MachServices" << std::endl - << " " << std::endl - << " " << CMIO_ASSISTANT_NAME - << "" << std::endl - << " " << std::endl - << " " << std::endl - << " " << std::endl - << "" << std::endl; - - return true; -} - -bool AkVCam::IpcBridgePrivate::loadDaemon() -{ - AkLogFunction(); - auto launchctl = popen("launchctl list " CMIO_ASSISTANT_NAME, "r"); - - if (launchctl && !pclose(launchctl)) - return true; - - auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); - auto dstDaemonsPath = daemonsPath + "/" CMIO_ASSISTANT_NAME ".plist"; - - if (!this->fileExists(dstDaemonsPath)) { - this->m_error = L"Daemon plist does not exists"; - - return false; - } - - launchctl = popen(("launchctl load -w '" + dstDaemonsPath + "'").c_str(), - "r"); - - bool result = launchctl && !pclose(launchctl); - - if (!result) - this->m_error = L"Can't launch daemon"; - - return result; -} - -void AkVCam::IpcBridgePrivate::unloadDaemon() const -{ - AkLogFunction(); - std::string daemonPlist = CMIO_ASSISTANT_NAME ".plist"; - auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); - auto dstDaemonsPath = daemonsPath + "/" + daemonPlist; - - if (!this->fileExists(dstDaemonsPath)) - return; - - auto launchctl = - popen(("launchctl unload -w '" + dstDaemonsPath + "'").c_str(), - "r"); - pclose(launchctl); -} - -bool AkVCam::IpcBridgePrivate::checkDaemon() -{ - AkLogFunction(); - auto driverPath = this->locateDriverPath(); - - if (driverPath.empty()) { - this->m_error = L"Driver not found"; - - return false; - } - - auto plugin = this->fileName(driverPath); - std::wstring dstPath = CMIO_PLUGINS_DAL_PATH_L; - std::wstring pluginInstallPath = dstPath + L'/' + plugin; - - if (!this->fileExists(pluginInstallPath)) { - const std::string cmdFileName = "/tmp/akvcam_exec.sh"; - - std::wfstream cmds; - cmds.open(cmdFileName, std::ios_base::out); - - if (!cmds.is_open()) { - this->m_error = L"Can't create script"; - - return false; - } - - cmds << L"mkdir -p " - << pluginInstallPath - << std::endl - << L"cp -rvf '" - << driverPath << L"'/* " - << pluginInstallPath << L"/" - << std::endl - << L"chmod +x " - << pluginInstallPath << L"/Contents/Resources/" CMIO_PLUGIN_ASSISTANT_NAME_L - << std::endl; - cmds.close(); - chmod(cmdFileName.c_str(), S_IRWXU | S_IRGRP | S_IROTH); - - if (this->sudo({"sh", cmdFileName})) { - this->rm(cmdFileName); - - return false; - } - - this->rm(cmdFileName); - } - - auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); - auto dstDaemonsPath = daemonsPath + "/" + CMIO_ASSISTANT_NAME + ".plist"; - - if (!this->fileExists(dstDaemonsPath)) { - if (!this->mkpath(daemonsPath)) { - this->m_error = L"Can't create daemon path"; - - return false; - } - - if (!this->createDaemonPlist(dstDaemonsPath)) { - this->m_error = L"Can't create daemon plist"; - - return false; - } - } - - return this->loadDaemon(); -} - -void AkVCam::IpcBridgePrivate::uninstallPlugin() -{ - AkLogFunction(); - - // Stop the daemon - this->unloadDaemon(); - - // Remove the agent plist - auto daemonsPath = - replace(CMIO_DAEMONS_PATH, "~", this->homePath()); - this->rm(daemonsPath + "/" CMIO_ASSISTANT_NAME ".plist"); - - // Remove the plugin - auto driverPath = this->locateDriverPath(); - - if (driverPath.empty()) - return; - - auto plugin = this->fileName(driverPath); - std::wstring dstPath = CMIO_PLUGINS_DAL_PATH_L; - this->sudo({"rm", "-rvf", - std::string(dstPath.begin(), dstPath.end()) - + '/' - + std::string(plugin.begin(), plugin.end())}); -} - std::wstring AkVCam::IpcBridgePrivate::locateDriverPath() const { AkLogFunction(); @@ -1693,38 +1456,3 @@ std::wstring AkVCam::IpcBridgePrivate::locateDriverPath() const return driverPath; } - -int AkVCam::IpcBridgePrivate::sudo(const std::vector ¶meters) -{ - AkLogFunction(); - std::stringstream ss; - ss << "osascript -e \"do shell script \\\""; - - for (auto param: parameters) { - if (param.find(' ') == std::string::npos) - ss << param; - else - ss << "'" << param << "'"; - - ss << ' '; - } - - ss << "\\\" with administrator privileges\" 2>&1"; - auto sudo = popen(ss.str().c_str(), "r"); - - if (!sudo) - return -1; - - std::string output; - char buffer[1024]; - - while (fgets(buffer, 1024, sudo)) - output += std::string(buffer); - - auto result = pclose(sudo); - - if (result) - AkLogInfo() << output << std::endl; - - return result; -} diff --git a/cmio/VirtualCamera/src/plugininterface.cpp b/cmio/VirtualCamera/src/plugininterface.cpp index d31ade3..217daa7 100644 --- a/cmio/VirtualCamera/src/plugininterface.cpp +++ b/cmio/VirtualCamera/src/plugininterface.cpp @@ -25,6 +25,7 @@ #include "plugininterface.h" #include "utils.h" #include "Assistant/src/assistantglobals.h" +#include "PlatformUtils/src/preferences.h" #include "VCamUtils/src/image/videoformat.h" #include "VCamUtils/src/ipcbridge.h" #include "VCamUtils/src/logger/logger.h" @@ -168,7 +169,7 @@ AkVCam::PluginInterface::PluginInterface(): struct stat fileInfo; if (stat(daemon.c_str(), &fileInfo) == 0) - this->d->m_ipcBridge.connectService(true); + this->d->m_ipcBridge.connectService(); this->d->m_ipcBridge.connectServerStateChanged(this, &PluginInterface::serverStateChanged); this->d->m_ipcBridge.connectDeviceAdded(this, &PluginInterface::deviceAdded); @@ -280,8 +281,9 @@ void AkVCam::PluginInterface::deviceAdded(void *userData, auto self = reinterpret_cast(userData); auto description = self->d->m_ipcBridge.description(deviceId); auto formats = self->d->m_ipcBridge.formats(deviceId); + auto type = self->d->m_ipcBridge.deviceType(deviceId); - self->createDevice(deviceId, description, formats); + self->createDevice(deviceId, description, formats, type); } void AkVCam::PluginInterface::deviceRemoved(void *userData, @@ -300,9 +302,12 @@ void AkVCam::PluginInterface::frameReady(void *userData, { AkLogFunction(); auto self = reinterpret_cast(userData); + auto connections = Preferences::cameraConnections(deviceId); for (auto device: self->m_devices) - if (device->deviceId() == deviceId) + if (std::find(connections.begin(), + connections.end(), + device->deviceId()) != connections.end()) device->frameReady(frame); } @@ -387,14 +392,15 @@ void AkVCam::PluginInterface::removeListener(void *userData, bool AkVCam::PluginInterface::createDevice(const std::string &deviceId, const std::wstring &description, - const std::vector &formats) + const std::vector &formats, + IpcBridge::DeviceType type) { AkLogFunction(); StreamPtr stream; // Create one device. auto pluginRef = reinterpret_cast(this->d); - auto device = std::make_shared(pluginRef); + auto device = std::make_shared(pluginRef, false); device->setDeviceId(deviceId); device->connectAddListener(this, &PluginInterface::addListener); device->connectRemoveListener(this, &PluginInterface::removeListener); @@ -402,7 +408,7 @@ bool AkVCam::PluginInterface::createDevice(const std::string &deviceId, // Define device properties. device->properties().setProperty(kCMIOObjectPropertyName, - description.c_str()); + description); device->properties().setProperty(kCMIOObjectPropertyManufacturer, CMIO_PLUGIN_VENDOR); device->properties().setProperty(kCMIODevicePropertyModelUID, @@ -423,7 +429,7 @@ bool AkVCam::PluginInterface::createDevice(const std::string &deviceId, device->properties().setProperty(kCMIODevicePropertyDeviceIsAlive, UInt32(1)); device->properties().setProperty(kCMIODevicePropertyDeviceUID, - deviceId.c_str()); + deviceId); device->properties().setProperty(kCMIODevicePropertyTransportType, UInt32(kIOAudioDeviceTransportTypePCI)); device->properties().setProperty(kCMIODevicePropertyDeviceIsRunningSomewhere, @@ -438,8 +444,12 @@ bool AkVCam::PluginInterface::createDevice(const std::string &deviceId, if (!stream) goto createDevice_failed; + stream->setBridge(&this->d->m_ipcBridge); stream->setFormats(formats); - stream->properties().setProperty(kCMIOStreamPropertyDirection, UInt32(0)); + stream->properties().setProperty(kCMIOStreamPropertyDirection, + UInt32(type == IpcBridge::DeviceTypeOutput? + Stream::Output: + Stream::Input)); if (device->registerStreams() != kCMIOHardwareNoError) { device->registerStreams(false); diff --git a/cmio/VirtualCamera/src/plugininterface.h b/cmio/VirtualCamera/src/plugininterface.h index 5233492..de825fd 100644 --- a/cmio/VirtualCamera/src/plugininterface.h +++ b/cmio/VirtualCamera/src/plugininterface.h @@ -79,7 +79,8 @@ namespace AkVCam const std::string &deviceId); bool createDevice(const std::string &deviceId, const std::wstring &description, - const std::vector &formats); + const std::vector &formats, + IpcBridge::DeviceType type); void destroyDevice(const std::string &deviceId); friend struct PluginInterfacePrivate; diff --git a/cmio/VirtualCamera/src/queue.h b/cmio/VirtualCamera/src/queue.h index 75b2c8f..7c5b2bf 100644 --- a/cmio/VirtualCamera/src/queue.h +++ b/cmio/VirtualCamera/src/queue.h @@ -55,7 +55,7 @@ namespace AkVCam T dequeue() { - return CMSimpleQueueDequeue(this->m_queue); + return T(CMSimpleQueueDequeue(this->m_queue)); } void clear() diff --git a/cmio/VirtualCamera/src/stream.cpp b/cmio/VirtualCamera/src/stream.cpp index 6c5b689..2fd5f30 100644 --- a/cmio/VirtualCamera/src/stream.cpp +++ b/cmio/VirtualCamera/src/stream.cpp @@ -34,6 +34,7 @@ namespace AkVCam { public: Stream *self; + IpcBridge *m_bridge {nullptr}; ClockPtr m_clock; UInt64 m_sequence; CMTime m_pts; @@ -58,6 +59,7 @@ namespace AkVCam void stopTimer(); static void streamLoop(CFRunLoopTimerRef timer, void *info); void sendFrame(const VideoFrame &frame); + void requestFrame(); void updateTestFrame(); VideoFrame applyAdjusts(const VideoFrame &frame); VideoFrame randomFrame(); @@ -153,6 +155,11 @@ OSStatus AkVCam::Stream::registerObject(bool regist) return status; } +void AkVCam::Stream::setBridge(IpcBridge *bridge) +{ + this->d->m_bridge = bridge; +} + void AkVCam::Stream::setFormats(const std::vector &formats) { AkLogFunction(); @@ -217,13 +224,29 @@ bool AkVCam::Stream::start() if (this->d->m_running) return false; - this->d->updateTestFrame(); - this->d->m_currentFrame = this->d->m_testFrameAdapted; + UInt32 direction = 0; + this->properties().getProperty(kCMIOStreamPropertyDirection, &direction); + + if (Direction(direction) == Output) { + this->d->updateTestFrame(); + this->d->m_currentFrame = this->d->m_testFrameAdapted; + } + this->d->m_sequence = 0; memset(&this->d->m_pts, 0, sizeof(CMTime)); this->d->m_running = this->d->startTimer(); AkLogInfo() << "Running: " << this->d->m_running << std::endl; + if (Direction(direction) == Input) { + std::string deviceId; + this->m_parent->properties().getProperty(kCMIODevicePropertyDeviceUID, + &deviceId); + VideoFormat format; + this->m_properties.getProperty(kCMIOStreamPropertyFormatDescription, + &format); + this->d->m_bridge->deviceStart(deviceId, format); + } + return this->d->m_running; } @@ -234,6 +257,16 @@ void AkVCam::Stream::stop() if (!this->d->m_running) return; + UInt32 direction = 0; + this->properties().getProperty(kCMIOStreamPropertyDirection, &direction); + + if (Direction(direction) == Input) { + std::string deviceId; + this->m_parent->properties().getProperty(kCMIODevicePropertyDeviceUID, + &deviceId); + this->d->m_bridge->deviceStop(deviceId); + } + this->d->m_running = false; this->d->stopTimer(); this->d->m_currentFrame.clear(); @@ -458,10 +491,17 @@ void AkVCam::StreamPrivate::streamLoop(CFRunLoopTimerRef timer, void *info) self->m_mutex.lock(); - if (self->m_currentFrame.format().size() < 1) - self->sendFrame(self->randomFrame()); - else - self->sendFrame(self->m_currentFrame); + UInt32 direction = 0; + self->self->m_properties.getProperty(kCMIOStreamPropertyDirection, &direction); + + if (Stream::Direction(direction) == Stream::Output) { + if (self->m_currentFrame.format().size() < 1) + self->sendFrame(self->randomFrame()); + else + self->sendFrame(self->m_currentFrame); + } else { + self->requestFrame(); + } self->m_mutex.unlock(); } @@ -559,6 +599,97 @@ void AkVCam::StreamPrivate::sendFrame(const VideoFrame &frame) this->m_queueAlteredRefCon); } +void AkVCam::StreamPrivate::requestFrame() +{ + AkLogFunction(); + + if (this->m_queue->fullness() >= 1.0f) + return; + + bool resync = false; + auto hostTime = CFAbsoluteTimeGetCurrent(); + auto pts = CMTimeMake(int64_t(hostTime), 1e9); + auto ptsDiff = CMTimeGetSeconds(CMTimeSubtract(this->m_pts, pts)); + + if (CMTimeCompare(pts, this->m_pts) == 0) + return; + + Float64 fps = 0; + this->self->m_properties.getProperty(kCMIOStreamPropertyFrameRate, &fps); + + if (CMTIME_IS_INVALID(this->m_pts) + || ptsDiff < 0 + || ptsDiff > 2. / fps) { + this->m_pts = pts; + resync = true; + } + + CMIOStreamClockPostTimingEvent(this->m_pts, + UInt64(hostTime), + resync, + this->m_clock->ref()); + + if (!this->m_queueAltered) + return; + + this->m_queueAltered(this->self->m_objectID, + nullptr, + this->m_queueAlteredRefCon); + + for (;;) { + auto videoBuffer = this->m_queue->dequeue(); + + if (!videoBuffer) + break; + + // Read frame data. + auto imageBuffer = CMSampleBufferGetImageBuffer(videoBuffer); + auto dataBuffer = CMSampleBufferGetDataBuffer(videoBuffer); + auto formatDesc = CMSampleBufferGetFormatDescription(videoBuffer); + auto fourCC = CMFormatDescriptionGetMediaSubType(formatDesc); + auto size = CMVideoFormatDescriptionGetDimensions(formatDesc); + Float64 fps = 0; + this->self->m_properties.getProperty(kCMIOStreamPropertyFrameRate, &fps); + VideoFormat videoFormat(formatFromCM(fourCC), + size.width, + size.height, + {{int64_t(std::round(fps)), 1}}); + VideoFrame videoFrame(videoFormat); + + if (imageBuffer) { + size_t dataSize = CVPixelBufferGetDataSize(imageBuffer); + CVPixelBufferLockBaseAddress(imageBuffer, 0); + void *data = CVPixelBufferGetBaseAddress(imageBuffer); + memcpy(videoFrame.data().data(), + data, + std::min(videoFrame.data().size(), dataSize)); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + } else if (dataBuffer) { + size_t lengthAtOffset = 0; + size_t dataSize = 0; + char *data = nullptr; + CMBlockBufferGetDataPointer(dataBuffer, + 0, + &lengthAtOffset, + &dataSize, + &data); + memcpy(videoFrame.data().data(), + data, + std::min(videoFrame.data().size(), dataSize)); + } + + CFRelease(videoBuffer); + + std::string deviceId; + this->self->properties().getProperty(kCMIODevicePropertyDeviceUID, + &deviceId); + this->m_bridge->write(deviceId, videoFrame); + } + + auto duration = CMTimeMake(1e3, int32_t(1e3 * fps)); + this->m_pts = CMTimeAdd(this->m_pts, duration); +} + void AkVCam::StreamPrivate::updateTestFrame() { this->m_testFrameAdapted = this->applyAdjusts(this->m_testFrame); diff --git a/cmio/VirtualCamera/src/stream.h b/cmio/VirtualCamera/src/stream.h index 390ddf1..68d5585 100644 --- a/cmio/VirtualCamera/src/stream.h +++ b/cmio/VirtualCamera/src/stream.h @@ -38,12 +38,19 @@ namespace AkVCam class Stream: public Object { public: + enum Direction + { + Output, + Input + }; + Stream(bool registerObject=false, Object *m_parent=nullptr); Stream(const Stream &other) = delete; ~Stream(); OSStatus createObject(); OSStatus registerObject(bool regist=true); + void setBridge(IpcBridge *bridge); void setFormats(const std::vector &formats); void setFormat(const VideoFormat &format); void setFrameRate(const Fraction &frameRate); diff --git a/dshow/VCamIPC/src/ipcbridge.cpp b/dshow/VCamIPC/src/ipcbridge.cpp index defb2d0..df15246 100644 --- a/dshow/VCamIPC/src/ipcbridge.cpp +++ b/dshow/VCamIPC/src/ipcbridge.cpp @@ -658,7 +658,8 @@ bool AkVCam::IpcBridge::canApply(AkVCam::IpcBridge::Operation operation) const } std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, - const std::vector &formats) + const std::vector &formats, + DeviceType type) { AkLogFunction();