diff --git a/data/90-feedbackd.rules b/data/90-feedbackd.rules index 5cece64..1e5b4ff 100644 --- a/data/90-feedbackd.rules +++ b/data/90-feedbackd.rules @@ -25,4 +25,10 @@ SUBSYSTEM=="leds", DEVPATH=="*/*:indicator", ENV{FEEDBACKD_TYPE}="led", RUN{buil SUBSYSTEM=="leds", DEVPATH=="*/*:flash", ENV{FEEDBACKD_TYPE}="led", RUN{builtin}+="kmod load ledtrig-pattern", RUN+="/usr/libexec/fbd-ledctrl -p %S%p -t pattern -G feedbackd" SUBSYSTEM=="leds", DEVPATH=="*/*:torch", ENV{FEEDBACKD_TYPE}="led", RUN{builtin}+="kmod load ledtrig-pattern", RUN+="/usr/libexec/fbd-ledctrl -p %S%p -t pattern -G feedbackd" +# +# Alert sliders +# +# OnePlus 6,6T (DT bits not yet upstream) +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT}=="1", ENV{ID_PATH}=="platform-alert-slider", TAG+="uaccess", ENV{FEEDBACKD_TYPE}="alert-slider" + LABEL="feedbackd_end" diff --git a/data/fbd-alert-slider.service.in b/data/fbd-alert-slider.service.in new file mode 100644 index 0000000..75bbf23 --- /dev/null +++ b/data/fbd-alert-slider.service.in @@ -0,0 +1,11 @@ +[Unit] +Description=Feedbackd Alert Slider Handler +PartOf=graphical-session.target + +[Service] +ExecStart=@libexecdir@/fbd-alert-slider +Restart=on-abnormal +RestartSec=10s + +[Install] +WantedBy=graphical-session.target diff --git a/data/meson.build b/data/meson.build index f9b78e6..7ff25cc 100644 --- a/data/meson.build +++ b/data/meson.build @@ -72,4 +72,24 @@ if get_option('udev') install_data('90-feedbackd.rules', install_dir: udevdir) endif +systemd_user_unit_dir = get_option('systemd_user_unit_dir') +systemd_dep = dependency('systemd', required: false) +if systemd_user_unit_dir == '' + if systemd_dep.found() + systemd_user_unit_dir = systemd_dep.get_variable('systemd_user_unit_dir') + else + systemd_user_unit_dir = prefix / libdir / 'systemd' / 'user' + endif +endif + +# Alert slider Systemd user service +alert_slider_unit = 'fbd-alert-slider.service' +configure_file( + input : alert_slider_unit + '.in', + output : alert_slider_unit, + configuration : service_data, + install : true, + install_dir : systemd_user_unit_dir, +) + endif # daemon diff --git a/data/org.sigxcpu.feedbackd.gschema.xml b/data/org.sigxcpu.feedbackd.gschema.xml index c70da2a..064e680 100644 --- a/data/org.sigxcpu.feedbackd.gschema.xml +++ b/data/org.sigxcpu.feedbackd.gschema.xml @@ -33,7 +33,8 @@ - [] + ['org.sigxcpu.FbdAlertSlider', + 'org.sigxcpu.FbdAlertSlider'] List of apps that can override the feedback level Apps listed here can override the configured feedack level by diff --git a/debian/feedbackd.install b/debian/feedbackd.install index fcfbd17..b963012 100644 --- a/debian/feedbackd.install +++ b/debian/feedbackd.install @@ -1,4 +1,5 @@ usr/bin/ +usr/lib/systemd/user usr/lib/udev/ usr/libexec/ usr/share/dbus-1/ diff --git a/meson.build b/meson.build index 1515920..6e8f301 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ if get_option('daemon') gsound = dependency('gsound') gudev = dependency('gudev-1.0', version: '>=232') json_glib = dependency('json-glib-1.0') + systemd_dep = dependency('systemd', required: false) endif prefix = get_option('prefix') diff --git a/meson_options.txt b/meson_options.txt index 33a5071..9db4de7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,3 +16,6 @@ option('man', option('tests', type: 'boolean', value: true, description: 'Whether to compile unit tests') +option('systemd_user_unit_dir', + type: 'string', value: '', + description: 'Directory for systemd user units') diff --git a/src/fbd-alert-slider.c b/src/fbd-alert-slider.c new file mode 100644 index 0000000..a789d34 --- /dev/null +++ b/src/fbd-alert-slider.c @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2024 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0+ + * + * Heavily based on a shell script posted to the phosh matrix channel + * by isyourbrain foss + * + * Author: Guido Günther + */ + +#define G_LOG_DOMAIN "fbd-alert-slider" + +#define LIBFEEDBACK_USE_UNSTABLE_API +#include "libfeedback.h" + +#include "fbd.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x) - 1) / BITS_PER_LONG) + 1) +#define OFF(x) ((x) % BITS_PER_LONG) +#define BIT(x) (1UL << OFF (x)) +#define LONG(x) ((x) / BITS_PER_LONG) +#define test_bit(bit, array) ((array[LONG (bit)] >> OFF (bit)) & 1) + +typedef enum { + FBD_ALERT_SLIDER_STATE_SILENT = 0, + FBD_ALERT_SLIDER_STATE_QUIET = 1, + FBD_ALERT_SLIDER_STATE_FULL = 2, +} FbdAlertSliderState; + +GSettings *settings = NULL; + +static void +set_level (FbdAlertSliderState state, gboolean silent) +{ + g_autoptr (LfbEvent) event = NULL; + const char *profile; + + switch (state) { + case FBD_ALERT_SLIDER_STATE_SILENT: + profile = "silent"; + break; + case FBD_ALERT_SLIDER_STATE_QUIET: + profile = "quiet"; + break; + case FBD_ALERT_SLIDER_STATE_FULL: + profile = "full"; + break; + default: + g_return_if_reached (); + } + + g_debug ("Setting profile to %s", profile); + lfb_set_feedback_profile (profile); + + if (!silent) { + event = lfb_event_new ("button-pressed"); + lfb_event_set_important (event, TRUE); + lfb_event_set_feedback_profile (event, "quiet"); + lfb_event_trigger_feedback (event, NULL); + } +} + + +static int +get_initial_value (int fd, int axis) +{ + int abs[6] = {0}; + + ioctl (fd, EVIOCGABS (axis), abs); + return abs[0]; +} + + +static int +probe (FbdAlertSliderState *initial) +{ + GList *l; + g_autolist (GUdevClient) devices = NULL; + g_autoptr (GUdevClient) client = NULL; + unsigned int code; + int version, fd, value = 0; + char name[256] = "Unknown"; + unsigned long bit[EV_MAX][NBITS (KEY_MAX)]; + const char *device_file = NULL; + const char * const subsystems[] = { "input", NULL }; + + client = g_udev_client_new (subsystems); + devices = g_udev_client_query_by_subsystem (client, "input"); + + for (l = devices; l != NULL; l = l->next) { + GUdevDevice *dev = l->data; + + if (!g_strcmp0 (g_udev_device_get_property (dev, FEEDBACKD_UDEV_ATTR), "alert-slider")) { + device_file = g_udev_device_get_device_file (dev); + g_debug ("Found alert slider device: %s", device_file); + break; + } + } + + if (!device_file) { + g_debug ("No matching device found"); + return -ENOENT; + } + + if ((fd = open (device_file, O_RDONLY)) < 0) { + g_warning ("Failed to open alert slider device: %s", strerror (errno)); + return -1; + } + + if (ioctl (fd, EVIOCGVERSION, &version)) { + g_warning ("Can't get version %s", strerror (errno)); + return -1; + } + g_debug ("Input driver version: %d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff); + + ioctl (fd, EVIOCGNAME (sizeof(name)), name); + g_debug ("Input device name: '%s'", name); + if (g_strcmp0 (name, "Alert slider")) { + g_critical ("Not an alert slider but '%s'", name); + return -1; + } + + memset (bit, 0, sizeof(bit)); + ioctl (fd, EVIOCGBIT (0, EV_MAX), bit[0]); + + if (test_bit (EV_ABS, bit[0])) { + ioctl (fd, EVIOCGBIT (EV_ABS, KEY_MAX), bit[EV_ABS]); + for (code = 0; code < KEY_MAX; code++) { + if (test_bit (code, bit[EV_ABS])) { + value = get_initial_value (fd, code); + break; + } + } + } else { + g_critical ("Device doesn't support EV_ABS"); + return -1; + } + + if (fd >= 0) { + g_debug ("Initial value: %d", value); + *initial = value; + } + return fd; +} + + +static gboolean +on_input_ready (GIOChannel *source, GIOCondition cond, gpointer unused) +{ + GIOError status; + struct input_event ev[64]; + gsize rd; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + status = g_io_channel_read (source, (char *)ev, sizeof(ev), &rd /*, &err */); + G_GNUC_END_IGNORE_DEPRECATIONS + if (status != G_IO_ERROR_NONE ) { + g_warning ("Failed to read event: %d", status); + return TRUE; + } + + if (rd < sizeof(struct input_event)) { + g_warning ("Expected %d bytes, got %zd", (int) sizeof(struct input_event), rd); + return TRUE; + } + + for (int i = 0; i < rd / sizeof(struct input_event); i++) { + unsigned int type, code; + + type = ev[i].type; + code = ev[i].code; + + if (type == EV_SYN) + continue; + + if (type != EV_ABS || code != 34) { + g_warning ("Unexpected event %d / %d", type, code); + continue; + } + + g_debug ("Event: time %ld.%06ld, value: %d", + ev[i].input_event_sec, + ev[i].input_event_usec, + ev[i].value); + set_level (ev[i].value, FALSE); + } + + return TRUE; +} + + +int +main (int argc, char *argv[]) +{ + g_autoptr (GError) err = NULL; + g_autoptr (GOptionContext) opt_context = NULL; + g_autoptr (GIOChannel) input = NULL; + g_autoptr (GMainLoop) loop = NULL; + FbdAlertSliderState state = 0; + int fd; + const GOptionEntry options [] = { + { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } + }; + + opt_context = g_option_context_new ("- helper to prepare sysfs for feedbackd"); + g_option_context_add_main_entries (opt_context, options, NULL); + if (!g_option_context_parse (opt_context, &argc, &argv, &err)) { + g_print ("%s", err->message); + return 1; + } + + fd = probe (&state); + if (fd < 0) { + if (fd == -ENOENT) + return EXIT_SUCCESS; + else + return EXIT_FAILURE; + } + + if (!lfb_init ("org.sigxcpu.FbdAlertSlider", &err)) { + g_print ("Failed to init libfeedback: %s\n", err->message); + return 1; + } + + set_level (state, TRUE); + + input = g_io_channel_unix_new (fd); + g_io_channel_set_encoding (input, NULL, NULL); + g_io_add_watch (input, G_IO_IN | G_IO_ERR | G_IO_HUP, on_input_ready, NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_clear_object (&settings); + + return 0; +} diff --git a/src/meson.build b/src/meson.build index 3b3f945..1efea59 100644 --- a/src/meson.build +++ b/src/meson.build @@ -87,6 +87,15 @@ executable( install_dir: libexecdir, ) +executable( + 'fbd-alert-slider', + sources : ['fbd-alert-slider.c'], + include_directories : fbd_inc, + dependencies : [glib, gio, gio_unix, gudev, libfeedback_dep], + install : true, + install_dir: libexecdir, +) + executable( 'fbd-theme-validate', sources : ['fbd-theme-validate.c'],