summaryrefslogtreecommitdiff
path: root/taro/src
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2024-10-19 12:05:15 -0400
committerAndrew Opalach <andrew@akon.city> 2024-10-19 12:13:26 -0400
commit42e8c4d28b6fe9cc5e041f02e0c85b390d49a218 (patch)
tree59080eec3dd79d225bb31fb0f7e646cc22880d39 /taro/src
parent2bb807dd32e1c18a6f1e8f22c25141de3334de9f (diff)
downloadmauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.tar.gz
mauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.tar.bz2
mauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.zip
Attempt to make usable and packageable
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'taro/src')
-rw-r--r--taro/src/config.h69
-rw-r--r--taro/src/daemon.c446
-rw-r--r--taro/src/db.c212
-rw-r--r--taro/src/db.h41
-rw-r--r--taro/src/file_ext.c56
-rw-r--r--taro/src/file_ext.h7
-rw-r--r--taro/src/ui.c1203
7 files changed, 0 insertions, 2034 deletions
diff --git a/taro/src/config.h b/taro/src/config.h
deleted file mode 100644
index ddf3430..0000000
--- a/taro/src/config.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-
-#include <al/str.h>
-
-#include "file_ext.h"
-
-#define CONFIG_PATH "/.config/taro"
-
-struct config {
- str config_dir;
- str wallpaper_dir;
-};
-
-AL_UNUSED_FUNCTION_PUSH
-
-static void close_config(struct config *conf)
-{
- al_str_free(&conf->config_dir);
- al_str_free(&conf->wallpaper_dir);
-}
-
-static bool open_config(struct config *conf)
-{
- conf->config_dir = al_str_zero();
- conf->wallpaper_dir = al_str_zero();
-
- char *envhome = getenv("HOME");
- if (!envhome) return false;
- al_str_from(&conf->config_dir, envhome);
- al_str_cat(&conf->config_dir, al_str_c(CONFIG_PATH));
- if (!aki_dir_exists(&conf->config_dir)) {
- if (!aki_dir_create(&conf->config_dir)) {
- close_config(conf);
- return false;
- }
- }
-
- str config_path;
- al_str_clone(&config_path, &conf->config_dir);
- al_str_cat(&config_path, al_str_c("/config.json"));
- json_t *root = aki_file_open_as_json(&config_path);
- al_str_free(&config_path);
- if (!root) {
- close_config(conf);
- return false;
- }
-
- json_t *wallpaper_dir = json_object_get(root, "wallpaper_directory");
- if (!wallpaper_dir) {
- json_decref(root);
- close_config(conf);
- return false;
- }
-
- const char *dir_str = json_string_value(wallpaper_dir);
- if (dir_str[0] == '~') {
- al_str_from(&conf->wallpaper_dir, envhome);
- dir_str++;
- al_str_cat(&conf->wallpaper_dir, al_str_cr(dir_str));
- } else {
- al_str_from(&conf->wallpaper_dir, dir_str);
- }
-
- json_decref(root);
-
- return true;
-}
-
-AL_UNUSED_FUNCTION_POP
diff --git a/taro/src/daemon.c b/taro/src/daemon.c
deleted file mode 100644
index 45eb599..0000000
--- a/taro/src/daemon.c
+++ /dev/null
@@ -1,446 +0,0 @@
-#include <sys/wait.h>
-#include <al/types.h>
-#include <al/array.h>
-#include <al/log.h>
-#include <al/str.h>
-#include <aki/common.h>
-#include <aki/event_loop.h>
-#include <aki/fs_event.h>
-#include <aki/socket.h>
-#include <stl/window.h>
-#include <jansson.h>
-
-#include "file_ext.h"
-#include "config.h"
-
-struct monitor {
- struct stl_monitor *os;
- struct aki_fs_event fs_event;
- bool self_event_skip;
- str config_dir;
- str selection_path;
- pid_t pid;
- bool paused;
- bool covered;
- struct daemon *dmon;
-};
-
-#define IPC_HEADER_LENGTH 14
-#define IPC_MESSAGE_BUF_SIZE 64
-
-struct daemon {
- struct aki_event_loop loop;
- struct aki_poll context_poll;
- array(struct monitor *) monitors;
- struct aki_socket ipc_socket;
- bool connected;
- struct aki_poll ipc_poll;
- struct {
- u8 *buf;
- ssize_t index;
- ssize_t size;
- } out;
- struct {
- u8 *buf;
- ssize_t index;
- ssize_t size;
- ssize_t alloc;
- } ipc;
- struct config conf;
-};
-
-static s32 parse_and_set_props(str *selection, str *wallpaper_path, bool *is_package)
-{
- s32 index = al_str_find(selection, '|');
- s32 rindex = al_str_rfind(selection, '|');
- if (index == -1 || rindex == -1 || index == rindex) {
- return -1;
- }
-
- // index < 2 or id->len == 0 is expected behavior
- // when a monitor has no wallpaper set.
- if (index < 2) return 1;
- str *id = al_str_substr(selection, 0, index - 2);
- str *type = al_str_substr(selection, index - 2, index);
- if (id->len == 0) return 1;
-
- al_str_cat(wallpaper_path, id);
-
- if (al_str_eq(type, al_str_c("_p"))) {
- *is_package = true;
- } else if (al_str_eq(type, al_str_c("_d"))) {
- *is_package = false;
- } else {
- return -1;
- }
-
- return 0;
-}
-
-static bool send_signal_to_pid(pid_t pid, s32 signal)
-{
- if (kill(pid, signal) != 0) {
- al_log_error("tarod", "kill(%d) failed (%s)", pid, aki_strerror(errno));
- return false;
- }
- return true;
-}
-
-static pid_t exec_or_exit(char *args[])
-{
- pid_t pid = fork();
- if (pid == -1) {
- al_log_error("tarod", "fatal: failed to fork process, exiting...");
- exit(EXIT_FAILURE);
- } else if (pid == 0) {
- if (execv(args[0], args) == -1) {
- al_log_error("tarod", "fatal: failed to run '%s', exiting...", args[0]);
- // This should return the pid of init if the parent process has already exited.
- pid_t ppid = getppid();
- if (ppid != 1) send_signal_to_pid(ppid, SIGINT);
- }
- }
- return pid;
-}
-
-static void maybe_kill_wallpaper(struct monitor *monitor)
-{
- if (monitor->pid == -1) return;
-
- if (monitor->paused) {
- if (!send_signal_to_pid(monitor->pid, SIGCONT)) {
- send_signal_to_pid(monitor->pid, SIGKILL);
- }
- monitor->paused = false;
- }
-
- send_signal_to_pid(monitor->pid, SIGTERM);
- monitor->pid = -1;
-}
-
-static bool run_wallpaper(struct daemon *dmon, struct monitor *monitor)
-{
- maybe_kill_wallpaper(monitor);
-
- str selection;
- monitor->self_event_skip = true;
- if (!aki_file_open_and_read_wholly(&monitor->selection_path, &selection, true)) {
- monitor->self_event_skip = false;
- return false;
- }
-
- if (selection.len == 0) {
- al_log_error("tarod", "config for %s is empty", monitor->os->name);
- al_str_free(&selection);
- return false;
- }
-
- str wallpaper_path;
- al_str_clone(&wallpaper_path, &dmon->conf.wallpaper_dir);
- al_str_cat(&wallpaper_path, al_str_c("/"));
- bool is_package;
- s32 ret = parse_and_set_props(&selection, &wallpaper_path, &is_package);
- al_str_free(&selection);
- if (ret != 0) {
- if (ret == -1) {
- al_log_error("tarod", "failed to set properties for wallpaper on %s", monitor->os->name);
- }
- al_str_free(&wallpaper_path);
- return false;
- }
-
- al_log_info("tarod", "setting wallpaper %.*s on %s", AL_STR_PRINTF(&wallpaper_path), monitor->os->name);
-
- char width[8], height[8];
- al_sprintf(width, "%d", monitor->os->width / monitor->os->scale);
- al_sprintf(height, "%d", monitor->os->height / monitor->os->scale);
-
- char *c_str0 = al_str_to_c_str(&wallpaper_path);
- al_str_free(&wallpaper_path);
- monitor->pid = exec_or_exit((char *[]){ "./run_wallpaper.sh",
- AL_BOOLSTR(is_package), c_str0, monitor->os->name, width, height, NULL });
- al_free(c_str0);
-
- if (monitor->covered && send_signal_to_pid(monitor->pid, SIGSTOP)) {
- monitor->paused = true;
- }
-
- return true;
-}
-
-static void selection_fs_event_callback(void *userdata, struct inotify_event *event)
-{
- struct monitor *monitor = (struct monitor *)userdata;
- (void)event;
- if (monitor->self_event_skip) {
- monitor->self_event_skip = false;
- return;
- }
- struct daemon *dmon = monitor->dmon;
- run_wallpaper(dmon, monitor);
-}
-
-static void output_discovered_callback(void *userdata, struct stl_monitor *os_monitor)
-{
- struct daemon *dmon = (struct daemon *)userdata;
-
- struct monitor *monitor;
- al_array_foreach(dmon->monitors, i, monitor) {
- if (strcmp(monitor->os->name, os_monitor->name) == 0) {
- if (monitor->pid == -1) {
- run_wallpaper(dmon, monitor);
- }
- return;
- }
- }
-
- str config_dir;
- al_str_clone(&config_dir, &dmon->conf.config_dir);
- al_str_cat(&config_dir, al_str_c("/"));
- al_str_cat(&config_dir, al_str_cr(os_monitor->name));
-
- if (!aki_dir_exists(&config_dir)) {
- if (!aki_dir_create(&config_dir)) return;
- }
-
- monitor = al_alloc_object(struct monitor);
- monitor->os = os_monitor;
- monitor->dmon = dmon;
- al_array_push(dmon->monitors, monitor);
-
- al_str_clone(&monitor->config_dir, &config_dir);
- al_str_free(&config_dir);
-
- al_str_clone(&monitor->selection_path, &monitor->config_dir);
- al_str_cat(&monitor->selection_path, al_str_c("/selection"));
-
- if (!aki_file_exists(&monitor->selection_path)) {
- aki_file_create(&monitor->selection_path);
- aki_file_open_and_replace(&monitor->selection_path, al_str_c("||"), false);
- }
-
- monitor->pid = -1;
- monitor->paused = false;
- monitor->covered = false;
-
- run_wallpaper(dmon, monitor);
-
- monitor->self_event_skip = false;
- aki_fs_event_init(&monitor->fs_event, &monitor->selection_path, AKI_CLOSE_WRITE,
- selection_fs_event_callback, monitor);
- aki_fs_event_start(&monitor->fs_event, &monitor->dmon->loop);
-}
-
-static void close_monitor(struct monitor *monitor)
-{
- al_str_free(&monitor->config_dir);
- al_str_free(&monitor->selection_path);
- aki_fs_event_stop(&monitor->fs_event);
- aki_fs_event_free(&monitor->fs_event);
-}
-
-static void ipc_handle_subscribe_success(json_t *root)
-{
- if (json_boolean_value(root)) {
- al_log_info("tarod", "successfully subscribed to sway background covered events");
- } else {
- al_log_info("tarod", "failed to subscribe to sway background covered events");
- }
-}
-
-static void ipc_handle_response(struct daemon *dmon, u8 *buf, u32 size)
-{
- al_log_debug("tarod", "IN: %.*s", size, (char *)buf);
-
- json_error_t error;
- json_t *root = json_loadb((char *)buf, size, 0, &error);
- if (!root) {
- al_log_error("tarod", "failed to parse sway-ipc json payload (%s)", error.text);
- goto done;
- }
-
- json_t *success = json_object_get(root, "success");
- if (success) {
- ipc_handle_subscribe_success(success);
- goto done;
- }
-
- json_t *output = json_object_get(root, "output");
- if (output) {
- bool covered = json_boolean_value(json_object_get(root, "covered"));
- struct monitor *monitor;
- al_array_foreach(dmon->monitors, i, monitor) {
- if (strcmp(monitor->os->name, json_string_value(output)) != 0) continue;
- monitor->covered = covered;
- if (monitor->pid == -1) goto done;
- if (covered && !monitor->paused && send_signal_to_pid(monitor->pid, SIGSTOP)) {
- monitor->paused = true;
- } else if (!covered && monitor->paused && send_signal_to_pid(monitor->pid, SIGCONT)) {
- monitor->paused = false;
- }
- break;
- }
- }
-
-done:
- if (root) json_decref(root);
-}
-
-static void ensure_ipc_buf_size(struct daemon *dmon, ssize_t size)
-{
- if (dmon->ipc.alloc < size) {
- dmon->ipc.buf = realloc(dmon->ipc.buf, size);
- dmon->ipc.alloc = size;
- }
-}
-
-static void ipc_poll_callback(void *userdata, s32 revents)
-{
- struct daemon *dmon = (struct daemon *)userdata;
-
- if (revents & AKI_POLL_WRITE) {
- if (!dmon->connected) {
- al_log_info("tarod", "connected to sway ipc socket");
- dmon->connected = true;
- }
- if (!dmon->out.buf) return;
- dmon->out.index += aki_socket_write(&dmon->ipc_socket, dmon->out.buf + dmon->out.index,
- dmon->out.size - dmon->out.index);
- if (dmon->out.index == dmon->out.size) {
- al_free(dmon->out.buf);
- dmon->out.buf = NULL;
- aki_poll_stop(&dmon->ipc_poll);
- aki_poll_set(&dmon->ipc_poll, aki_socket_get_fd(&dmon->ipc_socket), AKI_POLL_READ);
- aki_poll_start(&dmon->ipc_poll, &dmon->loop);
- }
- }
-
- if (revents & AKI_POLL_READ) {
- if (dmon->ipc.index < IPC_HEADER_LENGTH) {
- dmon->ipc.index += aki_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index,
- IPC_HEADER_LENGTH - dmon->ipc.index);
- }
- if (dmon->ipc.index == IPC_HEADER_LENGTH && !dmon->ipc.size) {
- dmon->ipc.size = *((s32 *)&dmon->ipc.buf[6]) + IPC_HEADER_LENGTH;
- ensure_ipc_buf_size(dmon, dmon->ipc.size);
- }
- if (dmon->ipc.size) {
- dmon->ipc.index += aki_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index,
- dmon->ipc.size - dmon->ipc.index);
- if (dmon->ipc.index == dmon->ipc.size) {
- ipc_handle_response(dmon, dmon->ipc.buf + IPC_HEADER_LENGTH,
- dmon->ipc.size - IPC_HEADER_LENGTH);
- dmon->ipc.size = 0;
- dmon->ipc.index = 0;
- }
- }
- }
-}
-
-static void context_poll_callback(void *userdata, s32 revents)
-{
- (void)userdata;
- al_assert(revents & AKI_POLL_READ);
- stl_global_process_events();
-}
-
-
-// https://stackoverflow.com/a/59071370
-static void handle_sigchld(s32 sig)
-{
- (void)sig;
- s32 saved_errno = errno;
- while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {}
- errno = saved_errno;
-}
-
-#define SWAY_IPC_SUBSCRIBE 2
-
-static struct daemon dmon = { 0 };
-
-s32 main(void)
-{
- if (!aki_common_init()) return EXIT_FAILURE;
-
- signal(SIGCHLD, handle_sigchld);
-
- s32 ret = EXIT_FAILURE;
-
- if (!open_config(&dmon.conf)) goto out;
-
- aki_event_loop_init(&dmon.loop);
-
- al_array_init(dmon.monitors);
-
- stl_set_output_discovered_callback(output_discovered_callback, &dmon);
- if (!stl_global_init()) return EXIT_FAILURE;
- aki_poll_init(&dmon.context_poll, context_poll_callback, &dmon);
- aki_poll_set(&dmon.context_poll, stl_global_get_poll_fd(), AKI_POLL_READ);
- aki_poll_start(&dmon.context_poll, &dmon.loop);
-
- str monitors;
- al_str_from(&monitors, "");
- struct monitor *monitor;
- al_array_foreach(dmon.monitors, i, monitor) {
- al_str_cat(&monitors, al_str_cr(monitor->os->name));
- al_str_cat(&monitors, al_str_c("\n"));
- }
-
- str monitors_path;
- al_str_clone(&monitors_path, &dmon.conf.config_dir);
- al_str_cat(&monitors_path, al_str_c("/monitors"));
- if (!aki_file_exists(&monitors_path)) {
- aki_file_create(&monitors_path);
- }
- aki_file_open_and_replace(&monitors_path, &monitors, false);
-
- dmon.ipc.buf = NULL;
- dmon.ipc.size = 0;
- dmon.ipc.index = 0;
- dmon.ipc.alloc = 0;
- ensure_ipc_buf_size(&dmon, IPC_HEADER_LENGTH);
-
- json_t *payload = json_array();
- json_array_append(payload, json_string("background_covered"));
- dmon.out.buf = al_malloc(IPC_MESSAGE_BUF_SIZE);
- al_memcpy(dmon.out.buf, "i3-ipc", strlen("i3-ipc"));
- s32 type = SWAY_IPC_SUBSCRIBE;
- al_memcpy(&dmon.out.buf[10], &type, sizeof(s32));
- s32 len = json_dumpb(payload, (char *)&dmon.out.buf[14], IPC_MESSAGE_BUF_SIZE - IPC_HEADER_LENGTH, 0);
- al_memcpy(&dmon.out.buf[6], &len, sizeof(s32));
- dmon.out.size = IPC_HEADER_LENGTH + len;
- dmon.out.index = 0;
- json_decref(payload);
-
- dmon.ipc_socket.type = AKI_SOCKET_UNIX;
- aki_socket_init(&dmon.ipc_socket);
- aki_socket_set_blocking(&dmon.ipc_socket, false);
- aki_poll_init(&dmon.ipc_poll, ipc_poll_callback, &dmon);
- aki_poll_set(&dmon.ipc_poll, aki_socket_get_fd(&dmon.ipc_socket), AKI_POLL_WRITE);
- dmon.connected = false;
- char *swaysock = getenv("SWAYSOCK");
- if (swaysock && aki_socket_connect(&dmon.ipc_socket, al_str_cr(swaysock), 0)) {
- aki_poll_start(&dmon.ipc_poll, &dmon.loop);
- } else {
- al_log_error("tarod", "failed to connect to sway ipc socket");
- }
-
- aki_event_loop_run(&dmon.loop);
-
- al_array_foreach(dmon.monitors, i, monitor) {
- close_monitor(monitor);
- }
- al_array_free(dmon.monitors);
- al_free(dmon.ipc.buf);
-
- aki_event_loop_destroy(&dmon.loop);
-
- close_config(&dmon.conf);
-
- ret = EXIT_SUCCESS;
-
-out:
- aki_common_close();
-
- return ret;
-}
diff --git a/taro/src/db.c b/taro/src/db.c
deleted file mode 100644
index 9d2ffa2..0000000
--- a/taro/src/db.c
+++ /dev/null
@@ -1,212 +0,0 @@
-#include <al/log.h>
-
-#include "db.h"
-#include "file_ext.h"
-
-#define REAL_OR_INT(v) \
- json_typeof(v) == JSON_REAL ? json_real_value(v) : json_integer_value(v)
-
-static bool load_entry(struct entry *e)
-{
- struct aki_dir dir;
- if (!aki_dir_open(&dir, &e->path)) return false;
-
- struct aki_dir_entry entry;
- while (aki_dir_read(&dir, &entry)) {
- if (entry.entry->d_type != DT_REG) {
- continue;
- }
-
- s32 index = al_str_rfind(al_str_cr(entry.entry->d_name), '.');
- if (index == -1) {
- continue;
- }
-
- if (al_str_eq(al_str_w(entry.entry->d_name, 0, index), al_str_c("project"))) {
- str package_path;
- al_str_clone(&package_path, &e->path);
- al_str_cat(&package_path, al_str_c("/scene.pkg"));
- e->has_package = aki_file_exists(&package_path);
- al_str_free(&package_path);
-
- str project_path;
- al_str_clone(&project_path, &e->path);
- al_str_cat(&project_path, al_str_c("/"));
- al_str_cat(&project_path, al_str_cr(entry.entry->d_name));
-
- json_t *root = aki_file_open_as_json(&project_path);
- al_str_free(&project_path);
- if (!root) continue;
- e->loaded = true;
-
- json_t *title = json_object_get(root, "title");
- if (title) {
- al_wstr_from_cstr(&e->title, (char *)json_string_value(title));
- }
-
- json_t *preview = json_object_get(root, "preview");
- if (preview) {
- al_str_clone(&e->preview_path, &e->path);
- al_str_cat(&e->preview_path, al_str_c("/"));
- al_str_cat(&e->preview_path, al_str_cr(json_string_value(preview)));
- e->has_preview = aki_file_exists(&e->preview_path);
- }
-
- json_t *general = json_object_get(root, "general");
- if (!general) {
- continue;
- }
-
- al_array_init(e->props);
- json_t *properties = json_object_get(general, "properties");
- if (properties) {
- void *iter = json_object_iter(properties);
- json_t *iter_obj;
- json_t *text, *type;
- while (iter) {
- iter_obj = json_object_iter_value(iter);
- text = json_object_get(iter_obj, "text");
- if (!text || strcmp(json_string_value(text), "ui_browse_properties_scheme_color") == 0) {
- iter = json_object_iter_next(properties, iter);
- continue;
- }
-
- struct prop p;
- p.type = PROP_TYPE_UNKNOWN;
- al_wstr_from_cstr(&p.title, (char *)json_string_value(text));
- type = json_object_get(iter_obj, "type");
- if (!type) {
- // Silently ignore for now. Is "Notes" a special case? see 1918273588.
- //log_warn("improperly handled prop (%.*s)", AL_STR_PRINTF(&e->id));
- iter = json_object_iter_next(properties, iter);
- continue;
- }
-
- str *type_str = al_str_cr(json_string_value(type));
- if (al_str_eq(type_str, al_str_c("bool"))) {
- json_t *value = json_object_get(iter_obj, "value");
- if (!value) {
- al_log_warn("db", "improperly handled bool (%.*s)", AL_STR_PRINTF(&e->id));
- iter = json_object_iter_next(properties, iter);
- continue;
- }
- p.value = al_malloc(sizeof(bool));
- *((bool *)p.value) = json_boolean_value(value);
- p.type = PROP_TYPE_BOOL;
- } else if (al_str_eq(type_str, al_str_c("slider"))) {
- json_t *value = json_object_get(iter_obj, "value");
- json_t *min = json_object_get(iter_obj, "min");
- json_t *max = json_object_get(iter_obj, "max");
- if (!value || !min || !max) {
- al_log_warn("db", "improperly handled slider (%.*s)", AL_STR_PRINTF(&e->id));
- iter = json_object_iter_next(properties, iter);
- continue;
- }
- json_t *step = json_object_get(iter_obj, "step");
- p.value = al_malloc(sizeof(struct slider_value));
- struct slider_value *slider = (struct slider_value *)p.value;
- slider->value = REAL_OR_INT(value);
- slider->min = REAL_OR_INT(min);
- slider->max = REAL_OR_INT(max);
- if (step) {
- slider->step = REAL_OR_INT(step);
- } else {
- // Assume the default step is 1.0.
- slider->step = 1.0;
- }
- p.type = PROP_TYPE_SLIDER;
- } else {
- //al_log_warn("db", "unhandled type. (%.*s)", AL_STR_PRINTF(type_str));
- }
-
- al_array_push(e->props, p);
-
- iter = json_object_iter_next(properties, iter);
- }
- }
- }
- }
-
- aki_dir_close(&dir);
-
- return e->loaded;
-}
-
-bool db_ensure_entry_loaded(struct db *db, struct entry *e)
-{
- if (!e->loaded && !load_entry(e)) {
- // Remove entry if it cannot be loaded.
- struct entry *re;
- al_array_foreach_ptr(db->entries, i, re) {
- if (re == e) {
- al_array_remove_at(db->entries, i);
- break;
- }
- }
- }
- return e->loaded;
-}
-
-static s32 id_compare(void *a, void *b)
-{
- s64 aid = al_str_to_long(&((struct entry *)a)->id, 10);
- s64 bid = al_str_to_long(&((struct entry *)b)->id, 10);
- if (aid > bid) {
- return 1;
- } else if (aid < bid) {
- return -1;
- } else {
- return 0;
- }
-}
-
-bool db_load_entries(struct db *db, str *path)
-{
- struct aki_dir dir;
- if (!aki_dir_open(&dir, path)) return false;
-
- al_array_init(db->entries);
- struct aki_dir_entry entry;
- while (aki_dir_read(&dir, &entry)) {
- if (!(entry.entry->d_type == DT_DIR || entry.entry->d_type == DT_UNKNOWN)) {
- continue;
- }
- if (strcmp(entry.entry->d_name, ".") == 0 ||
- strcmp(entry.entry->d_name, "..") == 0) {
- continue;
- }
- struct entry e = { 0 };
- e.loaded = false;
- al_str_clone(&e.id, al_str_cr(entry.entry->d_name));
- al_str_clone(&e.path, path);
- al_str_cat(&e.path, al_str_c("/"));
- al_str_cat(&e.path, &e.id);
- if (!aki_dir_exists(&e.path)) {
- al_str_free(&e.id);
- al_str_free(&e.path);
- continue;
- }
- al_array_push(db->entries, e);
- }
-
- al_array_sort(db->entries, struct entry, id_compare);
-
- return true;
-}
-
-void db_unload(struct db *db)
-{
- struct entry *e;
- al_array_foreach_ptr(db->entries, i, e) {
- al_str_free(&e->path);
- al_str_free(&e->preview_path);
- al_wstr_free(&e->title);
- struct prop *p;
- al_array_foreach_ptr(e->props, j, p) {
- al_wstr_free(&p->title);
- if (p->type != PROP_TYPE_UNKNOWN) {
- al_free(p->value);
- }
- }
- }
-}
diff --git a/taro/src/db.h b/taro/src/db.h
deleted file mode 100644
index 78f4c77..0000000
--- a/taro/src/db.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include <al/array.h>
-#include <al/wstr.h>
-
-enum {
- PROP_TYPE_BOOL,
- PROP_TYPE_SLIDER,
- PROP_TYPE_UNKNOWN
-};
-
-struct slider_value {
- f64 min, max;
- f64 step;
- f64 value;
-};
-
-struct prop {
- u8 type;
- wstr title;
- void *value;
-};
-
-struct entry {
- bool loaded;
- str id;
- str path;
- bool has_package;
- str preview_path;
- bool has_preview;
- wstr title;
- array(struct prop) props;
-};
-
-struct db {
- array(struct entry) entries;
-};
-
-bool db_load_entries(struct db *db, str *path);
-bool db_ensure_entry_loaded(struct db *db, struct entry *e);
-void db_unload(struct db *db);
diff --git a/taro/src/file_ext.c b/taro/src/file_ext.c
deleted file mode 100644
index 48dffce..0000000
--- a/taro/src/file_ext.c
+++ /dev/null
@@ -1,56 +0,0 @@
-#include <al/log.h>
-
-#include "file_ext.h"
-
-bool aki_file_open_and_read_wholly(str *path, str *out, bool lock)
-{
- struct aki_file file;
- if (!aki_file_open(&file, path, lock ? AKI_FILE_LOCK : AKI_FILE_READONLY)) {
- return false;
- }
- s32 ret = aki_file_read_as_str(&file, out);
- aki_file_close(&file);
- return ret != -1;
-}
-
-bool aki_file_open_and_replace(str *path, str *buf, bool lock)
-{
- struct aki_file file;
- if (!aki_file_open(&file, path, lock ? AKI_FILE_LOCK : 0)) {
- return false;
- }
- s32 ret = aki_file_replace(&file, buf);
- aki_file_close(&file);
- return ret != -1;
-}
-
-json_t *aki_file_open_as_json(str *path)
-{
- str s;
- if (!aki_file_open_and_read_wholly(path, &s, false)) {
- return NULL;
- }
- json_error_t error;
- json_t *root = json_loadb(s.data, s.len, 0, &error);
- if (!root) {
- al_log_error("file_ext", "json_loadb(%.*s, %u) failed (%s)", AL_STR_PRINTF(path), s.len, error.text);
- }
- al_str_free(&s);
- return root;
-}
-
-bool aki_file_dump_and_decref_json(str *path, json_t *root)
-{
- str s;
- size_t size = json_dumpb(root, NULL, 0, JSON_INDENT(4));
- al_str_sized(&s, size);
- s.len = json_dumpb(root, s.data, size, JSON_INDENT(4));
- if (s.len == 0) {
- json_decref(root);
- return false;
- }
- bool ret = aki_file_open_and_replace(path, &s, false);
- al_str_free(&s);
- json_decref(root);
- return ret;
-}
diff --git a/taro/src/file_ext.h b/taro/src/file_ext.h
deleted file mode 100644
index ddaceba..0000000
--- a/taro/src/file_ext.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <aki/file.h>
-#include <jansson.h>
-
-bool aki_file_open_and_read_wholly(str *path, str *out, bool lock);
-bool aki_file_open_and_replace(str *path, str *buf, bool lock);
-json_t *aki_file_open_as_json(str *path);
-bool aki_file_dump_and_decref_json(str *path, json_t *root);
diff --git a/taro/src/ui.c b/taro/src/ui.c
deleted file mode 100644
index b2c700e..0000000
--- a/taro/src/ui.c
+++ /dev/null
@@ -1,1203 +0,0 @@
-#include <notcurses/notcurses.h>
-#include <notcurses/nckeys.h>
-#include <aki/common.h>
-#include <aki/event_loop.h>
-#include <aki/timer.h>
-#include <al/log.h>
-
-#include "db.h"
-#include "file_ext.h"
-#include "config.h"
-
-#define PIXEL_BLIT 1
-
-#define TIMER_UPDATE_BASE 1.f
-
-#define TILE_WIDTH 18
-#define TILE_HEIGHT 10
-#define FLASH_DURATION 0.3f
-
-#define TEXT_WIDTH (TILE_WIDTH - 4)
-#define TEXT_UPDATE_RATE 0.3f;
-
-#define INFO_RES_WIDTH 33
-#define INFO_RES_HEIGHT 2
-
-#define INFO_X_OFFSET 2
-#define INFO_Y_OFFSET 1
-
-#define INFO_WIDTH 30
-// INFO_HEIGHT = term_rows + INFO_RES_HEIGHT
-
-#define INFO_TILE_WIDTH 24
-#define INFO_TILE_HEIGHT 12
-
-#define INFO_TEXT_WIDTH (TEXT_WIDTH + 4)
-
-#define PROP_TEXT_WIDTH 16
-
-#define MAX_MONITOR_NAME_LEN 255
-
-struct scrolling_text {
- wstr *s;
- s32 width;
- s32 index, max;
- s32 x, y;
-};
-
-struct image {
- u32 hash;
- bool gif;
- bool errored;
- struct ncvisual *v;
-};
-
-struct image_pool {
- u32 max;
- u32 chunk_size;
- array(struct image) images;
-};
-
-struct tile {
- struct ncplane *p, *ip;
- struct entry *e;
- bool blitted;
- struct scrolling_text text;
-};
-
-struct monitors {
- array(char *) monitors;
- s32 selected_monitor;
- s32 set_monitor;
- f32 flash;
- str selection_path;
-};
-
-struct grid {
- struct ncplane *p;
- s32 tiles_x, tiles_y;
- s32 min_index;
- s32 max_index;
- s32 selected_index;
- s32 set_index;
- f32 flash;
- s32 tiles_per_page;
- array(struct tile) tiles;
-};
-
-struct info_prop {
- struct prop *p;
- struct scrolling_text text;
-};
-
-struct info {
- struct ncplane *ip;
- struct entry *e;
- bool blitted;
- struct scrolling_text text;
- u32 max_props;
- s32 selected_prop;
- array(struct info_prop) props;
-};
-
-enum {
- MENU_GRID = 0,
- MENU_INFO,
- MENU_MONITORS
-};
-
-struct ui {
- struct notcurses *nc;
- struct aki_event_loop loop;
- struct aki_poll input_poll;
- u64 delay;
- struct aki_timer timer;
- bool layed_out;
- f32 text_update;
- u32 term_rows, term_cols;
- u8 selected_menu;
- struct ncplane *p, *mp, *gp, *ip;
- struct monitors m;
- struct grid g;
- struct info n;
- struct image_pool pool;
- struct db db;
- struct config conf;
-};
-
-// ---------
-// Client
-// ---------
-
-static s32 get_entry_from_config(struct ui *u)
-{
- s32 ret = -1;
- str config;
- if (aki_file_open_and_read_wholly(&u->m.selection_path, &config, false)) {
- s32 index = al_str_find(&config, '|');
- if (index > 2) {
- str *id = al_str_substr(&config, 0, index - 2);
- struct entry *e;
- al_array_foreach_ptr(u->db.entries, i, e) {
- if (al_str_eq(&e->id, id)) {
- ret = i;
- break;
- }
- }
- }
- al_str_free(&config);
- }
- return ret;
-}
-
-static void select_monitor(struct ui *u)
-{
- if (u->m.set_monitor == -1) return;
- u->m.set_monitor = u->m.selected_monitor;
- al_str_free(&u->m.selection_path);
- al_str_clone(&u->m.selection_path, &u->conf.config_dir);
- al_str_cat(&u->m.selection_path, al_str_c("/"));
- char *monitor = al_array_at(u->m.monitors, u->m.set_monitor);
- al_str_cat(&u->m.selection_path, al_str_cr(monitor));
- al_str_cat(&u->m.selection_path, al_str_c("/selection"));
- u->g.set_index = get_entry_from_config(u);
- if (u->g.set_index >= 0) {
- u->g.selected_index = u->g.set_index;
- }
-}
-
-static void write_selected_entry_to_config(struct ui *u)
-{
- if (u->m.set_monitor == -1) return;
- str config;
- if (u->g.set_index == -1) {
- al_str_from(&config, "||");
- } else {
- al_str_clone(&config, &u->n.e->id);
- if (u->n.e->has_package) {
- al_str_cat(&config, al_str_c("_p"));
- } else {
- al_str_cat(&config, al_str_c("_d"));
- }
- al_str_cat(&config, al_str_c("|false,100|"));
- struct prop *p;
- al_array_foreach_ptr(u->n.e->props, i, p) {
- switch (p->type) {
- case PROP_TYPE_BOOL:
- al_str_cat(&config, *((bool *)p->value) ? al_str_c("true") : al_str_c("false"));
- break;
- }
- al_str_cat(&config, al_str_c(","));
- }
- if (al_str_at(&config, config.len - 1) == ',') {
- config.len--;
- }
- }
- aki_file_open_and_replace(&u->m.selection_path, &config, true);
-}
-
-// ---------
-// Interface
-// ---------
-
-static void set_scrolling_text(struct scrolling_text *text, wstr *s, s32 text_width, s32 width, s32 x, s32 y)
-{
- text->index = 0;
- text->width = width;
- text->s = s;
- if (text_width > text->width) {
- text->max = (text_width - text->width);
- // If the character at text->index is wider than 1 cell,
- // that is the amount the text will scroll by. So, account
- // for that here.
- for (s32 i = 0; i < text->max; i++) {
- s32 wcl = al_wchar_width(al_wstr_at(s, i));
- if (wcl > 1) {
- text->max -= wcl - 1;
- }
- }
- text->max++;
- } else {
- text->max = -1;
- }
- text->x = x;
- text->y = y;
-}
-
-static void draw_scrolling_text(struct ncplane *p, struct scrolling_text *t, bool update)
-{
- ncplane_cursor_move_yx(p, t->y, t->x);
- if (t->max != -1) {
- s32 i = 0;
- s32 width = 0;
- while (width < t->width) {
- wchar_t wc = al_wstr_at(t->s, t->index + i);
- width += al_wchar_width(wc);
- if (width > t->width) {
- break;
- }
- ncplane_putwc(p, wc);
- i++;
- }
- if (update) {
- t->index = (t->index + 1) % t->max;
- }
- } else {
- ncplane_putwstr(p, &al_wstr_at(t->s, 0));
- }
-}
-
-static void set_info_entry(struct ui *u, struct info *n)
-{
- n->selected_prop = 0;
- n->props.size = 0;
-
- if (u->g.selected_index == -1 || u->g.selected_index >= (s32)u->db.entries.size) {
- n->e = NULL;
- return;
- }
-
- n->e = &al_array_at(u->db.entries, u->g.selected_index);
-
- n->blitted = false;
-
- s32 width = al_wstr_width(&u->n.e->title);
- s32 tx = 2 + (width < INFO_TEXT_WIDTH ? (INFO_TEXT_WIDTH - width) / 2 : 0);
- set_scrolling_text(&n->text, &u->n.e->title, width, INFO_TEXT_WIDTH, tx, INFO_TILE_HEIGHT + 1);
-
- struct prop *p;
- al_array_foreach_ptr(u->n.e->props, i, p) {
- struct info_prop ip = { 0 };
- ip.p = p;
- width = al_wstr_width(&p->title);
- s32 ty = (INFO_TILE_HEIGHT + 3) + ((i % n->max_props) * 2);
- set_scrolling_text(&ip.text, &p->title, width, PROP_TEXT_WIDTH, 0, ty);
- al_array_push(n->props, ip);
- }
-}
-
-static bool init_info(struct info *n)
-{
- al_array_init(n->props);
- return true;
-}
-
-static bool init_image_pool(struct image_pool *pool)
-{
- pool->chunk_size = 0;
- pool->max = 0;
- al_array_init(pool->images);
- return true;
-}
-
-static bool init_grid(struct grid *g)
-{
- al_array_init(g->tiles);
- return true;
-}
-
-static s32 monitor_cmp(void *va, void *vb)
-{
- return strncmp(*(char **)va, *(char **)vb, MAX_MONITOR_NAME_LEN);
-}
-
-static bool init_monitors(struct ui *u, struct monitors *m)
-{
- al_array_init(m->monitors);
- str monitors_path;
- al_str_clone(&monitors_path, &u->conf.config_dir);
- al_str_cat(&monitors_path, al_str_c("/monitors"));
- str monitors;
- bool ret = aki_file_open_and_read_wholly(&monitors_path, &monitors, false);
- al_str_free(&monitors_path);
- if (!ret) {
- al_log_error("taro", "failed to open monitor list, is the daemon configured?");
- return false;
- }
- str monitor_dir;
- str monitor = al_str_zero();
- while (al_str_get_line(&monitors, '\n', &monitor)) {
- al_str_clone(&monitor_dir, &u->conf.config_dir);
- al_str_cat(&monitor_dir, al_str_c("/"));
- al_str_cat(&monitor_dir, &monitor);
- if (aki_dir_exists(&monitor_dir)) {
- char *c_str = al_str_to_c_str(&monitor);
- al_array_push(m->monitors, c_str);
- }
- al_str_free(&monitor_dir);
- }
- if (!u->m.monitors.size) {
- u->m.set_monitor = -1;
- u->m.selected_monitor = -1;
- }
- al_array_sort(m->monitors, char *, monitor_cmp);
- u->g.set_index = -1;
- if (m->selected_monitor >= 0) {
- select_monitor(u);
- }
- if (u->db.entries.size > 0) {
- u->g.selected_index = (u->g.set_index >= 0) ? u->g.set_index : 0;
- }
- return true;
-}
-
-static bool init_ui(struct ui *u)
-{
- if (!init_monitors(u, &u->m)) return false;
- if (!init_grid(&u->g)) return false;
- if (!init_image_pool(&u->pool)) return false;
- if (!init_info(&u->n)) return false;
- if (!(u->nc = notcurses_init(NULL, stdin))) {
- al_log_error("taro", "failed to initialize notcurses");
- return false;
- }
- return true;
-}
-
-struct image *get_image_from_pool(struct image_pool *pool, str *path)
-{
- u32 hash = al_str_hash(path);
- struct image *rimg;
- al_array_foreach_ptr(pool->images, i, rimg) {
- if (rimg->hash == hash) {
- return rimg;
- }
- }
-
- bool gif = false;
- s32 index = al_str_rfind(path, '.');
- if (index != -1) {
- if (al_str_eq(al_str_w(path->data, index, 4), al_str_c(".gif"))) {
- gif = true;
- }
- }
-
- if (pool->images.size >= pool->max) {
- // al_array_remove_range()...
- for (u32 i = 0; i < pool->chunk_size; i++) {
- struct image img;
- al_array_pop_at(pool->images, 0, img);
- ncvisual_destroy(img.v);
- }
- }
-
- char *c_str = al_str_to_c_str(path);
- struct image img = {
- .hash = hash,
- .gif = gif,
- .errored = false,
- .v = ncvisual_from_file(c_str)
- };
- al_free(c_str);
- if (!img.v) return NULL;
- al_array_push(pool->images, img);
-
- return &al_array_last(pool->images);
-}
-
-static bool layout_ui(struct ui *u);
-static void draw_monitors(struct ui *u, struct monitors *m);
-static void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text);
-static void draw_info(struct ui *u, struct info *n, bool update_text);
-
-static s32 resize_cb(struct ncplane *p)
-{
- struct ui *u = ncplane_userptr(p);
- if (!u) return 0;
- notcurses_stddim_yx(u->nc, &u->term_rows, &u->term_cols);
- if (!layout_ui(u)) {
- u->layed_out = false;
- return 0;
- }
- u->layed_out = true;
- struct tile *t;
- al_array_foreach_ptr(u->g.tiles, i, t) {
- t->blitted = false;
- ncplane_erase(t->ip);
- }
- u->n.blitted = false;
- ncplane_erase(u->n.ip);
- // To not break images we need to erase them
- // then render them in 2 steps.
- notcurses_refresh(u->nc, NULL, NULL);
- notcurses_render(u->nc);
- draw_monitors(u, &u->m);
- draw_grid_tiles(u, &u->g, false);
- draw_info(u, &u->n, false);
- notcurses_render(u->nc);
- return 0;
-}
-
-static void maybe_destroy_info(struct info *n)
-{
- if (n->ip) {
- ncplane_destroy(n->ip);
- n->ip = NULL;
- }
-}
-
-static bool layout_info(struct ui *u, struct info *n, struct ncplane *parent, s32 width, s32 height)
-{
- (void)width;
-
- maybe_destroy_info(n);
-
- n->max_props = (height - INFO_TILE_HEIGHT - 3) / 2;
- if (!n->max_props) return false;
-
- struct ncplane_options nopts = { 0 };
-
- nopts.cols = INFO_TILE_WIDTH;
- nopts.rows = INFO_TILE_HEIGHT;
- nopts.x = 0;
- nopts.y = 1;
-
- n->ip = ncplane_create(parent, &nopts);
- if (!n->ip) {
- maybe_destroy_info(n);
- return false;
- }
-
- set_info_entry(u, &u->n);
-
- return true;
-}
-
-static void maybe_destroy_grid(struct grid *g)
-{
- if (!g->p) return;
- struct tile *t;
- al_array_foreach_ptr(g->tiles, i, t) {
- ncplane_destroy(t->ip);
- t->ip = NULL;
- ncplane_destroy(t->p);
- t->p = NULL;
- }
- g->tiles.size = 0;
- ncplane_destroy(g->p);
- g->p = NULL;
-}
-
-static void set_grid_tiles(struct ui *u, struct grid *g)
-{
- u32 index = (g->selected_index - (g->selected_index % g->tiles_per_page));
-
- g->min_index = index;
- g->max_index = index;
-
- for (s32 y = 0; y < g->tiles_y; y++) {
- for (s32 x = 0; x < g->tiles_x; x++) {
- struct tile *t = &al_array_at(g->tiles, y * g->tiles_x + x);
- ncplane_erase(t->ip);
- t->blitted = false;
- ncplane_erase(t->p);
-
- if (g->max_index >= (s32)u->db.entries.size) {
- t->e = NULL;
- t->text.s = NULL;
- ncplane_erase(t->ip);
- continue;
- }
-
- t->e = &al_array_at(u->db.entries, g->max_index);
- if (!db_ensure_entry_loaded(&u->db, t->e)) {
- x--;
- continue;
- }
-
- s32 width = al_wstr_width(&t->e->title);
- s32 tx = 2 + ((width < TEXT_WIDTH) ? (TEXT_WIDTH - width) / 2 : 0);
- set_scrolling_text(&t->text, &t->e->title, width, TEXT_WIDTH, tx, TILE_HEIGHT - 1);
-
- g->max_index++;
- }}
-
- g->max_index--;
-}
-
-static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s32 width, s32 height)
-{
- maybe_destroy_grid(g);
-
- s32 inner_width = width;
- s32 inner_height = height;
-
- g->tiles_x = (s32)(width / TILE_WIDTH);
- g->tiles_y = (s32)(height / TILE_HEIGHT);
-
- if (!g->tiles_x || !g->tiles_y) {
- // Window is too small.
- return false;
- }
-
- g->tiles_per_page = g->tiles_x * g->tiles_y;
-
- // Allow 4 pages of tiles in the pool, but never shrink.
- if (u->pool.chunk_size < (u32)g->tiles_per_page) {
- u->pool.chunk_size = g->tiles_per_page;
- u->pool.max = u->pool.chunk_size * 4;
- al_array_reserve(u->pool.images, u->pool.max);
- }
-
- while (inner_width && inner_width % g->tiles_x != 0) {
- inner_width--;
- }
-
- while (inner_height && inner_height % g->tiles_y != 0) {
- inner_height--;
- }
-
- struct ncplane_options nopts = { 0 };
-
- nopts.cols = inner_width;
- nopts.rows = inner_height;
- nopts.x = (width - (g->tiles_x * TILE_WIDTH)) / 2;
- nopts.y = (height - (g->tiles_y * TILE_HEIGHT)) / 2;
-
- g->p = ncplane_create(parent, &nopts);
- if (!g->p) return false;
-
- for (s32 y = 0; y < g->tiles_y; y++) {
- for (s32 x = 0; x < g->tiles_x; x++) {
- struct tile t = { 0 };
-
- nopts.cols = TILE_WIDTH;
- nopts.rows = TILE_HEIGHT;
- nopts.x = x * TILE_WIDTH;
- nopts.y = y * TILE_HEIGHT;
-
- t.p = ncplane_create(g->p, &nopts);
- if (!t.p) {
- maybe_destroy_grid(g);
- return false;
- }
-
- nopts.cols = TILE_WIDTH - 2;
- nopts.rows = TILE_HEIGHT - 2;
- nopts.x = 1;
- nopts.y = 1;
-
- t.ip = ncplane_create(t.p, &nopts);
- if (!t.ip) {
- maybe_destroy_grid(g);
- return false;
- }
-
- al_array_push(g->tiles, t);
- }}
-
- set_grid_tiles(u, g);
-
- return true;
-}
-
-static void maybe_destroy_ui(struct ui *u)
-{
- if (u->ip) {
- ncplane_destroy(u->ip);
- u->ip = NULL;
- }
- if (u->gp) {
- ncplane_destroy(u->gp);
- u->gp = NULL;
- }
- if (u->mp) {
- ncplane_destroy(u->mp);
- u->mp = NULL;
- }
- if (u->p) {
- ncplane_destroy(u->p);
- u->p = NULL;
- }
-}
-
-bool layout_ui(struct ui *u)
-{
- bool even = u->term_cols % 2 == 0;
-
- u32 rwidth = (even) ? INFO_RES_WIDTH + 1 : INFO_RES_WIDTH;
- u32 rheight = (even) ? INFO_RES_HEIGHT + 1 : INFO_RES_HEIGHT;
-
- if (u->term_cols < TILE_WIDTH + rwidth || u->term_rows < TILE_HEIGHT + rheight) {
- return false;
- }
-
- maybe_destroy_ui(u);
-
- struct ncplane_options nopts = { 0 };
-
- nopts.rows = u->term_rows;
- nopts.cols = u->term_cols;
-
- u->p = ncplane_create(notcurses_stdplane(u->nc), &nopts);
- if (!u->p) {
- return false;
- }
-
- s32 cols = u->term_cols - rwidth;
- s32 rows = u->term_rows - rheight;
-
- if (cols < 0 || rows < 0) {
- maybe_destroy_ui(u);
- return false;
- }
-
- nopts.x = 1;
- nopts.y = 1;
- nopts.cols = cols - 1;
- nopts.rows = 1;
-
- u->mp = ncplane_create(u->p, &nopts);
- if (!u->mp) {
- maybe_destroy_ui(u);
- return false;
- }
-
- nopts.x = 1;
- nopts.y = 2;
- nopts.cols = cols - 1;
- nopts.rows = rows - 2;
-
- u->gp = ncplane_create(u->p, &nopts);
- if (!u->gp) {
- maybe_destroy_ui(u);
- return false;
- }
-
- if (!layout_grid(u, &u->g, u->gp, nopts.cols, nopts.rows)) {
- maybe_destroy_ui(u);
- return false;
- }
-
- nopts.x = nopts.cols + ((even) ? INFO_X_OFFSET + 1 : INFO_X_OFFSET);
- nopts.y = INFO_Y_OFFSET;
- nopts.cols = INFO_WIDTH;
- nopts.rows = u->term_rows - rheight;
-
- u->ip = ncplane_create(u->p, &nopts);
- if (!u->ip) {
- maybe_destroy_ui(u);
- return false;
- }
-
- if (!layout_info(u, &u->n, u->ip, nopts.cols, nopts.rows)) {
- maybe_destroy_ui(u);
- return false;
- }
-
- return true;
-}
-
-void draw_monitors(struct ui *u, struct monitors *m)
-{
- if (!u->layed_out) return;
-
- s32 width = 0;
- char *monitor;
- al_array_foreach(m->monitors, i, monitor) {
- width += strlen(monitor) + 1;
- }
-
- ncplane_set_styles(u->mp, NCSTYLE_BOLD);
- s32 w = ncplane_dim_x(u->mp);
- s32 x = ((w - 1) - width) / 2;
- ncplane_cursor_move_yx(u->mp, 0, x);
- al_array_foreach(m->monitors, i, monitor) {
- if (m->flash <= 0.f && u->selected_menu == MENU_MONITORS && i == (u32)m->selected_monitor) {
- ncplane_set_bg_rgb8(u->mp, 211, 184, 152);
- ncplane_set_fg_rgb8(u->mp, 13, 22, 45);
- } else if (i == (u32)m->set_monitor) {
- ncplane_set_bg_rgb8(u->mp, 242, 233, 215);
- ncplane_set_fg_rgb8(u->mp, 13, 22, 45);
- m->flash -= AL_MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f);
- } else {
- ncplane_set_fg_rgb8(u->mp, 0xff, 0xff, 0xff);
- ncplane_set_bg_palindex(u->mp, 0);
- }
- ncplane_cursor_move_rel(u->mp, 0, 1);
- ncplane_putstr(u->mp, monitor);
- }
-}
-
-void draw_info(struct ui *u, struct info *n, bool update_text)
-{
- if (!u->layed_out || !n->e) return;
-
- ncplane_erase(u->ip);
-
- struct ncvisual_options vopts = { 0 };
- vopts.scaling = NCSCALE_SCALE;
- vopts.blitter = PIXEL_BLIT ? NCBLIT_PIXEL : NCBLIT_2x1;
- struct image *img = get_image_from_pool(&u->pool, &n->e->preview_path);
- if (img && !n->blitted && !img->errored) {
- ncplane_erase(n->ip);
- img->errored = img->gif && ncvisual_decode_loop(img->v) < 0;
- u->delay = ncvisual_get_duration(img->v);
- if (!img->errored) {
- vopts.n = n->ip;
- ncvisual_blit(u->nc, img->v, &vopts);
- n->blitted = !img->gif;
- }
- }
-
- ncplane_set_fg_rgb8(u->ip, 0xff, 0xff, 0xff);
- draw_scrolling_text(u->ip, &n->text, update_text);
-
- u32 selected_index = n->selected_prop - (n->selected_prop % n->max_props);
- struct info_prop *ip = NULL;
- u32 i, index;
- for (i = 0; i < n->max_props; i++) {
- index = i + selected_index;
- if (index >= n->props.size) {
- break;
- }
- ip = &al_array_at(n->props, index);
- if (u->selected_menu == MENU_INFO && index == (u32)n->selected_prop) {
- ncplane_set_fg_rgb8(u->ip, 211, 184, 152);
- } else {
- ncplane_set_fg_rgb8(u->ip, 0xff, 0xff, 0xff);
- }
- draw_scrolling_text(u->ip, &ip->text, update_text);
- switch (ip->p->type) {
- case PROP_TYPE_BOOL:
- ncplane_putstr_yx(u->ip, ip->text.y, ip->text.width + 2,
- *((bool *)ip->p->value) ? "[x]" : "[ ]");
- break;
- case PROP_TYPE_SLIDER: {
- char c[16];
- sprintf(c, "%.2f", ((struct slider_value *)ip->p->value)->value);
- ncplane_putstr_yx(u->ip, ip->text.y, ip->text.width + 2, c);
- break;
- }
- }
- }
- if (ip && i == n->max_props && i != n->props.size) {
- ncplane_putstr_yx(u->ip, ip->text.y + 1, (INFO_TILE_WIDTH - 1) / 2, "V");
- }
-}
-
-static void set_highlighted_style(struct tile *t, s32 br, s32 bg, s32 bb, s32 fr, s32 fg, s32 fb)
-{
- ncplane_set_fg_rgb8(t->p, br, bg, bb);
- ncplane_set_bg_alpha(t->p, NCALPHA_TRANSPARENT);
- u64 c = 0;
- ncchannels_set_bg_rgb8(&c, br, bg, bb);
- ncchannels_set_fg_rgb8(&c, br, bg, bb);
- c = ncchannels_reverse(c);
- ncplane_gradient(t->p, 1, 0, TILE_HEIGHT - 1, TILE_WIDTH, "", 0, c, c, c, c);
- for (s32 i = 0; i < TILE_WIDTH; i++) {
- ncplane_putwc_yx(t->p, 0, i, L'▂');
- }
- ncplane_set_bg_rgb8(t->p, br, bg, bb);
- ncplane_set_fg_rgb8(t->p, fr, fg, fb);
- ncplane_set_bg_alpha(t->p, NCALPHA_OPAQUE);
- ncplane_set_styles(t->p, NCSTYLE_BOLD);
-}
-
-void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text)
-{
- if (!u->layed_out) return;
-
- struct ncvisual_options vopts = { 0 };
- vopts.scaling = NCSCALE_SCALE;
- vopts.blitter = PIXEL_BLIT ? NCBLIT_PIXEL : NCBLIT_2x1;
-
- s32 index = (g->selected_index - (g->selected_index % g->tiles_per_page));
-
- struct tile *t;
- al_array_foreach_ptr(g->tiles, i, t) {
- if (!t->e) continue;
- if (t->e->has_preview) {
- struct image *img = get_image_from_pool(&u->pool, &t->e->preview_path);
- if (img && !t->blitted && !img->errored) {
- vopts.n = t->ip;
- ncvisual_blit(u->nc, img->v, &vopts);
- t->blitted = true;
- }
- }
- if (g->flash <= 0.f && u->selected_menu == MENU_GRID && index == g->selected_index) {
- set_highlighted_style(t, 211, 184, 152, 13, 22, 45);
- } else if (g->set_index >= 0 && index == g->set_index) {
- set_highlighted_style(t, 242, 233, 215, 13, 22, 45);
- g->flash -= AL_MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f);
- } else {
- ncplane_erase(t->p);
- ncplane_set_fg_rgb8(t->p, 0xff, 0xff, 0xff);
- ncplane_set_bg_palindex(t->p, 0);
- ncplane_set_styles(t->p, 0);
- }
- if (t->text.s && t->text.s->len) {
- draw_scrolling_text(t->p, &t->text, update_text);
- }
- index++;
- }
-}
-
-static bool handle_input_monitors(struct ui *u, struct monitors *m, struct ncinput *input)
-{
- bool should_redraw_monitors = false;
- bool should_redraw_grid = false;
- bool should_redraw_info = false;
- switch (input->id) {
- case NCKEY_RIGHT:
- case 'l':
- if ((u32)(m->selected_monitor + 1) >= m->monitors.size) {
- return false;
- }
- m->selected_monitor++;
- should_redraw_monitors = true;
- break;
- case NCKEY_LEFT:
- case 'h':
- if (m->selected_monitor - 1 < 0) {
- return false;
- }
- m->selected_monitor--;
- should_redraw_monitors = true;
- break;
- case NCKEY_RETURN:
- select_monitor(u);
- m->flash = FLASH_DURATION;
- should_redraw_monitors = true;
- should_redraw_grid = true;
- should_redraw_info = true;
- break;
- }
- if (should_redraw_monitors) {
- draw_monitors(u, m);
- }
- if (should_redraw_grid) {
- set_grid_tiles(u, &u->g);
- draw_grid_tiles(u, &u->g, false);
- }
- if (should_redraw_info) {
- set_info_entry(u, &u->n);
- draw_info(u, &u->n, false);
- }
- return should_redraw_monitors || should_redraw_grid || should_redraw_info;
-}
-
-static bool handle_input_info(struct ui *u, struct info *n, struct ncinput *input)
-{
- bool should_redraw_info = false;
- switch (input->id) {
- case NCKEY_DOWN:
- case 'j':
- if ((u32)(n->selected_prop + 1) >= n->props.size) {
- return false;
- }
- n->selected_prop++;
- should_redraw_info = true;
- break;
- case NCKEY_UP:
- case 'k':
- if (n->selected_prop - 1 < 0) {
- return false;
- }
- n->selected_prop--;
- should_redraw_info = true;
- break;
- case NCKEY_RETURN:
- case NCKEY_SPACE: {
- if (!n->props.size) return false;
- struct prop *p = &al_array_at(n->e->props, n->selected_prop);
- if (p->type == PROP_TYPE_BOOL) {
- *((bool *)p->value) = !*((bool *)p->value);
- write_selected_entry_to_config(u);
- should_redraw_info = true;
- }
- break;
- }
- case NCKEY_LEFT:
- case 'h':
- case NCKEY_RIGHT:
- case 'l':
- if (!n->props.size) return false;
- struct prop *p = &al_array_at(n->e->props, n->selected_prop);
- if (p->type == PROP_TYPE_SLIDER) {
- struct slider_value *value = (struct slider_value *)p->value;
- if (input->id == NCKEY_RIGHT) {
- if (value->value == value->max) {
- break;
- }
- if (value->value + value->step <= value->max) {
- value->value += value->step;
- } else {
- value->value = value->max;
- }
- } else if (input->id == NCKEY_LEFT) {
- if (value->value == value->min) {
- break;
- }
- if (value->value - value->step >= value->min) {
- value->value -= value->step;
- } else {
- value->value = value->min;
- }
- }
- write_selected_entry_to_config(u);
- should_redraw_info = true;
- }
- break;
- }
- if (should_redraw_info) draw_info(u, n, false);
- return should_redraw_info;
-}
-
-static bool grid_next_page(struct ui *u, struct grid *g, s32 offset)
-{
- s32 max = (s32)u->db.entries.size - 1;
- if (g->selected_index == max) {
- return false;
- }
- g->selected_index += g->tiles_per_page - offset;
- if (g->selected_index > max) {
- g->selected_index = max;
- }
- return true;
-}
-
-static bool grid_previous_page(struct grid *g, s32 offset)
-{
- if (g->selected_index == 0) {
- return false;
- }
- g->selected_index -= g->tiles_per_page - offset;
- if (g->selected_index < 0) {
- g->selected_index = 0;
- }
- return true;
-}
-
-static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *input)
-{
- bool should_update_grid = false;
- bool should_redraw_grid = false;
- bool should_redraw_info = false;
- switch (input->id) {
- case NCKEY_RIGHT:
- case 'l':
- if (g->selected_index + 1 >= (s32)u->db.entries.size) {
- return false;
- }
- if ((g->selected_index + 1) % g->tiles_x == 0) {
- should_update_grid = grid_next_page(u, &u->g, g->tiles_x - 1);
- } else {
- g->selected_index++;
- }
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case NCKEY_LEFT:
- case 'h':
- if (g->selected_index - 1 < 0) {
- return false;
- }
- if (g->selected_index % g->tiles_x == 0) {
- should_update_grid = grid_previous_page(&u->g, g->tiles_x - 1);
- } else {
- g->selected_index--;
- }
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case NCKEY_DOWN:
- case 'j':
- if (g->selected_index + g->tiles_x > g->max_index) {
- return false;
- }
- g->selected_index += g->tiles_x;
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case NCKEY_UP:
- case 'k':
- if (g->selected_index - g->tiles_x < g->min_index) {
- return false;
- }
- g->selected_index -= g->tiles_x;
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case 'b':
- should_update_grid = grid_previous_page(&u->g, 0);
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case 'f':
- should_update_grid = grid_next_page(u, &u->g, 0);
- should_redraw_info = true;
- should_redraw_grid = true;
- break;
- case 'r':
- g->set_index = -1;
- write_selected_entry_to_config(u);
- should_redraw_grid = true;
- should_redraw_info = true;
- break;
- case NCKEY_RETURN:
- case NCKEY_SPACE: {
- if (g->selected_index >= 0) {
- g->set_index = g->selected_index;
- g->flash = FLASH_DURATION;
- }
- write_selected_entry_to_config(u);
- should_redraw_grid = true;
- should_redraw_info = true;
- break;
- }
- }
- if (should_update_grid) {
- set_grid_tiles(u, g);
- }
- if (should_redraw_grid) {
- draw_grid_tiles(u, g, false);
- }
- if (should_redraw_info) {
- set_info_entry(u, &u->n);
- draw_info(u, &u->n, false);
- }
- return should_redraw_grid || should_redraw_info;
-}
-
-static void input_poll_callback(void *userdata, s32 revents)
-{
- struct ui *u = (struct ui *)userdata;
- (void)revents;
- struct ncinput input;
- bool should_render = false;
- u32 ret;
- while (1) {
- ret = notcurses_get_nblock(u->nc, &input);
- if (ret == (u32)-1 || ret == 0) {
- break;
- }
- if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) {
- if (input.id == NCKEY_TAB) {
- switch (u->selected_menu) {
- case MENU_GRID:
- if (u->n.props.size > 0) {
- u->selected_menu = MENU_INFO;
- } else {
- u->selected_menu = MENU_MONITORS;
- }
- break;
- case MENU_INFO:
- if (u->m.selected_monitor >= 0) {
- u->selected_menu = MENU_MONITORS;
- } else {
- u->selected_menu = MENU_GRID;
- }
- break;
- case MENU_MONITORS:
- u->selected_menu = MENU_GRID;
- break;
- }
- draw_info(u, &u->n, false);
- draw_monitors(u, &u->m);
- draw_grid_tiles(u, &u->g, false);
- should_render = true;
- } else if (input.id == 'q') {
- aki_event_loop_break(&u->loop);
- }
- switch (u->selected_menu) {
- case MENU_GRID:
- should_render |= handle_input_grid(u, &u->g, &input);
- break;
- case MENU_INFO:
- should_render |= handle_input_info(u, &u->n, &input);
- break;
- case MENU_MONITORS:
- should_render |= handle_input_monitors(u, &u->m, &input);
- break;
- }
- }
- }
- if (should_render) notcurses_render(u->nc);
-}
-
-static void timer_callback(void *userdata, struct aki_timer *timer)
-{
- struct ui *u = (struct ui *)userdata;
- (void)timer;
-
- if (!u->layed_out) {
- notcurses_render(u->nc);
- return;
- }
-
- if (u->text_update <= 0.f) {
- u->text_update = TEXT_UPDATE_RATE;
- draw_grid_tiles(u, &u->g, true);
- draw_info(u, &u->n, true);
- } else {
- u->text_update -= AL_MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f);
- draw_grid_tiles(u, &u->g, false);
- draw_info(u, &u->n, false);
- }
-
- draw_monitors(u, &u->m);
-
- notcurses_render(u->nc);
-
- aki_timer_set_repeat(&u->timer, AKI_TS_FROM_USEC(u->delay));
- aki_timer_again(&u->timer);
-}
-
-static struct ui u = { 0 };
-
-s32 main(void)
-{
- if (!aki_common_init()) return EXIT_FAILURE;
-
- s32 ret = EXIT_FAILURE;
-
- if (!open_config(&u.conf)) goto out;
-
- if (!db_load_entries(&u.db, &u.conf.wallpaper_dir)) {
- close_config(&u.conf);
- goto out;
- }
-
- if (!init_ui(&u)) {
- close_config(&u.conf);
- goto out;
- }
-
- notcurses_stddim_yx(u.nc, &u.term_rows, &u.term_cols);
-
- ncplane_set_resizecb(notcurses_stdplane(u.nc), resize_cb);
- ncplane_set_userptr(notcurses_stdplane(u.nc), &u);
-
- if (!layout_ui(&u)) {
- notcurses_stop(u.nc);
- close_config(&u.conf);
- al_log_error("taro", "failed to layout ui, your terminal window is probably too small");
- goto out;
- }
- u.layed_out = true;
- draw_monitors(&u, &u.m);
-
- aki_event_loop_init(&u.loop);
-
- aki_poll_init(&u.input_poll, input_poll_callback, &u);
- aki_poll_set(&u.input_poll, notcurses_inputready_fd(u.nc), AKI_POLL_READ);
- aki_poll_start(&u.input_poll, &u.loop);
-
- aki_timer_init(&u.timer, &u.loop, timer_callback, &u);
- u.delay = 1000000; // 1fps.
- timer_callback(&u, NULL);
-
- aki_event_loop_run(&u.loop);
-
- notcurses_stop(u.nc);
-
- close_config(&u.conf);
-
- ret = EXIT_SUCCESS;
-
-out:
- aki_common_close();
-
- return ret;
-}