Added --fps flag to regulate stream. Added hacks commands.
This commit is contained in:
parent
64296f0223
commit
0957eb4be6
20 changed files with 1228 additions and 34 deletions
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <codecvt>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
|
@ -61,6 +62,15 @@ namespace AkVCam {
|
|||
std::string helpString;
|
||||
ProgramOptionsFunc func;
|
||||
std::vector<CmdParserFlags> flags;
|
||||
bool advanced {false};
|
||||
|
||||
CmdParserCommand();
|
||||
CmdParserCommand(const std::string &command,
|
||||
const std::string &arguments,
|
||||
const std::string &helpString,
|
||||
const ProgramOptionsFunc &func,
|
||||
const std::vector<CmdParserFlags> flags,
|
||||
bool advanced);
|
||||
};
|
||||
|
||||
class CmdParserPrivate
|
||||
|
@ -74,8 +84,8 @@ namespace AkVCam {
|
|||
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 maxCommandLength(bool showAdvancedHelp);
|
||||
size_t maxArgumentsLength(bool showAdvancedHelp);
|
||||
size_t maxFlagsLength(const std::vector<CmdParserFlags> &flags);
|
||||
size_t maxFlagsValueLength(const std::vector<CmdParserFlags> &flags);
|
||||
size_t maxColumnLength(const StringVector &table,
|
||||
|
@ -126,6 +136,9 @@ namespace AkVCam {
|
|||
int setLogLevel(const StringMap &flags, const StringVector &args);
|
||||
int showClients(const StringMap &flags, const StringVector &args);
|
||||
int dumpInfo(const StringMap &flags, const StringVector &args);
|
||||
int hacks(const StringMap &flags, const StringVector &args);
|
||||
int hackInfo(const StringMap &flags, const StringVector &args);
|
||||
int hack(const StringMap &flags, const StringVector &args);
|
||||
void loadGenerals(Settings &settings);
|
||||
VideoFormatMatrix readFormats(Settings &settings);
|
||||
std::vector<VideoFormat> readFormat(Settings &settings);
|
||||
|
@ -154,6 +167,9 @@ AkVCam::CmdParser::CmdParser()
|
|||
this->addFlags("",
|
||||
{"-h", "--help"},
|
||||
"Show help.");
|
||||
this->addFlags("",
|
||||
{"--help-all"},
|
||||
"Show advanced help.");
|
||||
this->addFlags("",
|
||||
{"-v", "--version"},
|
||||
"Show program version.");
|
||||
|
@ -236,6 +252,10 @@ AkVCam::CmdParser::CmdParser()
|
|||
"DEVICE FORMAT WIDTH HEIGHT",
|
||||
"Read frames from stdin and send them to the device.",
|
||||
AKVCAM_BIND_FUNC(CmdParserPrivate::stream));
|
||||
this->addFlags("stream",
|
||||
{"-f", "--fps"},
|
||||
"FPS",
|
||||
"Read stream input at a constant frame rate.");
|
||||
this->addCommand("listen-events",
|
||||
"",
|
||||
"Keep the manager running and listening to global events.",
|
||||
|
@ -297,6 +317,30 @@ AkVCam::CmdParser::CmdParser()
|
|||
"",
|
||||
"Show all information in a parseable XML format.",
|
||||
AKVCAM_BIND_FUNC(CmdParserPrivate::dumpInfo));
|
||||
this->addCommand("hacks",
|
||||
"",
|
||||
"List system hacks to make the virtual camera work.",
|
||||
AKVCAM_BIND_FUNC(CmdParserPrivate::hacks),
|
||||
true);
|
||||
this->addCommand("hack-info",
|
||||
"HACK",
|
||||
"Show hack information.",
|
||||
AKVCAM_BIND_FUNC(CmdParserPrivate::hackInfo),
|
||||
true);
|
||||
this->addFlags("hack-info",
|
||||
{"-s", "--issafe"},
|
||||
"Is hack safe?");
|
||||
this->addFlags("hack-info",
|
||||
{"-c", "--description"},
|
||||
"Show hack description.");
|
||||
this->addCommand("hack",
|
||||
"HACK PARAMS...",
|
||||
"Apply system hack.",
|
||||
AKVCAM_BIND_FUNC(CmdParserPrivate::hack),
|
||||
true);
|
||||
this->addFlags("hack",
|
||||
{"-y", "--yes"},
|
||||
"Accept all risks and continue anyway.");
|
||||
}
|
||||
|
||||
AkVCam::CmdParser::~CmdParser()
|
||||
|
@ -374,7 +418,10 @@ int AkVCam::CmdParser::parse(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
if (this->d->m_ipcBridge.needsRoot(command->command))
|
||||
if (this->d->m_ipcBridge.needsRoot(command->command)
|
||||
|| (command->command == "hack"
|
||||
&& arguments.size() >= 2
|
||||
&& this->d->m_ipcBridge.hackNeedsRoot(arguments[1])))
|
||||
return this->d->m_ipcBridge.sudo(argc, argv);
|
||||
|
||||
return command->func(flags, arguments);
|
||||
|
@ -388,7 +435,8 @@ void AkVCam::CmdParser::setDefaultFuntion(const ProgramOptionsFunc &func)
|
|||
void AkVCam::CmdParser::addCommand(const std::string &command,
|
||||
const std::string &arguments,
|
||||
const std::string &helpString,
|
||||
const ProgramOptionsFunc &func)
|
||||
const ProgramOptionsFunc &func,
|
||||
bool advanced)
|
||||
{
|
||||
auto it =
|
||||
std::find_if(this->d->m_commands.begin(),
|
||||
|
@ -402,12 +450,14 @@ void AkVCam::CmdParser::addCommand(const std::string &command,
|
|||
arguments,
|
||||
helpString,
|
||||
func,
|
||||
{}});
|
||||
{},
|
||||
advanced});
|
||||
} else {
|
||||
it->command = command;
|
||||
it->arguments = arguments;
|
||||
it->helpString = helpString;
|
||||
it->func = func;
|
||||
it->advanced = advanced;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,22 +547,24 @@ void AkVCam::CmdParserPrivate::printFlags(const std::vector<CmdParserFlags> &cmd
|
|||
}
|
||||
}
|
||||
|
||||
size_t AkVCam::CmdParserPrivate::maxCommandLength()
|
||||
size_t AkVCam::CmdParserPrivate::maxCommandLength(bool showAdvancedHelp)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
for (auto &cmd: this->m_commands)
|
||||
length = std::max(cmd.command.size(), length);
|
||||
if (!cmd.advanced || showAdvancedHelp)
|
||||
length = std::max(cmd.command.size(), length);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t AkVCam::CmdParserPrivate::maxArgumentsLength()
|
||||
size_t AkVCam::CmdParserPrivate::maxArgumentsLength(bool showAdvancedHelp)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
for (auto &cmd: this->m_commands)
|
||||
length = std::max(cmd.arguments.size(), length);
|
||||
if (!cmd.advanced || showAdvancedHelp)
|
||||
length = std::max(cmd.arguments.size(), length);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
@ -674,7 +726,9 @@ std::string AkVCam::CmdParserPrivate::flagValue(const AkVCam::StringMap &flags,
|
|||
int AkVCam::CmdParserPrivate::defaultHandler(const StringMap &flags,
|
||||
const StringVector &args)
|
||||
{
|
||||
if (flags.empty() || this->containsFlag(flags, "", "-h"))
|
||||
if (flags.empty()
|
||||
|| this->containsFlag(flags, "", "-h")
|
||||
|| this->containsFlag(flags, "", "--help-all"))
|
||||
return this->showHelp(flags, args);
|
||||
|
||||
if (this->containsFlag(flags, "", "-v")) {
|
||||
|
@ -707,11 +761,13 @@ int AkVCam::CmdParserPrivate::showHelp(const StringMap &flags,
|
|||
std::cout << "Commands:" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
auto maxCmdLen = this->maxCommandLength();
|
||||
auto maxArgsLen = this->maxArgumentsLength();
|
||||
bool showAdvancedHelp = this->containsFlag(flags, "", "--help-all");
|
||||
auto maxCmdLen = this->maxCommandLength(showAdvancedHelp);
|
||||
auto maxArgsLen = this->maxArgumentsLength(showAdvancedHelp);
|
||||
|
||||
for (auto &cmd: this->m_commands) {
|
||||
if (cmd.command.empty())
|
||||
if (cmd.command.empty()
|
||||
|| (cmd.advanced && !showAdvancedHelp))
|
||||
continue;
|
||||
|
||||
std::cout << " "
|
||||
|
@ -1230,6 +1286,30 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags,
|
|||
return -1;
|
||||
}
|
||||
|
||||
auto fpsStr = this->flagValue(flags, "stream", "-f");
|
||||
double fps = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
if (!fpsStr.empty()) {
|
||||
p = nullptr;
|
||||
fps = int(strtod(fpsStr.c_str(), &p));
|
||||
|
||||
if (*p) {
|
||||
if (!Fraction::isFraction(fpsStr)) {
|
||||
std::cerr << "The framerate must be a number or a fraction." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
fps = Fraction(fpsStr).value();
|
||||
}
|
||||
|
||||
if (fps <= 0 || std::isinf(fps)) {
|
||||
std::cerr << "The framerate is out of range." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
VideoFormat fmt(format, int(width), int(height), {{30, 1}});
|
||||
|
||||
if (!this->m_ipcBridge.deviceStart(deviceId, fmt)) {
|
||||
|
@ -1253,6 +1333,20 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags,
|
|||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
#endif
|
||||
|
||||
auto clock = [] (const std::chrono::time_point<std::chrono::high_resolution_clock> &since) -> double {
|
||||
return std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::high_resolution_clock::now() - since).count();
|
||||
};
|
||||
|
||||
const double minThreshold = 0.04;
|
||||
const double maxThreshold = 0.1;
|
||||
const double framedupThreshold = 0.1;
|
||||
const double nosyncThreshold = 10.0;
|
||||
|
||||
double lastPts = 0.0;
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
double drift = 0;
|
||||
uint64_t i = 0;
|
||||
|
||||
do {
|
||||
std::cin.read(reinterpret_cast<char *>(frame.data().data()
|
||||
+ bufferSize),
|
||||
|
@ -1260,7 +1354,46 @@ int AkVCam::CmdParserPrivate::stream(const AkVCam::StringMap &flags,
|
|||
bufferSize += size_t(std::cin.gcount());
|
||||
|
||||
if (bufferSize == frame.data().size()) {
|
||||
this->m_ipcBridge.write(deviceId, frame);
|
||||
if (fpsStr.empty()) {
|
||||
this->m_ipcBridge.write(deviceId, frame);
|
||||
} else {
|
||||
double pts = double(i) / fps;
|
||||
|
||||
for (;;) {
|
||||
double clock_pts = clock(t0) + drift;
|
||||
double diff = pts - clock_pts;
|
||||
double delay = pts - lastPts;
|
||||
double syncThreshold =
|
||||
std::max(minThreshold,
|
||||
std::min(delay, maxThreshold));
|
||||
|
||||
if (!std::isnan(diff)
|
||||
&& std::abs(diff) < nosyncThreshold
|
||||
&& delay < framedupThreshold) {
|
||||
if (diff <= -syncThreshold) {
|
||||
lastPts = pts;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (diff > syncThreshold) {
|
||||
std::this_thread::sleep_for(std::chrono::duration<double>(diff - syncThreshold));
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
drift = clock(t0) - pts;
|
||||
}
|
||||
|
||||
this->m_ipcBridge.write(deviceId, frame);
|
||||
lastPts = pts;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
bufferSize = 0;
|
||||
}
|
||||
} while (!std::cin.eof() && !exit);
|
||||
|
@ -1365,8 +1498,6 @@ int AkVCam::CmdParserPrivate::showControls(const StringMap &flags,
|
|||
int AkVCam::CmdParserPrivate::readControl(const StringMap &flags,
|
||||
const StringVector &args)
|
||||
{
|
||||
UNUSED(flags);
|
||||
|
||||
if (args.size() < 3) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
|
@ -1389,9 +1520,9 @@ int AkVCam::CmdParserPrivate::readControl(const StringMap &flags,
|
|||
std::cout << control.value << std::endl;
|
||||
} else {
|
||||
if (this->containsFlag(flags, "get-control", "-c")) {
|
||||
auto typeStr = typeStrMap();
|
||||
std::cout << control.description << std::endl;
|
||||
}
|
||||
|
||||
if (this->containsFlag(flags, "get-control", "-t")) {
|
||||
auto typeStr = typeStrMap();
|
||||
std::cout << typeStr[control.type] << std::endl;
|
||||
|
@ -1871,6 +2002,146 @@ int AkVCam::CmdParserPrivate::dumpInfo(const AkVCam::StringMap &flags,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int AkVCam::CmdParserPrivate::hacks(const AkVCam::StringMap &flags,
|
||||
const AkVCam::StringVector &args)
|
||||
{
|
||||
UNUSED(flags);
|
||||
UNUSED(args);
|
||||
|
||||
auto hacks = this->m_ipcBridge.hacks();
|
||||
|
||||
if (hacks.empty())
|
||||
return 0;
|
||||
|
||||
if (this->m_parseable) {
|
||||
for (auto &hack: hacks)
|
||||
std::cout << hack << std::endl;
|
||||
} else {
|
||||
std::cout << "Hacks are intended to fix common problems with the "
|
||||
"virtual camera, and are intended to be used by developers "
|
||||
"and advanced users only." << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "WARNING: Unsafe hacks can brick your system, make it "
|
||||
"unstable, or expose it to a serious security risk. You "
|
||||
"are solely responsible of whatever happens for using "
|
||||
"them. You been warned, don't come and cry later."
|
||||
<< std::endl;
|
||||
std::cout << std::endl;
|
||||
std::vector<std::string> table {
|
||||
"Hack",
|
||||
"Is safe?",
|
||||
"Description"
|
||||
};
|
||||
auto columns = table.size();
|
||||
|
||||
for (auto &hack: hacks) {
|
||||
table.push_back(hack);
|
||||
table.push_back(this->m_ipcBridge.hackIsSafe(hack)? "Yes": "No");
|
||||
table.push_back(this->m_ipcBridge.hackDescription(hack));
|
||||
}
|
||||
|
||||
this->drawTable(table, columns);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AkVCam::CmdParserPrivate::hackInfo(const AkVCam::StringMap &flags,
|
||||
const AkVCam::StringVector &args)
|
||||
{
|
||||
if (args.size() < 2) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto hack = args[1];
|
||||
auto hacks = this->m_ipcBridge.hacks();
|
||||
auto dit = std::find(hacks.begin(), hacks.end(), hack);
|
||||
|
||||
if (dit == hacks.end()) {
|
||||
std::cerr << "Unknown hack: " << hack << "." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this->containsFlag(flags, "hack-info", "-c"))
|
||||
std::cout << this->m_ipcBridge.hackDescription(hack) << std::endl;
|
||||
|
||||
if (this->containsFlag(flags, "hack-info", "-s")) {
|
||||
if (this->m_ipcBridge.hackIsSafe(hack))
|
||||
std::cout << "Yes" << std::endl;
|
||||
else
|
||||
std::cout << "No" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AkVCam::CmdParserPrivate::hack(const AkVCam::StringMap &flags,
|
||||
const AkVCam::StringVector &args)
|
||||
{
|
||||
if (args.size() < 2) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto hack = args[1];
|
||||
auto hacks = this->m_ipcBridge.hacks();
|
||||
auto dit = std::find(hacks.begin(), hacks.end(), hack);
|
||||
|
||||
if (dit == hacks.end()) {
|
||||
std::cerr << "Unknown hack: " << hack << "." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool accepted = this->m_parseable | this->m_ipcBridge.hackIsSafe(hack);
|
||||
|
||||
if (!accepted && !this->m_parseable) {
|
||||
std::cout << "WARNING: Applying this hack can brick your system, make "
|
||||
"it unstable, or expose it to a serious security risk. "
|
||||
"Agreeing to continue, you accept the full responsability "
|
||||
"of whatever happens from now on."
|
||||
<< std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
if (this->containsFlag(flags, "hack", "-y")) {
|
||||
std::cout << "You agreed to continue from command line."
|
||||
<< std::endl;
|
||||
std::cout << std::endl;
|
||||
accepted = true;
|
||||
} else {
|
||||
std::cout << "If you agree to continue write YES: ";
|
||||
std::string answer;
|
||||
std::cin >> answer;
|
||||
std::cout << std::endl;
|
||||
accepted = answer == "YES";
|
||||
}
|
||||
}
|
||||
|
||||
if (!accepted) {
|
||||
std::cerr << "Hack not applied." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
StringVector hargs;
|
||||
|
||||
for (size_t i = 2; i < args.size(); i++)
|
||||
hargs.push_back(args[i]);
|
||||
|
||||
auto result = this->m_ipcBridge.execHack(hack, hargs);
|
||||
|
||||
if (result == 0)
|
||||
std::cout << "Success" << std::endl;
|
||||
else
|
||||
std::cout << "Failed" << std::endl;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AkVCam::CmdParserPrivate::loadGenerals(Settings &settings)
|
||||
{
|
||||
settings.beginGroup("General");
|
||||
|
@ -2081,3 +2352,22 @@ std::string AkVCam::operator *(size_t n, const std::string &str)
|
|||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
AkVCam::CmdParserCommand::CmdParserCommand()
|
||||
{
|
||||
}
|
||||
|
||||
AkVCam::CmdParserCommand::CmdParserCommand(const std::string &command,
|
||||
const std::string &arguments,
|
||||
const std::string &helpString,
|
||||
const AkVCam::ProgramOptionsFunc &func,
|
||||
const std::vector<AkVCam::CmdParserFlags> flags,
|
||||
bool advanced):
|
||||
command(command),
|
||||
arguments(arguments),
|
||||
helpString(helpString),
|
||||
func(func),
|
||||
flags(flags),
|
||||
advanced(advanced)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ namespace AkVCam {
|
|||
void addCommand(const std::string &command,
|
||||
const std::string &arguments,
|
||||
const std::string &helpString,
|
||||
const ProgramOptionsFunc &func);
|
||||
const ProgramOptionsFunc &func,
|
||||
bool advanced=false);
|
||||
void addFlags(const std::string &command,
|
||||
const StringVector &flags,
|
||||
const std::string &value,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* Web-Site: http://webcamoid.github.io/
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
@ -143,6 +144,42 @@ std::string AkVCam::Fraction::toString() const
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
bool AkVCam::Fraction::isInfinity() const
|
||||
{
|
||||
return this->d->m_num != 0 && this->d->m_den == 0;
|
||||
}
|
||||
|
||||
int AkVCam::Fraction::sign() const
|
||||
{
|
||||
return std::signbit(this->d->m_num) == std::signbit(this->d->m_den)? 1: -1;
|
||||
}
|
||||
|
||||
bool AkVCam::Fraction::isFraction(const std::string &str)
|
||||
{
|
||||
auto pos = str.find('/');
|
||||
|
||||
if (pos == std::string::npos) {
|
||||
auto strCpy = trimmed(str);
|
||||
char *p = nullptr;
|
||||
strtol(strCpy.c_str(), &p, 10);
|
||||
|
||||
if (*p)
|
||||
return false;
|
||||
} else {
|
||||
auto numStr = trimmed(str.substr(0, pos));
|
||||
auto denStr = trimmed(str.substr(pos + 1));
|
||||
char *p = nullptr;
|
||||
char *q = nullptr;
|
||||
strtol(numStr.c_str(), &p, 10);
|
||||
strtol(denStr.c_str(), &q, 10);
|
||||
|
||||
if (*p || *q)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ostream &operator <<(std::ostream &os, const AkVCam::Fraction &fraction)
|
||||
{
|
||||
os << fraction.toString();
|
||||
|
|
|
@ -47,6 +47,9 @@ namespace AkVCam
|
|||
int64_t &den();
|
||||
double value() const;
|
||||
std::string toString() const;
|
||||
bool isInfinity() const;
|
||||
int sign() const;
|
||||
static bool isFraction(const std::string &str);
|
||||
|
||||
private:
|
||||
FractionPrivate *d;
|
||||
|
|
|
@ -174,6 +174,15 @@ namespace AkVCam
|
|||
bool needsRoot(const std::string &operation) const;
|
||||
int sudo(int argc, char **argv) const;
|
||||
|
||||
/* Hacks */
|
||||
|
||||
std::vector<std::string> hacks() const;
|
||||
std::string hackDescription(const std::string &hack) const;
|
||||
bool hackIsSafe(const std::string &hack) const;
|
||||
bool hackNeedsRoot(const std::string &hack) const;
|
||||
int execHack(const std::string &hack,
|
||||
const std::vector<std::string> &args);
|
||||
|
||||
private:
|
||||
IpcBridgePrivate *d;
|
||||
|
||||
|
|
|
@ -151,3 +151,11 @@ std::pair<std::string, std::string> AkVCam::splitOnce(const std::string &str,
|
|||
|
||||
return {first, second};
|
||||
}
|
||||
|
||||
void AkVCam::move(const std::string &from, const std::string &to)
|
||||
{
|
||||
std::ifstream infile(from, std::ios::in | std::ios::binary);
|
||||
std::ofstream outfile(to, std::ios::out | std::ios::binary);
|
||||
outfile << infile.rdbuf();
|
||||
std::remove(from.c_str());
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ namespace AkVCam
|
|||
std::vector<std::string> split(const std::string &str, char separator);
|
||||
std::pair<std::string, std::string> splitOnce(const std::string &str,
|
||||
const std::string &separator);
|
||||
void move(const std::string &from, const std::string &to);
|
||||
}
|
||||
|
||||
#endif // AKVCAMUTILS_UTILS_H
|
||||
|
|
|
@ -2,7 +2,7 @@ version: 9.0.0.{build}-{branch}
|
|||
os: MinGW
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
|
@ -20,7 +20,7 @@ environment:
|
|||
|
||||
matrix:
|
||||
- CMAKE_GENERATOR: MSYS Makefiles
|
||||
- CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
- CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
|
||||
install:
|
||||
- ports\ci\appveyor\install_deps.bat
|
||||
|
|
|
@ -45,8 +45,32 @@
|
|||
#define AKVCAM_BIND_FUNC(member) \
|
||||
std::bind(&member, this, std::placeholders::_1, std::placeholders::_2)
|
||||
|
||||
#define AKVCAM_BIND_HACK_FUNC(member) \
|
||||
std::bind(&member, this, std::placeholders::_1)
|
||||
|
||||
namespace AkVCam
|
||||
{
|
||||
class Hack
|
||||
{
|
||||
public:
|
||||
using HackFunc = std::function<int (const std::vector<std::string> &args)>;
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
bool isSafe {false};
|
||||
bool needsRoot {false};
|
||||
HackFunc func;
|
||||
|
||||
Hack();
|
||||
Hack(const std::string &name,
|
||||
const std::string &description,
|
||||
bool isSafe,
|
||||
bool needsRoot,
|
||||
const HackFunc &func);
|
||||
Hack(const Hack &other);
|
||||
Hack &operator =(const Hack &other);
|
||||
};
|
||||
|
||||
class IpcBridgePrivate
|
||||
{
|
||||
public:
|
||||
|
@ -81,6 +105,22 @@ namespace AkVCam
|
|||
// Utility methods
|
||||
bool fileExists(const std::string &path) const;
|
||||
static std::string locatePluginPath();
|
||||
bool isRoot() const;
|
||||
std::vector<std::string> listServices() const;
|
||||
std::vector<std::string> listDisabledServices() const;
|
||||
inline bool isServiceLoaded(const std::string &service) const;
|
||||
inline bool isServiceDisabled(const std::string &service) const;
|
||||
bool readEntitlements(const std::string &app,
|
||||
const std::string &output) const;
|
||||
|
||||
// Hacks
|
||||
const std::vector<Hack> &hacks();
|
||||
int setServiceUp(const std::vector<std::string> &args);
|
||||
int setServiceDown(const std::vector<std::string> &args);
|
||||
int disableLibraryValidation(const std::vector<std::string> &args);
|
||||
int codeResign(const std::vector<std::string> &args);
|
||||
int unsign(const std::vector<std::string> &args);
|
||||
int disableSIP(const std::vector<std::string> &args);
|
||||
|
||||
private:
|
||||
std::vector<IpcBridge *> m_bridges;
|
||||
|
@ -763,15 +803,64 @@ bool AkVCam::IpcBridge::removeListener(const std::string &deviceId)
|
|||
|
||||
bool AkVCam::IpcBridge::needsRoot(const std::string &operation) const
|
||||
{
|
||||
UNUSED(operation);
|
||||
static const std::vector<std::string> operations;
|
||||
auto it = std::find(operations.begin(), operations.end(), operation);
|
||||
|
||||
return false;
|
||||
return it != operations.end() && !this->d->isRoot();
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridge::sudo(int argc, char **argv) const
|
||||
{
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
std::cerr << "You must run this command with administrator privileges." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::IpcBridge::hacks() const
|
||||
{
|
||||
std::vector<std::string> hacks;
|
||||
|
||||
for (auto &hack: this->d->hacks())
|
||||
hacks.push_back(hack.name);
|
||||
|
||||
return hacks;
|
||||
}
|
||||
|
||||
std::string AkVCam::IpcBridge::hackDescription(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.description;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridge::hackIsSafe(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.isSafe;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridge::hackNeedsRoot(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.needsRoot && !this->d->isRoot();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridge::execHack(const std::string &hack,
|
||||
const std::vector<std::string> &args)
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.func(args);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1061,3 +1150,470 @@ std::string AkVCam::IpcBridgePrivate::locatePluginPath()
|
|||
|
||||
return realPath(dirName + "/../..");
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridgePrivate::isRoot() const
|
||||
{
|
||||
AkLogFunction();
|
||||
|
||||
return getuid() == 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::IpcBridgePrivate::listServices() const
|
||||
{
|
||||
std::vector<std::string> services;
|
||||
auto proc = popen("launchctl list", "r");
|
||||
|
||||
if (proc) {
|
||||
while (!feof(proc)) {
|
||||
char line[1024];
|
||||
|
||||
if (!fgets(line, 1024, proc))
|
||||
break;
|
||||
|
||||
if (strncmp(line, "PID", 3) == 0)
|
||||
continue;
|
||||
|
||||
char *pline = strtok(line, " \n\r\t");
|
||||
|
||||
for (size_t i = 0; i < 3 && pline != nullptr; i++) {
|
||||
if (i == 2)
|
||||
services.push_back(pline);
|
||||
|
||||
pline = strtok(nullptr, " \n\r\t");
|
||||
}
|
||||
}
|
||||
|
||||
pclose(proc);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::IpcBridgePrivate::listDisabledServices() const
|
||||
{
|
||||
std::vector<std::string> services;
|
||||
auto proc = popen("launchctl print-disabled system/", "r");
|
||||
|
||||
if (proc) {
|
||||
while (!feof(proc)) {
|
||||
char line[1024];
|
||||
|
||||
if (!fgets(line, 1024, proc))
|
||||
break;
|
||||
|
||||
if (strncmp(line, "\t", 1) != 0)
|
||||
continue;
|
||||
|
||||
std::string service;
|
||||
char *pline = strtok(line, " ");
|
||||
|
||||
for (size_t i = 0; i < 3 && pline; i++) {
|
||||
if (i == 0)
|
||||
service = std::string(pline).substr(1, strlen(pline) - 2);
|
||||
|
||||
if (i == 2 && strncmp(pline, "true", 4) == 0)
|
||||
services.push_back(service);
|
||||
|
||||
pline = strtok(nullptr, " ");
|
||||
}
|
||||
}
|
||||
|
||||
pclose(proc);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridgePrivate::isServiceLoaded(const std::string &service) const
|
||||
{
|
||||
auto services = this->listServices();
|
||||
|
||||
return std::find(services.begin(), services.end(), service) != services.end();
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridgePrivate::isServiceDisabled(const std::string &service) const
|
||||
{
|
||||
auto services = this->listDisabledServices();
|
||||
|
||||
return std::find(services.begin(), services.end(), service) != services.end();
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridgePrivate::readEntitlements(const std::string &app,
|
||||
const std::string &output) const
|
||||
{
|
||||
bool writen = false;
|
||||
std::string cmd = "codesign -d --entitlements - \"" + app + "\"";
|
||||
auto proc = popen(cmd.c_str(), "r");
|
||||
|
||||
if (proc) {
|
||||
auto entitlements = fopen(output.c_str(), "w");
|
||||
|
||||
if (entitlements) {
|
||||
for (size_t i = 0; !feof(proc); i++) {
|
||||
char data[1024];
|
||||
auto len = fread(data, 1, 1024, proc);
|
||||
|
||||
if (len < 1)
|
||||
break;
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
if (i == 0)
|
||||
offset = std::string(data, len).find("<?xml");
|
||||
|
||||
fwrite(data + offset, 1, len - offset, entitlements);
|
||||
writen = true;
|
||||
}
|
||||
|
||||
fclose(entitlements);
|
||||
}
|
||||
|
||||
pclose(proc);
|
||||
}
|
||||
|
||||
return writen;
|
||||
}
|
||||
|
||||
const std::vector<AkVCam::Hack> &AkVCam::IpcBridgePrivate::hacks()
|
||||
{
|
||||
static const std::vector<AkVCam::Hack> hacks {
|
||||
{"set-service-up",
|
||||
"Setup and start virtual camera service if isn't working",
|
||||
true,
|
||||
true,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::setServiceUp)},
|
||||
{"set-service-down",
|
||||
"Stop and unregister virtual camera service",
|
||||
true,
|
||||
true,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::setServiceDown)},
|
||||
{"disable-library-validation",
|
||||
"Disable external plugins validation in app bundle",
|
||||
false,
|
||||
false,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::disableLibraryValidation)},
|
||||
{"code-re-sign",
|
||||
"Remove app code signature and re-sign it with a developer signature",
|
||||
false,
|
||||
false,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::codeResign)},
|
||||
{"unsign",
|
||||
"Remove app code signature",
|
||||
false,
|
||||
false,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::unsign)},
|
||||
{"disable-sip",
|
||||
"Disable System Integrity Protection",
|
||||
false,
|
||||
false,
|
||||
AKVCAM_BIND_HACK_FUNC(IpcBridgePrivate::disableSIP)}
|
||||
};
|
||||
|
||||
return hacks;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::setServiceUp(const std::vector<std::string> &args)
|
||||
{
|
||||
UNUSED(args);
|
||||
AkLogFunction();
|
||||
std::string pluginPath = this->locatePluginPath();
|
||||
static const std::string dstPluginPath =
|
||||
"/Library/CoreMediaIO/Plug-Ins/DAL/" CMIO_PLUGIN_NAME ".plugin";
|
||||
|
||||
if (!fileExists(dstPluginPath))
|
||||
if (symlink(pluginPath.c_str(), dstPluginPath.c_str()) != 0) {
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const std::string daemonPlist =
|
||||
"/Library/LaunchDaemons/" CMIO_ASSISTANT_NAME ".plist";
|
||||
|
||||
if (!fileExists(daemonPlist)) {
|
||||
std::ofstream plist(daemonPlist);
|
||||
|
||||
if (!plist.is_open())
|
||||
return -1;
|
||||
|
||||
plist << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
|
||||
plist << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" ";
|
||||
plist << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << std::endl;
|
||||
plist << "<plist version=\"1.0\">" << std::endl;
|
||||
plist << " <dict>" << std::endl;
|
||||
plist << " <key>Label</key>" << std::endl;
|
||||
plist << " <string>" CMIO_ASSISTANT_NAME "</string>" << std::endl;
|
||||
plist << " <key>ProgramArguments</key>" << std::endl;
|
||||
plist << " <array>" << std::endl;
|
||||
plist << " <string>";
|
||||
plist << pluginPath;
|
||||
plist << "/Contents/Resources/AkVCamAssistant";
|
||||
plist << "</string>" << std::endl;
|
||||
plist << " <string>--timeout</string>" << std::endl;
|
||||
plist << " <string>300.0</string>" << std::endl;
|
||||
plist << " </array>" << std::endl;
|
||||
plist << " <key>MachServices</key>" << std::endl;
|
||||
plist << " <dict>" << std::endl;
|
||||
plist << " <key>" CMIO_ASSISTANT_NAME "</key>" << std::endl;
|
||||
plist << " <true/>" << std::endl;
|
||||
plist << " </dict>" << std::endl;
|
||||
plist << " <key>StandardOutPath</key>" << std::endl;
|
||||
plist << " <string>/tmp/AkVCamAssistant.log</string>" << std::endl;
|
||||
plist << " <key>StandardErrorPath</key>" << std::endl;
|
||||
plist << " <string>/tmp/AkVCamAssistant.log</string>" << std::endl;
|
||||
plist << " </dict>" << std::endl;
|
||||
plist << "</plist>" << std::endl;
|
||||
plist.close();
|
||||
}
|
||||
|
||||
if (this->isServiceDisabled(CMIO_ASSISTANT_NAME)) {
|
||||
int result = system("launchctl enable system/" CMIO_ASSISTANT_NAME);
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!this->isServiceLoaded(CMIO_ASSISTANT_NAME)) {
|
||||
auto cmd = "launchctl bootstrap system " + daemonPlist;
|
||||
int result = system(cmd.c_str());
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::setServiceDown(const std::vector<std::string> &args)
|
||||
{
|
||||
UNUSED(args);
|
||||
AkLogFunction();
|
||||
|
||||
static const std::string daemonPlist =
|
||||
"/Library/LaunchDaemons/" CMIO_ASSISTANT_NAME ".plist";
|
||||
|
||||
if (fileExists(daemonPlist)) {
|
||||
if (this->isServiceLoaded(CMIO_ASSISTANT_NAME)) {
|
||||
auto cmd = "launchctl bootout system " + daemonPlist;
|
||||
int result = system(cmd.c_str());
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
|
||||
std::remove(daemonPlist.c_str());
|
||||
}
|
||||
|
||||
static const std::string dstPluginPath =
|
||||
"/Library/CoreMediaIO/Plug-Ins/DAL/" CMIO_PLUGIN_NAME ".plugin";
|
||||
|
||||
if (this->fileExists(dstPluginPath))
|
||||
if (unlink(dstPluginPath.c_str()) != 0) {
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::disableLibraryValidation(const std::vector<std::string> &args)
|
||||
{
|
||||
if (args.size() < 1) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!this->fileExists(args[0])) {
|
||||
std::cerr << "No such file or directory." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const std::string entitlementsXml = "/tmp/entitlements.xml";
|
||||
static const std::string dlv =
|
||||
"com.apple.security.cs.disable-library-validation";
|
||||
|
||||
if (this->readEntitlements(args[0], entitlementsXml)) {
|
||||
auto nsdlv = [NSString
|
||||
stringWithCString: dlv.c_str()
|
||||
encoding: [NSString defaultCStringEncoding]];
|
||||
auto nsEntitlementsXml = [NSString
|
||||
stringWithCString: entitlementsXml.c_str()
|
||||
encoding: [NSString defaultCStringEncoding]];
|
||||
auto nsEntitlementsXmlUrl = [NSURL
|
||||
fileURLWithPath: nsEntitlementsXml];
|
||||
NSError *error = nil;
|
||||
auto entitlements =
|
||||
[[NSXMLDocument alloc]
|
||||
initWithContentsOfURL: nsEntitlementsXmlUrl
|
||||
options: 0
|
||||
error: &error];
|
||||
[nsEntitlementsXmlUrl release];
|
||||
const std::string xpath =
|
||||
"/plist/dict/key[contains(text(), \"" + dlv + "\")]";
|
||||
auto nsxpath = [NSString
|
||||
stringWithCString: xpath.c_str()
|
||||
encoding: [NSString defaultCStringEncoding]];
|
||||
auto nodes = [entitlements nodesForXPath: nsxpath error: &error];
|
||||
[nsxpath release];
|
||||
|
||||
if ([nodes count] < 1) {
|
||||
auto key = [[NSXMLElement alloc] initWithName: @"key" stringValue: nsdlv];
|
||||
[(NSXMLElement *) entitlements.rootElement.children[0] addChild: key];
|
||||
[key release];
|
||||
auto value = [[NSXMLElement alloc] initWithName: @"true"];
|
||||
[(NSXMLElement *) entitlements.rootElement.children[0] addChild: value];
|
||||
[value release];
|
||||
} else {
|
||||
for (NSXMLNode *node: nodes) {
|
||||
auto value = [[NSXMLElement alloc] initWithName: @"true"];
|
||||
[(NSXMLElement *) node.parent replaceChildAtIndex: node.nextSibling.index withNode: value];
|
||||
[value release];
|
||||
}
|
||||
}
|
||||
|
||||
auto data = [entitlements XMLDataWithOptions: NSXMLNodePrettyPrint
|
||||
| NSXMLNodeCompactEmptyElement];
|
||||
[data writeToFile: nsEntitlementsXml atomically: YES];
|
||||
[data release];
|
||||
[entitlements release];
|
||||
[nsEntitlementsXml release];
|
||||
[nsdlv release];
|
||||
} else {
|
||||
std::ofstream entitlements(entitlementsXml);
|
||||
|
||||
if (entitlements.is_open()) {
|
||||
entitlements << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
|
||||
entitlements << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << std::endl;
|
||||
entitlements << "<plist version=\"1.0\">" << std::endl;
|
||||
entitlements << "\t<dict>" << std::endl;
|
||||
entitlements << "\t\t<key>" << dlv << "</key>" << std::endl;
|
||||
entitlements << "\t\t<true/>" << std::endl;
|
||||
entitlements << "\t</dict>" << std::endl;
|
||||
entitlements << "</plist>" << std::endl;
|
||||
entitlements.close();
|
||||
}
|
||||
}
|
||||
|
||||
auto cmd = "codesign --entitlements \""
|
||||
+ entitlementsXml
|
||||
+ "\" -f -s - \""
|
||||
+ args[0]
|
||||
+ "\"";
|
||||
int result = system(cmd.c_str());
|
||||
std::remove(entitlementsXml.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::codeResign(const std::vector<std::string> &args)
|
||||
{
|
||||
if (args.size() < 1) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!this->fileExists(args[0])) {
|
||||
std::cerr << "No such file or directory." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const std::string entitlementsXml = "/tmp/entitlements.xml";
|
||||
std::string cmd;
|
||||
|
||||
if (this->readEntitlements(args[0], entitlementsXml))
|
||||
cmd = "codesign --entitlements \""
|
||||
+ entitlementsXml
|
||||
+ "\" -f -s - \""
|
||||
+ args[0]
|
||||
+ "\"";
|
||||
else
|
||||
cmd = "codesign -f -s - \"" + args[0] + "\"";
|
||||
|
||||
int result = system(cmd.c_str());
|
||||
std::remove(entitlementsXml.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::unsign(const std::vector<std::string> &args)
|
||||
{
|
||||
if (args.size() < 1) {
|
||||
std::cerr << "Not enough arguments." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!this->fileExists(args[0])) {
|
||||
std::cerr << "No such file or directory." << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto cmd = "codesign --remove-signature \"" + args[0] + "\"";
|
||||
|
||||
return system(cmd.c_str());
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::disableSIP(const std::vector<std::string> &args)
|
||||
{
|
||||
std::cerr << "SIP (System Integrity Protection) can't be disbled from "
|
||||
"inside the system, you must reboot your system and then "
|
||||
"press and hold Command + R keys on boot to enter to the "
|
||||
"recovery mode, then go to Utilities > Terminal and run:"
|
||||
<< std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "csrutil enable --without fs" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "If that does not works, then run:" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "csrutil disable" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack(const std::string &name,
|
||||
const std::string &description,
|
||||
bool isSafe,
|
||||
bool needsRoot,
|
||||
const Hack::HackFunc &func):
|
||||
name(name),
|
||||
description(description),
|
||||
isSafe(isSafe),
|
||||
needsRoot(needsRoot),
|
||||
func(func)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack(const Hack &other):
|
||||
name(other.name),
|
||||
description(other.description),
|
||||
isSafe(other.isSafe),
|
||||
needsRoot(other.needsRoot),
|
||||
func(other.func)
|
||||
{
|
||||
}
|
||||
|
||||
AkVCam::Hack &AkVCam::Hack::operator =(const Hack &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
this->name = other.name;
|
||||
this->description = other.description;
|
||||
this->isSafe = other.isSafe;
|
||||
this->needsRoot = other.needsRoot;
|
||||
this->func = other.func;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ target_include_directories(Assistant
|
|||
PRIVATE ..
|
||||
PRIVATE ../..)
|
||||
target_link_libraries(Assistant
|
||||
PlatformUtils
|
||||
PlatformUtils
|
||||
advapi32
|
||||
gdi32
|
||||
ole32
|
||||
|
|
|
@ -42,7 +42,7 @@ add_library(PlatformUtils STATIC
|
|||
add_dependencies(PlatformUtils VCamUtils)
|
||||
target_compile_definitions(PlatformUtils PRIVATE PLATFORMUTILS_LIBRARY)
|
||||
target_include_directories(PlatformUtils PRIVATE ../..)
|
||||
target_link_libraries(PlatformUtils
|
||||
target_link_libraries(PlatformUtils
|
||||
VCamUtils
|
||||
advapi32
|
||||
kernel32
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "VCamUtils/src/logger.h"
|
||||
|
||||
namespace AkVCam
|
||||
{
|
||||
{
|
||||
struct PipeThread
|
||||
{
|
||||
std::shared_ptr<std::thread> thread;
|
||||
|
@ -400,7 +400,7 @@ void AkVCam::MessageServerPrivate::messagesLoop()
|
|||
for (auto &thread: this->m_clientsThreads)
|
||||
thread->thread->join();
|
||||
|
||||
LocalFree(securityDescriptor);
|
||||
LocalFree(securityDescriptor);
|
||||
AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStopped)
|
||||
AkLogDebug() << "Server stopped." << std::endl;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ target_include_directories(VCamIPC
|
|||
PRIVATE ..
|
||||
PRIVATE ../..)
|
||||
target_link_libraries(VCamIPC
|
||||
PlatformUtils
|
||||
PlatformUtils
|
||||
advapi32
|
||||
kernel32
|
||||
psapi)
|
||||
|
|
|
@ -49,6 +49,27 @@ namespace AkVCam
|
|||
Mutex mutex;
|
||||
};
|
||||
|
||||
class Hack
|
||||
{
|
||||
public:
|
||||
using HackFunc = std::function<int (const std::vector<std::string> &args)>;
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
bool isSafe {false};
|
||||
bool needsRoot {false};
|
||||
HackFunc func;
|
||||
|
||||
Hack();
|
||||
Hack(const std::string &name,
|
||||
const std::string &description,
|
||||
bool isSafe,
|
||||
bool needsRoot,
|
||||
const HackFunc &func);
|
||||
Hack(const Hack &other);
|
||||
Hack &operator =(const Hack &other);
|
||||
};
|
||||
|
||||
class IpcBridgePrivate
|
||||
{
|
||||
public:
|
||||
|
@ -87,9 +108,16 @@ namespace AkVCam
|
|||
int sudo(const std::vector<std::string> ¶meters,
|
||||
const std::string &directory={},
|
||||
bool show=false);
|
||||
std::string assistant() const;
|
||||
std::string manager() const;
|
||||
std::string alternativeManager() const;
|
||||
std::string alternativePlugin() const;
|
||||
std::string service() const;
|
||||
|
||||
// Hacks
|
||||
const std::vector<Hack> &hacks();
|
||||
int setServiceUp(const std::vector<std::string> &args);
|
||||
int setServiceDown(const std::vector<std::string> &args);
|
||||
};
|
||||
|
||||
static const int maxFrameWidth = 1920;
|
||||
|
@ -178,7 +206,7 @@ bool AkVCam::IpcBridge::registerPeer()
|
|||
return false;
|
||||
}
|
||||
|
||||
AkLogDebug() << "Starting message server." << std::endl;
|
||||
AkLogDebug() << "Starting message server." << std::endl;
|
||||
auto pipeName = "\\\\.\\pipe\\" + portName;
|
||||
this->d->m_messageServer.setPipeName(pipeName);
|
||||
this->d->m_messageServer.setHandlers(this->d->m_messageHandlers);
|
||||
|
@ -244,7 +272,7 @@ void AkVCam::IpcBridge::unregisterPeer()
|
|||
(std::min<size_t>)(this->d->m_portName.size(), MAX_STRING));
|
||||
MessageServer::sendMessage("\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME,
|
||||
&message);
|
||||
this->d->m_messageServer.stop();
|
||||
this->d->m_messageServer.stop();
|
||||
this->d->m_sharedMemory.setName({});
|
||||
this->d->m_globalMutex = {};
|
||||
this->d->m_portName.clear();
|
||||
|
@ -465,7 +493,7 @@ std::vector<uint64_t> AkVCam::IpcBridge::clientsPids() const
|
|||
std::vector<std::string> plugins;
|
||||
|
||||
// First check for the existence of the main plugin binary.
|
||||
auto path = pluginPath + "\\" DSHOW_PLUGIN_NAME ".dll";
|
||||
auto path = pluginPath + "\\" DSHOW_PLUGIN_NAME ".dll";
|
||||
AkLogDebug() << "Plugin binary: " << path << std::endl;
|
||||
|
||||
if (fileExists(path))
|
||||
|
@ -826,15 +854,15 @@ bool AkVCam::IpcBridge::needsRoot(const std::string &operation) const
|
|||
{
|
||||
static const std::vector<std::string> operations {
|
||||
"add-device",
|
||||
"add-format",
|
||||
"load",
|
||||
"remove-device",
|
||||
"remove-devices",
|
||||
"set-description",
|
||||
"add-format",
|
||||
"remove-format",
|
||||
"remove-formats",
|
||||
"update",
|
||||
"load",
|
||||
"set-description",
|
||||
"set-loglevel",
|
||||
"update"
|
||||
};
|
||||
|
||||
auto it = std::find(operations.begin(), operations.end(), operation);
|
||||
|
@ -853,6 +881,53 @@ int AkVCam::IpcBridge::sudo(int argc, char **argv) const
|
|||
return this->d->sudo(arguments);
|
||||
}
|
||||
|
||||
std::vector<std::string> AkVCam::IpcBridge::hacks() const
|
||||
{
|
||||
std::vector<std::string> hacks;
|
||||
|
||||
for (auto &hack: this->d->hacks())
|
||||
hacks.push_back(hack.name);
|
||||
|
||||
return hacks;
|
||||
}
|
||||
|
||||
std::string AkVCam::IpcBridge::hackDescription(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.description;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridge::hackIsSafe(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.isSafe;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AkVCam::IpcBridge::hackNeedsRoot(const std::string &hack) const
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.needsRoot && !this->d->isRoot();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridge::execHack(const std::string &hack,
|
||||
const std::vector<std::string> &args)
|
||||
{
|
||||
for (auto &hck: this->d->hacks())
|
||||
if (hck.name == hack)
|
||||
return hck.func(args);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
AkVCam::IpcBridgePrivate::IpcBridgePrivate(IpcBridge *self):
|
||||
self(self)
|
||||
{
|
||||
|
@ -1180,6 +1255,31 @@ int AkVCam::IpcBridgePrivate::sudo(const std::vector<std::string> ¶meters,
|
|||
return int(exitCode);
|
||||
}
|
||||
|
||||
std::string AkVCam::IpcBridgePrivate::assistant() const
|
||||
{
|
||||
AkLogFunction();
|
||||
auto pluginPath = locatePluginPath();
|
||||
auto path = realPath(pluginPath + "\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe");
|
||||
|
||||
if (fileExists(path))
|
||||
return path;
|
||||
|
||||
#ifdef _WIN64
|
||||
path = realPath(pluginPath + "\\..\\x86\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe");
|
||||
#else
|
||||
SYSTEM_INFO info;
|
||||
memset(&info, 0, sizeof(SYSTEM_INFO));
|
||||
GetNativeSystemInfo(&info);
|
||||
|
||||
if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
|
||||
return {};
|
||||
|
||||
path = realPath(pluginPath + "\\..\\x64\\" DSHOW_PLUGIN_ASSISTANT_NAME ".exe");
|
||||
#endif
|
||||
|
||||
return fileExists(path)? path: std::string();
|
||||
}
|
||||
|
||||
std::string AkVCam::IpcBridgePrivate::manager() const
|
||||
{
|
||||
AkLogFunction();
|
||||
|
@ -1230,3 +1330,188 @@ std::string AkVCam::IpcBridgePrivate::alternativePlugin() const
|
|||
|
||||
return fileExists(path)? path: std::string();
|
||||
}
|
||||
|
||||
std::string AkVCam::IpcBridgePrivate::service() const
|
||||
{
|
||||
std::string path;
|
||||
auto manager = OpenSCManager(nullptr, nullptr, GENERIC_READ);
|
||||
|
||||
if (manager) {
|
||||
auto service = OpenServiceA(manager,
|
||||
DSHOW_PLUGIN_ASSISTANT_NAME,
|
||||
SERVICE_QUERY_CONFIG);
|
||||
|
||||
if (service) {
|
||||
DWORD bytesNeeded = 0;
|
||||
QueryServiceConfig(service, nullptr, 0, &bytesNeeded);
|
||||
auto bufSize = bytesNeeded;
|
||||
auto serviceConfig =
|
||||
reinterpret_cast<LPQUERY_SERVICE_CONFIGA>(LocalAlloc(LMEM_FIXED,
|
||||
bufSize));
|
||||
if (serviceConfig) {
|
||||
if (QueryServiceConfigA(service,
|
||||
serviceConfig,
|
||||
bufSize,
|
||||
&bytesNeeded)) {
|
||||
path = std::string(serviceConfig->lpBinaryPathName);
|
||||
}
|
||||
|
||||
LocalFree(serviceConfig);
|
||||
}
|
||||
|
||||
CloseServiceHandle(service);
|
||||
}
|
||||
|
||||
CloseServiceHandle(manager);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
const std::vector<AkVCam::Hack> &AkVCam::IpcBridgePrivate::hacks()
|
||||
{
|
||||
static const std::vector<AkVCam::Hack> hacks {
|
||||
{"set-service-up",
|
||||
"Setup and start virtual camera service if isn't working",
|
||||
true,
|
||||
true,
|
||||
AKVCAM_BIND_FUNC(IpcBridgePrivate::setServiceUp)},
|
||||
{"set-service-down",
|
||||
"Stop and unregister virtual camera service",
|
||||
true,
|
||||
true,
|
||||
AKVCAM_BIND_FUNC(IpcBridgePrivate::setServiceDown)}
|
||||
};
|
||||
|
||||
return hacks;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::setServiceUp(const std::vector<std::string> &args)
|
||||
{
|
||||
UNUSED(args);
|
||||
AkLogFunction();
|
||||
|
||||
// If the service is not installed, install it.
|
||||
auto servicePath = this->service();
|
||||
|
||||
if (servicePath.empty()) {
|
||||
auto assistant = this->assistant();
|
||||
|
||||
if (assistant.empty())
|
||||
return -1;
|
||||
|
||||
auto result = this->sudo({assistant, "--install"});
|
||||
|
||||
if (result < 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Start the service.
|
||||
bool result = false;
|
||||
auto manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||
|
||||
if (manager) {
|
||||
auto service = OpenService(manager,
|
||||
TEXT(DSHOW_PLUGIN_ASSISTANT_NAME),
|
||||
SERVICE_START);
|
||||
|
||||
if (service) {
|
||||
result = StartService(service, 0, nullptr);
|
||||
CloseServiceHandle(service);
|
||||
}
|
||||
|
||||
CloseServiceHandle(manager);
|
||||
}
|
||||
|
||||
return result? 0: -1;
|
||||
}
|
||||
|
||||
int AkVCam::IpcBridgePrivate::setServiceDown(const std::vector<std::string> &args)
|
||||
{
|
||||
UNUSED(args);
|
||||
AkLogFunction();
|
||||
auto servicePath = this->service();
|
||||
|
||||
if (servicePath.empty())
|
||||
return 0;
|
||||
|
||||
// Stop the service.
|
||||
bool stopped = false;
|
||||
auto manager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
|
||||
if (manager) {
|
||||
auto service = OpenService(manager,
|
||||
TEXT(DSHOW_PLUGIN_ASSISTANT_NAME),
|
||||
SERVICE_STOP | SERVICE_QUERY_STATUS);
|
||||
|
||||
if (service) {
|
||||
SERVICE_STATUS status;
|
||||
memset(&status, 0, sizeof(SERVICE_STATUS));
|
||||
|
||||
if (ControlService(service, SERVICE_CONTROL_STOP, &status)) {
|
||||
memset(&status, 0, sizeof(SERVICE_STATUS));
|
||||
|
||||
while(QueryServiceStatus(service, &status)) {
|
||||
if (status.dwCurrentState != SERVICE_STOP_PENDING)
|
||||
break;
|
||||
|
||||
Sleep(1000);
|
||||
}
|
||||
|
||||
stopped = status.dwCurrentState == SERVICE_STOPPED;
|
||||
}
|
||||
|
||||
CloseServiceHandle(service);
|
||||
}
|
||||
|
||||
CloseServiceHandle(manager);
|
||||
}
|
||||
|
||||
if (!stopped)
|
||||
return -1;
|
||||
|
||||
// Unistall the service.
|
||||
|
||||
return this->sudo({servicePath, "--uninstall"});
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack(const std::string &name,
|
||||
const std::string &description,
|
||||
bool isSafe,
|
||||
bool needsRoot,
|
||||
const Hack::HackFunc &func):
|
||||
name(name),
|
||||
description(description),
|
||||
isSafe(isSafe),
|
||||
needsRoot(needsRoot),
|
||||
func(func)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AkVCam::Hack::Hack(const Hack &other):
|
||||
name(other.name),
|
||||
description(other.description),
|
||||
isSafe(other.isSafe),
|
||||
needsRoot(other.needsRoot),
|
||||
func(other.func)
|
||||
{
|
||||
}
|
||||
|
||||
AkVCam::Hack &AkVCam::Hack::operator =(const Hack &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
this->name = other.name;
|
||||
this->description = other.description;
|
||||
this->isSafe = other.isSafe;
|
||||
this->needsRoot = other.needsRoot;
|
||||
this->func = other.func;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -203,7 +203,9 @@ HRESULT AkVCam::Pin::stateChanged(void *userData, FILTER_STATE state)
|
|||
return VFW_E_NOT_COMMITTED;
|
||||
|
||||
self->d->updateTestFrame();
|
||||
self->d->m_mutex.lock();
|
||||
self->d->m_currentFrame = self->d->m_testFrameAdapted;
|
||||
self->d->m_mutex.unlock();
|
||||
self->d->m_pts = -1;
|
||||
self->d->m_ptsDrift = 0;
|
||||
|
||||
|
@ -241,7 +243,9 @@ HRESULT AkVCam::Pin::stateChanged(void *userData, FILTER_STATE state)
|
|||
CloseHandle(self->d->m_sendFrameEvent);
|
||||
self->d->m_sendFrameEvent = nullptr;
|
||||
self->d->m_memAllocator->Decommit();
|
||||
self->d->m_mutex.lock();
|
||||
self->d->m_currentFrame.clear();
|
||||
self->d->m_mutex.unlock();
|
||||
self->d->m_testFrameAdapted.clear();
|
||||
}
|
||||
|
||||
|
|
0
ports/ci/appveyor/build.bat
Executable file → Normal file
0
ports/ci/appveyor/build.bat
Executable file → Normal file
0
ports/ci/appveyor/deploy.bat
Executable file → Normal file
0
ports/ci/appveyor/deploy.bat
Executable file → Normal file
0
ports/ci/appveyor/install_deps.bat
Executable file → Normal file
0
ports/ci/appveyor/install_deps.bat
Executable file → Normal file
0
ports/ci/appveyor/push_artifacts.bat
Executable file → Normal file
0
ports/ci/appveyor/push_artifacts.bat
Executable file → Normal file
0
ports/ci/appveyor/upload.bat
Executable file → Normal file
0
ports/ci/appveyor/upload.bat
Executable file → Normal file
Loading…
Reference in a new issue