/*
* Copyright 2019-2023 PixlOne
*
* This program 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.
*
* This program 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 this program. If not, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace logid;
using namespace logid::backend;
DeviceNickname::DeviceNickname(const std::shared_ptr& manager) :
_nickname(manager->newDeviceNickname()), _manager(manager) {
}
DeviceNickname::operator std::string() const {
return std::to_string(_nickname);
}
DeviceNickname::~DeviceNickname() {
if (auto manager = _manager.lock()) {
std::lock_guard lock(manager->_nick_lock);
manager->_device_nicknames.erase(_nickname);
}
}
namespace logid {
class DeviceWrapper : public Device {
public:
template
explicit DeviceWrapper(Args... args) : Device(std::forward(args)...) {}
};
}
std::shared_ptr Device::make(
std::string path, backend::hidpp::DeviceIndex index,
std::shared_ptr manager) {
auto ret = std::make_shared(std::move(path),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get());
return ret;
}
std::shared_ptr Device::make(
std::shared_ptr raw_device,
backend::hidpp::DeviceIndex index,
std::shared_ptr manager) {
auto ret = std::make_shared(std::move(raw_device),
index,
std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get());
return ret;
}
std::shared_ptr Device::make(
Receiver* receiver, backend::hidpp::DeviceIndex index,
std::shared_ptr manager) {
auto ret = std::make_shared(receiver, index, std::move(manager));
ret->_self = ret;
ret->_ipc_node->manage(ret);
ret->_ipc_interface = ret->_ipc_node->make_interface(ret.get());
return ret;
}
Device::Device(std::string path, backend::hidpp::DeviceIndex index,
const std::shared_ptr& manager) :
_hidpp20(hidpp20::Device::make(path, index, manager,
manager->config()->io_timeout.value_or(
defaults::io_timeout))),
_path(std::move(path)), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(std::shared_ptr raw_device,
hidpp::DeviceIndex index, const std::shared_ptr& manager) :
_hidpp20(hidpp20::Device::make(
std::move(raw_device), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(raw_device->rawPath()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(nullptr),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
Device::Device(Receiver* receiver, hidpp::DeviceIndex index,
const std::shared_ptr& manager) :
_hidpp20(hidpp20::Device::make(
receiver->rawReceiver(), index,
manager->config()->io_timeout.value_or(defaults::io_timeout))),
_path(receiver->path()), _index(index),
_config(_getConfig(manager, _hidpp20->name())),
_profile_name(ipcgull::property_readable, ""),
_receiver(receiver),
_manager(manager),
_nickname(manager),
_ipc_node(manager->devicesNode()->make_child(_nickname)),
_awake(ipcgull::property_readable, true) {
_init();
}
void Device::_init() {
logPrintf(INFO, "Device found: %s on %s:%d", name().c_str(),
hidpp20().devicePath().c_str(), _index);
{
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(_config.default_profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({_config.default_profile, {}}).first;
_profile_name = _config.default_profile;
}
_addFeature("dpi");
_addFeature("smartshift");
_addFeature("hiresscroll");
_addFeature("remapbutton");
_addFeature("devicestatus");
_addFeature("thumbwheel");
_makeResetMechanism();
reset();
for (auto& feature: _features) {
feature.second->configure();
feature.second->listen();
}
}
std::string Device::name() {
return _hidpp20->name();
}
uint16_t Device::pid() {
return _hidpp20->pid();
}
void Device::sleep() {
std::lock_guard lock(_state_lock);
if (_awake) {
logPrintf(INFO, "%s:%d fell asleep.", _path.c_str(), _index);
_awake = false;
_ipc_interface->notifyStatus();
}
}
void Device::wakeup() {
std::lock_guard lock(_state_lock);
logPrintf(INFO, "%s:%d woke up.", _path.c_str(), _index);
reconfigure();
if (!_awake) {
_awake = true;
_ipc_interface->notifyStatus();
}
}
void Device::reconfigure() {
reset();
for (auto& feature: _features)
feature.second->configure();
}
void Device::reset() {
if (_reset_mechanism)
(*_reset_mechanism)();
else
logPrintf(DEBUG, "%s:%d tried to reset, but no reset mechanism was "
"available.", _path.c_str(), _index);
}
std::shared_ptr Device::virtualInput() const {
if (auto manager = _manager.lock()) {
return manager->virtualInput();
} else {
logPrintf(ERROR, "Device manager lost");
logPrintf(ERROR,
"Fatal error occurred, file a bug report,"
" the program will now exit.");
std::terminate();
}
}
std::shared_ptr Device::ipcNode() const {
return _ipc_node;
}
std::vector Device::getProfiles() const {
std::shared_lock lock(_profile_mutex);
std::vector ret;
for (auto& profile : _config.profiles) {
ret.push_back(profile.first);
}
return ret;
}
void Device::setProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
_profile = _config.profiles.find(profile);
if (_profile == _config.profiles.end())
_profile = _config.profiles.insert({profile, {}}).first;
_profile_name = profile;
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
}
void Device::setProfileDelayed(const std::string& profile) {
run_task([self_weak = _self, profile](){
if (auto self = self_weak.lock())
self->setProfile(profile);
});
}
void Device::removeProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name)
throw std::invalid_argument("cannot remove active profile");
else if (profile == (std::string)_config.default_profile)
throw std::invalid_argument("cannot remove default profile");
_config.profiles.erase(profile);
}
void Device::clearProfile(const std::string& profile) {
std::unique_lock lock(_profile_mutex);
if (profile == (std::string)_profile_name) {
_profile->second = config::Profile();
for (auto& feature : _features)
feature.second->setProfile(_profile->second);
reconfigure();
} else {
auto it = _config.profiles.find(profile);
if (it != _config.profiles.end()) {
it->second = config::Profile();
} else {
throw std::invalid_argument("unknown profile");
}
}
}
config::Profile& Device::activeProfile() {
std::shared_lock lock(_profile_mutex);
return _profile->second;
}
hidpp20::Device& Device::hidpp20() {
return *_hidpp20;
}
void Device::_makeResetMechanism() {
try {
hidpp20::Reset reset(_hidpp20.get());
_reset_mechanism = std::make_unique>(
[dev = _hidpp20] {
hidpp20::Reset reset(dev.get());
reset.reset(reset.getProfile());
});
} catch (hidpp20::UnsupportedFeature& e) {
// Reset unsupported, ignore.
}
}
Device::IPC::IPC(Device* device) :
ipcgull::interface(
SERVICE_ROOT_NAME ".Device",
{
{"GetProfiles", {device, &Device::getProfiles, {"profiles"}}},
{"SetProfile", {device, &Device::setProfile, {"profile"}}},
{"RemoveProfile", {device, &Device::removeProfile, {"profile"}}},
{"ClearProfile", {device, &Device::clearProfile, {"profile"}}}
},
{
{"Name", ipcgull::property(
ipcgull::property_readable, device->name())},
{"ProductID", ipcgull::property(
ipcgull::property_readable, device->pid())},
{"Active", device->_awake},
{"DefaultProfile", device->_config.default_profile},
{"ActiveProfile", device->_profile_name}
}, {
{"StatusChanged", ipcgull::signal::make_signal({"active"})}
}), _device(*device) {
}
void Device::IPC::notifyStatus() const {
emit_signal("StatusChanged", (bool) (_device._awake));
}
config::Device& Device::_getConfig(
const std::shared_ptr& manager,
const std::string& name) {
static std::mutex config_mutex;
std::lock_guard lock(config_mutex);
auto& devices = manager->config()->devices.value();
if (!devices.count(name)) {
devices.emplace(name, config::Device());
}
auto& device = devices.at(name);
if (std::holds_alternative(device)) {
config::Device d;
d.profiles["default"] = std::get(device);
d.default_profile = "default";
device = std::move(d);
}
auto& conf = std::get(device);
if (conf.profiles.empty()) {
conf.profiles["default"] = {};
conf.default_profile = "default";
}
return conf;
}