Manager command line options more or less defined. Working on input device support for Mac.

This commit is contained in:
Gonzalo Exequiel Pedone 2020-07-11 14:22:23 -03:00
parent fd0c716c50
commit bde41facc9
No known key found for this signature in database
GPG key ID: B8B09E63E9B85BAF
18 changed files with 1128 additions and 411 deletions

View file

@ -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
View 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
View 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

View file

@ -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);
}

View file

@ -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,

View file

@ -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;
}

View file

@ -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

View file

@ -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,12 +39,16 @@
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};
@ -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;
@ -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,16 +301,10 @@ void AkVCam::AssistantPrivate::peerDied()
{
AkLogFunction();
std::vector<AssistantPeers *> allPeers {
&this->m_clients,
&this->m_servers
};
for (auto peers: allPeers) {
for (auto &peer: *peers) {
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(peer.second,
auto reply = xpc_connection_send_message_with_reply_sync(client.second,
dictionary);
xpc_release(dictionary);
auto replyType = xpc_get_type(reply);
@ -318,8 +316,7 @@ void AkVCam::AssistantPrivate::peerDied()
xpc_release(reply);
if (!alive)
this->removePortByName(peer.first);
}
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
};
bool breakLoop = false;
for (auto peers: allPeers) {
for (auto &peer: *peers)
for (auto &peer: this->m_clients)
if (peer.first == portName) {
xpc_release(peer.second);
peers->erase(portName);
breakLoop = true;
this->m_clients.erase(portName);
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);
}

View file

@ -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)>;

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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> &parameters);
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,25 +173,22 @@ 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";
@ -207,7 +196,6 @@ bool AkVCam::IpcBridge::registerPeer(bool asClient)
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> &parameters)
{
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;
}

View file

@ -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);

View file

@ -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;

View file

@ -55,7 +55,7 @@ namespace AkVCam
T dequeue()
{
return CMSimpleQueueDequeue(this->m_queue);
return T(CMSimpleQueueDequeue(this->m_queue));
}
void clear()

View file

@ -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;
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();
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);

View file

@ -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);

View file

@ -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();