diff options
| author | TheKit <thekit@disroot.org> | 2026-07-28 13:15:00 +0300 |
|---|---|---|
| committer | Mike Gabriel <mike.gabriel@das-netzwerkteam.de> | 2026-10-12 22:19:59 +0200 |
| commit | 7b3161b8ac511cf0dcc18aded00171f8cc07ae9a (patch) | |
| tree | a725573453e0f4bc85916d73688150844a60e3b2 /src/flashlight-binder.c | |
| parent | ce800c1e2012bf0d6cced36ec6ea8e1b9e9ddf38 (diff) | |
| download | ayatana-indicator-power-7b3161b8ac511cf0dcc18aded00171f8cc07ae9a.tar.gz ayatana-indicator-power-7b3161b8ac511cf0dcc18aded00171f8cc07ae9a.tar.bz2 ayatana-indicator-power-7b3161b8ac511cf0dcc18aded00171f8cc07ae9a.zip | |
flashlight: add a camera HAL backend
Some flash drivers expose a sysfs node that only toggles the LED and leaves
the duty and the flash safety timeout unset, so the LED goes out by itself.
Drive the torch through the AIDL camera provider instead, which programs all
three.
Select it with "FlashlightBackend: camera-hal" in the device yaml. Needs
libgbinder, so it is behind the ENABLE_LIBGBINDER build option.
Diffstat (limited to 'src/flashlight-binder.c')
| -rw-r--r-- | src/flashlight-binder.c | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/flashlight-binder.c b/src/flashlight-binder.c new file mode 100644 index 0000000..fd99a4f --- /dev/null +++ b/src/flashlight-binder.c @@ -0,0 +1,247 @@ +/* + * Copyright 2026 The UBports project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>. + */ + +/* + * Torch backend that goes through the AIDL camera provider HAL, for devices + * whose sysfs node cannot drive the LED on its own. setTorchMode() is + * specified to work with no camera session open. Some flash drivers expose a + * sysfs node that only toggles the LED and leaves the duty and the flash + * safety timeout unprogrammed, so the LED lights and then goes out again by + * itself; the HAL sets all three and the LED stays on. + * + * Selected with "FlashlightBackend: camera-hal" in the deviceinfo yaml. + */ + +#include "flashlight.h" + +#include <gbinder.h> +#include <glib.h> + +#define TORCH_BINDER_DEV "/dev/binder" +#define TORCH_PROVIDER_NAME "android.hardware.camera.provider.ICameraProvider/internal/0" +#define TORCH_PROVIDER_IFACE "android.hardware.camera.provider.ICameraProvider" +#define TORCH_DEVICE_IFACE "android.hardware.camera.device.ICameraDevice" + +/* AIDL transaction codes are FIRST_CALL_TRANSACTION plus the declaration index. */ +#define PROVIDER_GET_CAMERA_ID_LIST 3 +#define PROVIDER_GET_DEVICE_INTERFACE 4 +#define DEVICE_SET_TORCH_MODE 7 + +static GBinderServiceManager* torch_sm = NULL; +static GBinderClient* torch_provider = NULL; +static GBinderClient* torch_device = NULL; +static char** torch_camera_ids = NULL; +static gboolean torch_probed = FALSE; + +/* Every AIDL reply starts with an exception code; 0 is success. */ +static gboolean +reply_ok(GBinderReader* reader) +{ + gint32 exception = 0; + + return gbinder_reader_read_int32(reader, &exception) && exception == 0; +} + +static char** +read_camera_ids(void) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GPtrArray* ids = NULL; + int status = 0; + + req = gbinder_client_new_request(torch_provider); + reply = gbinder_client_transact_sync_reply(torch_provider, + PROVIDER_GET_CAMERA_ID_LIST, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + gint32 count = 0; + + gbinder_remote_reply_init_reader(reply, &reader); + + if (reply_ok(&reader) && gbinder_reader_read_int32(&reader, &count) && count > 0) + { + gint32 i; + + ids = g_ptr_array_new(); + + for (i = 0; i < count; i++) + { + char* name = gbinder_reader_read_string16(&reader); + + if (!name) + break; + + g_ptr_array_add(ids, name); + } + + g_ptr_array_add(ids, NULL); + } + + gbinder_remote_reply_unref(reply); + } + + return ids ? (char**) g_ptr_array_free(ids, FALSE) : NULL; +} + +static GBinderClient* +camera_device_client(const char* name) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GBinderWriter writer; + GBinderClient* client = NULL; + int status = 0; + + req = gbinder_client_new_request(torch_provider); + gbinder_local_request_init_writer(req, &writer); + gbinder_writer_append_string16(&writer, name); + reply = gbinder_client_transact_sync_reply(torch_provider, + PROVIDER_GET_DEVICE_INTERFACE, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + + gbinder_remote_reply_init_reader(reply, &reader); + + if (reply_ok(&reader)) + { + GBinderRemoteObject* obj = gbinder_reader_read_object(&reader); + + if (obj) + { + client = gbinder_client_new(obj, TORCH_DEVICE_IFACE); + gbinder_remote_object_unref(obj); + } + } + + gbinder_remote_reply_unref(reply); + } + + return client; +} + +static gboolean +set_torch_mode(GBinderClient* device, int enable) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GBinderWriter writer; + gboolean ok = FALSE; + int status = 0; + + req = gbinder_client_new_request(device); + gbinder_local_request_init_writer(req, &writer); + gbinder_writer_append_bool(&writer, enable != 0); + reply = gbinder_client_transact_sync_reply(device, DEVICE_SET_TORCH_MODE, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + + gbinder_remote_reply_init_reader(reply, &reader); + ok = reply_ok(&reader); + gbinder_remote_reply_unref(reply); + } + + return ok; +} + +static void +torch_probe(void) +{ + GBinderRemoteObject* remote; + int status = 0; + + torch_probed = TRUE; + + torch_sm = gbinder_servicemanager_new(TORCH_BINDER_DEV); + + if (!torch_sm) + { + g_warning("flashlight: cannot open %s", TORCH_BINDER_DEV); + return; + } + + /* Autoreleased, so it must not be unreffed here. */ + remote = gbinder_servicemanager_get_service_sync(torch_sm, TORCH_PROVIDER_NAME, + &status); + + if (!remote) + { + g_warning("flashlight: %s is not registered", TORCH_PROVIDER_NAME); + g_clear_pointer(&torch_sm, gbinder_servicemanager_unref); + return; + } + + torch_provider = gbinder_client_new(remote, TORCH_PROVIDER_IFACE); + torch_camera_ids = read_camera_ids(); + + if (!torch_camera_ids) + { + g_warning("flashlight: camera provider returned no cameras"); + g_clear_pointer(&torch_provider, gbinder_client_unref); + g_clear_pointer(&torch_sm, gbinder_servicemanager_unref); + } +} + +int +flashlight_camera_hal_supported() +{ + if (!torch_probed) + torch_probe(); + + return torch_camera_ids != NULL; +} + +int +flashlight_camera_hal_set(int enable) +{ + char** name; + + if (!flashlight_camera_hal_supported()) + return 0; + + if (torch_device && set_torch_mode(torch_device, enable)) + return 1; + + /* Only one camera owns the flash, and which one is not known up front. */ + g_clear_pointer(&torch_device, gbinder_client_unref); + + for (name = torch_camera_ids; *name; name++) + { + GBinderClient* device = camera_device_client(*name); + + if (!device) + continue; + + if (set_torch_mode(device, enable)) + { + torch_device = device; + return 1; + } + + gbinder_client_unref(device); + } + + return 0; +} |
