/* akvirtualcamera, virtual camera for Mac and Windows.
* Copyright (C) 2020 Gonzalo Exequiel Pedone
*
* akvirtualcamera is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* akvirtualcamera is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with akvirtualcamera. If not, see .
*
* Web-Site: http://webcamoid.github.io/
*/
#include
#include "preferences.h"
#include "utils.h"
#include "VCamUtils/src/image/videoformat.h"
#include "VCamUtils/src/logger/logger.h"
#define PREFERENCES_ID CFSTR(CMIO_ASSISTANT_NAME)
std::vector AkVCam::Preferences::keys()
{
AkLogFunction();
std::vector keys;
auto cfKeys = CFPreferencesCopyKeyList(PREFERENCES_ID,
kCFPreferencesCurrentUser,
kCFPreferencesAnyHost);
if (cfKeys) {
auto size = CFArrayGetCount(cfKeys);
for (CFIndex i = 0; i < size; i++) {
auto key = CFStringRef(CFArrayGetValueAtIndex(cfKeys, i));
keys.push_back(stringFromCFType(key));
}
CFRelease(cfKeys);
}
AkLogInfo() << "Keys: " << keys.size() << std::endl;
std::sort(keys.begin(), keys.end());
for (auto &key: keys)
AkLogInfo() << " " << key << std::endl;
return keys;
}
void AkVCam::Preferences::write(const std::string &key,
const std::shared_ptr &value)
{
AkLogFunction();
AkLogInfo() << "Writing: " << key << " = " << *value << std::endl;
auto cfKey = cfTypeFromStd(key);
CFPreferencesSetAppValue(CFStringRef(*cfKey), *value, PREFERENCES_ID);
}
void AkVCam::Preferences::write(const std::string &key,
const std::string &value)
{
AkLogFunction();
AkLogInfo() << "Writing: " << key << " = " << value << std::endl;
auto cfKey = cfTypeFromStd(key);
auto cfValue = cfTypeFromStd(value);
CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID);
}
void AkVCam::Preferences::write(const std::string &key,
const std::wstring &value)
{
AkLogFunction();
AkLogInfo() << "Writing: "
<< key
<< " = "
<< std::string(value.begin(), value.end()) << std::endl;
auto cfKey = cfTypeFromStd(key);
auto cfValue = cfTypeFromStd(value);
CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID);
}
void AkVCam::Preferences::write(const std::string &key, int value)
{
AkLogFunction();
AkLogInfo() << "Writing: " << key << " = " << value << std::endl;
auto cfKey = cfTypeFromStd(key);
auto cfValue = cfTypeFromStd(value);
CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID);
}
void AkVCam::Preferences::write(const std::string &key, double value)
{
AkLogFunction();
AkLogInfo() << "Writing: " << key << " = " << value << std::endl;
auto cfKey = cfTypeFromStd(key);
auto cfValue = cfTypeFromStd(value);
CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID);
}
void AkVCam::Preferences::write(const std::string &key,
std::vector &value)
{
AkLogFunction();
write(key, join(value, ","));
}
std::shared_ptr AkVCam::Preferences::read(const std::string &key)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue = CFTypeRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
if (!cfValue)
return {};
return std::shared_ptr(new CFTypeRef(cfValue),
[] (CFTypeRef *ptr) {
CFRelease(*ptr);
delete ptr;
});
}
std::string AkVCam::Preferences::readString(const std::string &key,
const std::string &defaultValue)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue =
CFStringRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
auto value = defaultValue;
if (cfValue) {
value = stringFromCFType(cfValue);
CFRelease(cfValue);
}
return value;
}
std::wstring AkVCam::Preferences::readWString(const std::string &key,
const std::wstring &defaultValue)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue =
CFStringRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
auto value = defaultValue;
if (cfValue) {
value = wstringFromCFType(cfValue);
CFRelease(cfValue);
}
return value;
}
int AkVCam::Preferences::readInt(const std::string &key, int defaultValue)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue =
CFNumberRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
auto value = defaultValue;
if (cfValue) {
CFNumberGetValue(cfValue, kCFNumberIntType, &value);
CFRelease(cfValue);
}
return value;
}
double AkVCam::Preferences::readDouble(const std::string &key,
double defaultValue)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue =
CFNumberRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
auto value = defaultValue;
if (cfValue) {
CFNumberGetValue(cfValue, kCFNumberDoubleType, &value);
CFRelease(cfValue);
}
return value;
}
bool AkVCam::Preferences::readBool(const std::string &key, bool defaultValue)
{
AkLogFunction();
auto cfKey = cfTypeFromStd(key);
auto cfValue =
CFBooleanRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey),
PREFERENCES_ID));
auto value = defaultValue;
if (cfValue) {
value = CFBooleanGetValue(cfValue);
CFRelease(cfValue);
}
return value;
}
std::vector AkVCam::Preferences::readStringList(const std::string &key,
const std::vector &defaultValue)
{
auto value = defaultValue;
for (auto &str: split(readString(key), ','))
value.push_back(trimmed(str));
return value;
}
void AkVCam::Preferences::deleteKey(const std::string &key)
{
AkLogFunction();
AkLogInfo() << "Deleting " << key << std::endl;
auto cfKey = cfTypeFromStd(key);
CFPreferencesSetAppValue(CFStringRef(*cfKey), nullptr, PREFERENCES_ID);
}
void AkVCam::Preferences::deleteAllKeys(const std::string &key)
{
AkLogFunction();
AkLogInfo() << "Key: " << key << std::endl;
for (auto &key_: keys())
if (key_.size() >= key.size() && key_.substr(0, key.size()) == key)
deleteKey(key_);
}
void AkVCam::Preferences::move(const std::string &keyFrom,
const std::string &keyTo)
{
AkLogFunction();
AkLogInfo() << "From: " << keyFrom << std::endl;
AkLogInfo() << "To: " << keyTo << std::endl;
auto value = read(keyFrom);
if (!value)
return;
write(keyTo, value);
deleteKey(keyFrom);
}
void AkVCam::Preferences::moveAll(const std::string &keyFrom,
const std::string &keyTo)
{
AkLogFunction();
AkLogInfo() << "From: " << keyFrom << std::endl;
AkLogInfo() << "To: " << keyTo << std::endl;
for (auto &key: keys())
if (key.size() >= keyFrom.size()
&& key.substr(0, keyFrom.size()) == keyFrom) {
if (key.size() == keyFrom.size())
move(key, keyTo);
else
move(key, keyTo + key.substr(keyFrom.size()));
}
}
void AkVCam::Preferences::sync()
{
AkLogFunction();
CFPreferencesAppSynchronize(PREFERENCES_ID);
}
std::string AkVCam::Preferences::addDevice(const std::wstring &description)
{
AkLogFunction();
auto path = createDevicePath();
int cameraIndex = readInt("cameras");
write("cameras", cameraIndex + 1);
write("cameras."
+ std::to_string(cameraIndex)
+ ".description",
description);
write("cameras."
+ std::to_string(cameraIndex)
+ ".path",
path);
sync();
return path;
}
std::string AkVCam::Preferences::addCamera(const std::wstring &description,
const std::vector &formats)
{
return addCamera("", description, formats);
}
std::string AkVCam::Preferences::addCamera(const std::string &path,
const std::wstring &description,
const std::vector &formats)
{
AkLogFunction();
if (!path.empty() && cameraExists(path))
return {};
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)
+ ".path",
path_);
write("cameras."
+ std::to_string(cameraIndex)
+ ".formats",
int(formats.size()));
for (size_t i = 0; i < formats.size(); i++) {
auto &format = formats[i];
auto prefix = "cameras."
+ std::to_string(cameraIndex)
+ ".formats."
+ std::to_string(i);
auto formatStr = VideoFormat::stringFromFourcc(format.fourcc());
write(prefix + ".format", formatStr);
write(prefix + ".width", format.width());
write(prefix + ".height", format.height());
write(prefix + ".fps", format.minimumFrameRate().toString());
}
sync();
return path_;
}
void AkVCam::Preferences::removeCamera(const std::string &path)
{
AkLogFunction();
AkLogInfo() << "Device: " << path << std::endl;
int cameraIndex = cameraFromPath(path);
if (cameraIndex < 0)
return;
cameraSetFormats(cameraIndex, {});
auto nCameras = camerasCount();
deleteAllKeys("cameras." + std::to_string(cameraIndex));
for (auto i = size_t(cameraIndex + 1); i < nCameras; i++)
moveAll("cameras." + std::to_string(i),
"cameras." + std::to_string(i - 1));
if (nCameras > 1)
write("cameras", int(nCameras - 1));
else
deleteKey("cameras");
sync();
}
size_t AkVCam::Preferences::camerasCount()
{
AkLogFunction();
int nCameras = readInt("cameras");
AkLogInfo() << "Cameras: " << nCameras << std::endl;
return size_t(nCameras);
}
std::string AkVCam::Preferences::createDevicePath()
{
AkLogFunction();
// List device paths in use.
std::vector cameraPaths;
for (size_t i = 0; i < camerasCount(); i++)
cameraPaths.push_back(cameraPath(i));
const int maxId = 64;
for (int i = 0; i < maxId; i++) {
/* There are no rules for device paths in Windows. Just append an
* incremental index to a common prefix.
*/
auto path = CMIO_PLUGIN_DEVICE_PREFIX + std::to_string(i);
// Check if the path is being used, if not return it.
if (std::find(cameraPaths.begin(),
cameraPaths.end(),
path) == cameraPaths.end())
return path;
}
return {};
}
int AkVCam::Preferences::cameraFromPath(const std::string &path)
{
for (size_t i = 0; i < camerasCount(); i++)
if (cameraPath(i) == path)
return int(i);
return -1;
}
bool AkVCam::Preferences::cameraExists(const std::string &path)
{
for (size_t i = 0; i < camerasCount(); i++)
if (cameraPath(i) == path)
return true;
return false;
}
std::wstring AkVCam::Preferences::cameraDescription(size_t cameraIndex)
{
if (cameraIndex >= camerasCount())
return {};
return readWString("cameras."
+ std::to_string(cameraIndex)
+ ".description");
}
void AkVCam::Preferences::cameraSetDescription(size_t cameraIndex,
const std::wstring &description)
{
if (cameraIndex >= camerasCount())
return;
write("cameras." + std::to_string(cameraIndex) + ".description",
description);
}
std::string AkVCam::Preferences::cameraPath(size_t cameraIndex)
{
return readString("cameras."
+ std::to_string(cameraIndex)
+ ".path");
}
size_t AkVCam::Preferences::formatsCount(size_t cameraIndex)
{
return size_t(readInt("cameras."
+ std::to_string(cameraIndex)
+ ".formats"));
}
AkVCam::VideoFormat AkVCam::Preferences::cameraFormat(size_t cameraIndex,
size_t formatIndex)
{
AkLogFunction();
auto prefix = "cameras."
+ std::to_string(cameraIndex)
+ ".formats."
+ std::to_string(formatIndex);
auto format = readString(prefix + ".format");
auto fourcc = VideoFormat::fourccFromString(format);
int width = readInt(prefix + ".width");
int height = readInt(prefix + ".height");
auto fps = Fraction(readString(prefix + ".fps"));
return VideoFormat(fourcc, width, height, {fps});
}
std::vector AkVCam::Preferences::cameraFormats(size_t cameraIndex)
{
AkLogFunction();
std::vector formats;
for (size_t i = 0; i < formatsCount(cameraIndex); i++) {
auto videoFormat = cameraFormat(cameraIndex, i);
if (videoFormat)
formats.push_back(videoFormat);
}
return formats;
}
void AkVCam::Preferences::cameraSetFormats(size_t cameraIndex,
const std::vector &formats)
{
AkLogFunction();
if (cameraIndex >= camerasCount())
return;
write("cameras."
+ std::to_string(cameraIndex)
+ ".formats",
int(formats.size()));
for (size_t i = 0; i < formats.size(); i++) {
auto &format = formats[i];
auto prefix = "cameras."
+ std::to_string(cameraIndex)
+ ".formats."
+ std::to_string(i);
auto formatStr = VideoFormat::stringFromFourcc(format.fourcc());
write(prefix + ".format", formatStr);
write(prefix + ".width", format.width());
write(prefix + ".height", format.height());
write(prefix + ".fps", format.minimumFrameRate().toString());
}
sync();
}
void AkVCam::Preferences::cameraAddFormat(size_t cameraIndex,
const AkVCam::VideoFormat &format,
int index)
{
AkLogFunction();
auto formats = cameraFormats(cameraIndex);
if (index < 0 || index > int(formats.size()))
index = formats.size();
formats.insert(formats.begin() + index, format);
write("cameras."
+ std::to_string(cameraIndex)
+ ".formats",
int(formats.size()));
for (size_t i = 0; i < formats.size(); i++) {
auto &format = formats[i];
auto prefix = "cameras."
+ std::to_string(cameraIndex)
+ ".formats."
+ std::to_string(i);
auto formatStr = VideoFormat::stringFromFourcc(format.fourcc());
write(prefix + ".format", formatStr);
write(prefix + ".width", format.width());
write(prefix + ".height", format.height());
write(prefix + ".fps", format.minimumFrameRate().toString());
}
sync();
}
void AkVCam::Preferences::cameraRemoveFormat(size_t cameraIndex, int index)
{
AkLogFunction();
auto formats = cameraFormats(cameraIndex);
if (index < 0 || index >= int(formats.size()))
return;
formats.erase(formats.begin() + index);
write("cameras."
+ std::to_string(cameraIndex)
+ ".formats",
int(formats.size()));
for (size_t i = 0; i < formats.size(); i++) {
auto &format = formats[i];
auto prefix = "cameras."
+ std::to_string(cameraIndex)
+ ".formats."
+ std::to_string(i);
auto formatStr = VideoFormat::stringFromFourcc(format.fourcc());
write(prefix + ".format", formatStr);
write(prefix + ".width", format.width());
write(prefix + ".height", format.height());
write(prefix + ".fps", format.minimumFrameRate().toString());
}
sync();
}
std::wstring AkVCam::Preferences::picture()
{
return readWString("picture");
}
void AkVCam::Preferences::setPicture(const std::wstring &picture)
{
write("picture", picture);
}
int AkVCam::Preferences::logLevel()
{
return readInt("loglevel", AKVCAM_LOGLEVEL_DEFAULT);
}
void AkVCam::Preferences::setLogLevel(int logLevel)
{
write("loglevel", logLevel);
}
int AkVCam::Preferences::cameraControlValue(size_t cameraIndex,
const std::string &key)
{
return readInt("cameras." + std::to_string(cameraIndex) + ".controls." + key);
}
void AkVCam::Preferences::cameraSetControlValue(size_t cameraIndex,
const std::string &key,
int value)
{
write("cameras." + std::to_string(cameraIndex) + ".controls." + key, value);
}