Manager command line options more or less defined. Working on input device support for Mac.
This commit is contained in:
parent
fd0c716c50
commit
bde41facc9
18 changed files with 1128 additions and 411 deletions
|
@ -36,8 +36,12 @@ CONFIG -= qt
|
|||
|
||||
TARGET = AkVCamManager
|
||||
|
||||
HEADERS = \
|
||||
src/cmdparser.h
|
||||
|
||||
SOURCES = \
|
||||
src/main.cpp
|
||||
src/main.cpp \
|
||||
src/cmdparser.cpp
|
||||
|
||||
INCLUDEPATH += \
|
||||
.. \
|
||||
|
|
647
Manager/src/cmdparser.cpp
Normal file
647
Manager/src/cmdparser.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Web-Site: http://webcamoid.github.io/
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <VCamUtils/src/ipcbridge.h>
|
||||
|
||||
#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<CmdParserFlags> flags;
|
||||
};
|
||||
|
||||
class CmdParserPrivate
|
||||
{
|
||||
public:
|
||||
std::vector<CmdParserCommand> m_commands;
|
||||
|
||||
std::string basename(const std::string &path);
|
||||
void printFlags(const std::vector<CmdParserFlags> &cmdFlags,
|
||||
size_t indent);
|
||||
size_t maxCommandLength();
|
||||
size_t maxArgumentsLength();
|
||||
size_t maxFlagsLength(const std::vector<CmdParserFlags> &flags);
|
||||
size_t maxFlagsValueLength(const std::vector<CmdParserFlags> &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<CmdParserFlags> &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<CmdParserFlags> &cmdFlags,
|
||||
size_t indent)
|
||||
{
|
||||
std::vector<char> 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<CmdParserFlags> &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<CmdParserFlags> &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<char> 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<CmdParserFlags> &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;
|
||||
}
|
58
Manager/src/cmdparser.h
Normal file
58
Manager/src/cmdparser.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Web-Site: http://webcamoid.github.io/
|
||||
*/
|
||||
|
||||
#ifndef CMDPARSER_H
|
||||
#define CMDPARSER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace AkVCam {
|
||||
class CmdParserPrivate;
|
||||
using StringVector = std::vector<std::string>;
|
||||
using StringMap = std::map<std::string, std::string>;
|
||||
using ProgramOptionsFunc = std::function<int (const StringMap &flags,
|
||||
const StringVector &args)>;
|
||||
|
||||
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
|
|
@ -17,9 +17,9 @@
|
|||
* Web-Site: http://webcamoid.github.io/
|
||||
*/
|
||||
|
||||
#include <VCamUtils/src/ipcbridge.h>
|
||||
#include "cmdparser.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
return 0;
|
||||
return AkVCam::CmdParser().parse(argc, argv);
|
||||
}
|
||||
|
|
|
@ -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<VideoFormat> &formats);
|
||||
const std::vector<VideoFormat> &formats,
|
||||
DeviceType type);
|
||||
|
||||
// Edit a device definition.
|
||||
bool deviceEdit(const std::string &deviceId,
|
||||
|
|
|
@ -156,3 +156,25 @@ std::wstring AkVCam::trimmed(const std::wstring &str)
|
|||
|
||||
return str.substr(size_t(left), strippedLen);
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::split(const std::string &str, char separator)
|
||||
{
|
||||
if (str.empty())
|
||||
return {};
|
||||
|
||||
std::vector<std::string> 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;
|
||||
}
|
||||
|
|
|
@ -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<std::string> split(const std::string &str, char separator);
|
||||
}
|
||||
|
||||
#endif // AKVCAMUTILS_UTILS_H
|
||||
|
|
|
@ -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<VideoFormat> formats;
|
||||
std::vector<std::string> connections;
|
||||
std::string broadcaster;
|
||||
std::vector<std::string> 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<int64_t, XpcMessage> 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<AssistantPeers *> 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<AssistantPeers *> 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<AssistantPeers *> 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<std::string> 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);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
#include <xpc/xpc.h>
|
||||
|
||||
#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<void (xpc_connection_t, xpc_object_t)>;
|
||||
|
|
|
@ -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<std::string> AkVCam::Preferences::readStringList(const std::string &key,
|
||||
const std::vector<std::string> &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<VideoFormat> &formats)
|
||||
const std::vector<VideoFormat> &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<VideoFormat> &formats)
|
||||
const std::vector<VideoFormat> &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::VideoFormat> AkVCam::Preferences::cameraFormats(size_t camer
|
|||
|
||||
return formats;
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::Preferences::cameraConnections(size_t cameraIndex)
|
||||
{
|
||||
AkLogFunction();
|
||||
|
||||
if (cameraIndex < 0 || !cameraIsInput(cameraIndex))
|
||||
return {};
|
||||
|
||||
std::vector<std::string> 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<std::string> AkVCam::Preferences::cameraConnections(const std::string &path)
|
||||
{
|
||||
AkLogFunction();
|
||||
|
||||
return cameraConnections(cameraFromPath(path));
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <vector>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#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<std::string> readStringList(const std::string &key,
|
||||
const std::vector<std::string> &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<VideoFormat> &formats);
|
||||
const std::vector<VideoFormat> &formats,
|
||||
IpcBridge::DeviceType type);
|
||||
std::string addCamera(const std::string &path,
|
||||
const std::wstring &description,
|
||||
const std::vector<VideoFormat> &formats);
|
||||
const std::vector<VideoFormat> &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<VideoFormat> cameraFormats(size_t cameraIndex);
|
||||
std::vector<std::string> cameraConnections(size_t cameraIndex);
|
||||
std::vector<std::string> cameraConnections(const std::string &path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <libproc.h>
|
||||
|
||||
#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<std::string> m_broadcasting;
|
||||
std::map<std::string, std::string> 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<std::string> ¶meters);
|
||||
|
||||
private:
|
||||
std::vector<IpcBridge *> 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<VideoFormat> &formats)
|
||||
const std::vector<VideoFormat> &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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl
|
||||
<< "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
|
||||
<< "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
|
||||
<< std::endl
|
||||
<< "<plist version=\"1.0\">" << std::endl
|
||||
<< " <dict>" << std::endl
|
||||
<< " <key>Label</key>" << std::endl
|
||||
<< " <string>" << CMIO_ASSISTANT_NAME
|
||||
<< "</string>" << std::endl
|
||||
<< " <key>ProgramArguments</key>" << std::endl
|
||||
<< " <array>" << std::endl
|
||||
<< " <string>" << CMIO_PLUGINS_DAL_PATH
|
||||
<< "/"
|
||||
<< CMIO_PLUGIN_NAME
|
||||
<< ".plugin/Contents/Resources/"
|
||||
<< CMIO_PLUGIN_ASSISTANT_NAME
|
||||
<< "</string>" << std::endl
|
||||
<< " <string>--timeout</string>" << std::endl
|
||||
<< " <string>300.0</string>" << std::endl
|
||||
<< " </array>" << std::endl
|
||||
<< " <key>MachServices</key>" << std::endl
|
||||
<< " <dict>" << std::endl
|
||||
<< " <key>" << CMIO_ASSISTANT_NAME
|
||||
<< "</key>" << std::endl
|
||||
<< " <true/>" << std::endl
|
||||
<< " </dict>" << std::endl
|
||||
<< " </dict>" << std::endl
|
||||
<< "</plist>" << 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<std::string> ¶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;
|
||||
}
|
||||
|
|
|
@ -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<PluginInterface *>(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<PluginInterface *>(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<VideoFormat> &formats)
|
||||
const std::vector<VideoFormat> &formats,
|
||||
IpcBridge::DeviceType type)
|
||||
{
|
||||
AkLogFunction();
|
||||
StreamPtr stream;
|
||||
|
||||
// Create one device.
|
||||
auto pluginRef = reinterpret_cast<CMIOHardwarePlugInRef>(this->d);
|
||||
auto device = std::make_shared<Device>(pluginRef);
|
||||
auto device = std::make_shared<Device>(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);
|
||||
|
|
|
@ -79,7 +79,8 @@ namespace AkVCam
|
|||
const std::string &deviceId);
|
||||
bool createDevice(const std::string &deviceId,
|
||||
const std::wstring &description,
|
||||
const std::vector<VideoFormat> &formats);
|
||||
const std::vector<VideoFormat> &formats,
|
||||
IpcBridge::DeviceType type);
|
||||
void destroyDevice(const std::string &deviceId);
|
||||
|
||||
friend struct PluginInterfacePrivate;
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace AkVCam
|
|||
|
||||
T dequeue()
|
||||
{
|
||||
return CMSimpleQueueDequeue(this->m_queue);
|
||||
return T(CMSimpleQueueDequeue(this->m_queue));
|
||||
}
|
||||
|
||||
void clear()
|
||||
|
|
|
@ -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<VideoFormat> &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);
|
||||
|
|
|
@ -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<VideoFormat> &formats);
|
||||
void setFormat(const VideoFormat &format);
|
||||
void setFrameRate(const Fraction &frameRate);
|
||||
|
|
|
@ -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<VideoFormat> &formats)
|
||||
const std::vector<VideoFormat> &formats,
|
||||
DeviceType type)
|
||||
{
|
||||
AkLogFunction();
|
||||
|
||||
|
|
Loading…
Reference in a new issue