mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-09 18:35:32 +00:00
195 lines
5.3 KiB
C++
195 lines
5.3 KiB
C++
// This file implements a service which will run on the system dbus,
|
|
// based on the sd-bus library, see
|
|
// https://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html
|
|
|
|
#include <cstdint>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <list>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <syslog.h>
|
|
#include <systemd/sd-bus.h>
|
|
#include "KeymanSystemService.h"
|
|
#include "KeyboardDevice.h"
|
|
|
|
#define KEYMAN_BUS_NAME "com.keyman.SystemService1"
|
|
#define KEYMAN_INTERFACE_NAME "com.keyman.SystemService1.System"
|
|
#define KEYMAN_OBJECT_PATH "/com/keyman/SystemService1/System"
|
|
|
|
using namespace std;
|
|
|
|
static int32_t
|
|
on_set_caps_lock_indicator(
|
|
sd_bus_message *msg,
|
|
void *user_data,
|
|
sd_bus_error *ret_error
|
|
) {
|
|
int32_t ret;
|
|
uint32_t state;
|
|
|
|
*ret_error = SD_BUS_ERROR_NULL;
|
|
|
|
ret = sd_bus_message_read_basic(msg, 'b', &state);
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to parse parameter: %s", strerror(-ret));
|
|
return ret;
|
|
}
|
|
|
|
KeymanSystemService *service = static_cast<KeymanSystemService *>(user_data);
|
|
service->SetCapsLockIndicatorOnDevices(state);
|
|
return sd_bus_reply_method_return(msg, "");
|
|
}
|
|
|
|
static int32_t
|
|
on_get_caps_lock_indicator(
|
|
sd_bus_message *msg,
|
|
void *user_data,
|
|
sd_bus_error *ret_error
|
|
) {
|
|
uint32_t state;
|
|
|
|
*ret_error = SD_BUS_ERROR_NULL;
|
|
|
|
KeymanSystemService *service = static_cast<KeymanSystemService *>(user_data);
|
|
state = service->GetCapsLockIndicatorOnDevices();
|
|
return sd_bus_reply_method_return(msg, "b", state);
|
|
}
|
|
|
|
static const sd_bus_vtable system_service_vtable[] = {
|
|
SD_BUS_VTABLE_START(0),
|
|
SD_BUS_METHOD("SetCapsLockIndicator", "b", "", on_set_caps_lock_indicator, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_METHOD("GetCapsLockIndicator", "", "b", on_get_caps_lock_indicator, SD_BUS_VTABLE_UNPRIVILEGED),
|
|
SD_BUS_VTABLE_END
|
|
};
|
|
|
|
KeymanSystemService::KeymanSystemService()
|
|
{
|
|
int ret;
|
|
|
|
GetKbdDevices();
|
|
|
|
#ifdef KEYMAN_TESTING
|
|
ret = sd_bus_open_user(&bus);
|
|
#else
|
|
ret = sd_bus_open_system(&bus);
|
|
#endif
|
|
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to connect to system bus: %s", strerror(-ret));
|
|
failed = true;
|
|
return;
|
|
}
|
|
|
|
// Install the object
|
|
ret = sd_bus_add_object_vtable(bus,
|
|
&slot,
|
|
KEYMAN_OBJECT_PATH,
|
|
KEYMAN_INTERFACE_NAME,
|
|
system_service_vtable,
|
|
this);
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to issue method call: %s", strerror(-ret));
|
|
failed = true;
|
|
return;
|
|
}
|
|
|
|
// Take a well-known service name so that clients can find us
|
|
ret = sd_bus_request_name(bus, KEYMAN_BUS_NAME, 0);
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to acquire service name: %s", strerror(-ret));
|
|
failed = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
KeymanSystemService::~KeymanSystemService()
|
|
{
|
|
if (slot) sd_bus_slot_unref(slot);
|
|
if (bus) sd_bus_unref(bus);
|
|
|
|
if (kbd_devices) {
|
|
for (KeyboardDevice *device : *kbd_devices) {
|
|
delete device;
|
|
}
|
|
delete kbd_devices;
|
|
}
|
|
}
|
|
|
|
int KeymanSystemService::Loop()
|
|
{
|
|
for (;;) {
|
|
// Process requests
|
|
int ret = sd_bus_process(bus, NULL);
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to process bus: %s", strerror(-ret));
|
|
failed = true;
|
|
return ret;
|
|
}
|
|
if (ret > 0) {
|
|
// we processed a request, try to process another one, right-away
|
|
continue;
|
|
}
|
|
|
|
// Wait for the next request to process
|
|
ret = sd_bus_wait(bus, (uint64_t)-1);
|
|
if (ret < 0) {
|
|
syslog(LOG_USER | LOG_NOTICE, "Failed to wait on bus: %s", strerror(-ret));
|
|
failed = true;
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void KeymanSystemService::GetKbdDevices() {
|
|
kbd_devices = new std::list<KeyboardDevice *>();
|
|
|
|
DIR *dirstream;
|
|
struct dirent *dir;
|
|
dirstream = opendir("/dev/input");
|
|
if (dirstream) {
|
|
while ((dir = readdir(dirstream)) != NULL) {
|
|
// we're looking for a character device like `event1`
|
|
if (dir->d_type == DT_CHR && strncmp(dir->d_name, "event", 5) == 0) {
|
|
KeyboardDevice *kbdDevice = new KeyboardDevice();
|
|
if (!kbdDevice->Initialize(dir->d_name) || !kbdDevice->HasCapsLockLed()) {
|
|
delete kbdDevice;
|
|
continue;
|
|
}
|
|
kbd_devices->push_back(kbdDevice);
|
|
}
|
|
}
|
|
closedir(dirstream);
|
|
}
|
|
}
|
|
|
|
// Set the CapsLock indicator on all keyboard devices.
|
|
void
|
|
KeymanSystemService::SetCapsLockIndicatorOnDevices(uint32_t state) {
|
|
for (KeyboardDevice *kbdDevice : *kbd_devices) {
|
|
if (!kbdDevice->HasCapsLockLed()) {
|
|
syslog(LOG_USER | LOG_NOTICE, "%s: No kbdDevice or no CapsLockLed", __FUNCTION__);
|
|
continue;
|
|
}
|
|
syslog(LOG_USER | LOG_NOTICE, "%s: Caps Lock indicator: %s", __FUNCTION__, (state != 0 ? "ON" : "OFF"));
|
|
kbdDevice->SetCapsLockLed(state != 0);
|
|
}
|
|
}
|
|
|
|
// Get the CapsLock indicator state from the list of keyboard devices.
|
|
// This will return true if any keyboard has the CapsLock indicator lit.
|
|
uint32_t
|
|
KeymanSystemService::GetCapsLockIndicatorOnDevices() {
|
|
bool state = false;
|
|
for (KeyboardDevice *kbdDevice : *kbd_devices) {
|
|
if (!kbdDevice->HasCapsLockLed()) {
|
|
syslog(LOG_USER | LOG_NOTICE, "%s: No kbdDevice or no CapsLockLed", __FUNCTION__);
|
|
continue;
|
|
}
|
|
state = state || kbdDevice->GetCapsLockLed();
|
|
}
|
|
return state;
|
|
}
|