From 04e2c64a5efdc55832e3a57fa2abfff319279f5c Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 31 Mar 2025 20:02:07 -0400 Subject: Update deps and TUI fixes Signed-off-by: Andrew Opalach --- taro/config.h | 57 ++++++-- taro/daemon.c | 274 +++++++++++++++++++---------------- taro/db.c | 255 +++++++++++++++++---------------- taro/db.h | 5 +- taro/file_ext.c | 9 +- taro/meson.build | 4 +- taro/ui.c | 428 +++++++++++++++++++++++++++++++------------------------ 7 files changed, 579 insertions(+), 453 deletions(-) (limited to 'taro') diff --git a/taro/config.h b/taro/config.h index 0357d70..16fad79 100644 --- a/taro/config.h +++ b/taro/config.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include "file_ext.h" @@ -11,18 +12,22 @@ struct config { str config_dir; str config_path; str asset_dir; - str wallpaper_dir; + array(str) wallpaper_dirs; bool valid; }; -AL_UNUSED_FUNCTION_PUSH +AL_IGNORE_WARNING("-Wunused-function") static void close_config(struct config *conf) { al_str_free(&conf->config_dir); al_str_free(&conf->config_path); al_str_free(&conf->asset_dir); - al_str_free(&conf->wallpaper_dir); + str *dir; + al_array_foreach_ptr(conf->wallpaper_dirs, i, dir) { + al_str_free(dir); + } + al_array_free(conf->wallpaper_dirs); } static void maybe_replace_home_in_path(str *dest, const char *envhome, const char *path) @@ -30,11 +35,11 @@ static void maybe_replace_home_in_path(str *dest, const char *envhome, const cha if (path[0] == '~') { al_str_from(dest, envhome); path++; - al_str_cat(dest, al_str_cr(path)); - } else if (al_str_cmp(al_str_cr(path), al_str_c("$HOME"), 0, 5) == 0) { + al_str_cat(dest, &al_str_cr(path)); + } else if (al_str_cmp(&al_str_cr(path), &al_str_c("$HOME"), 0, 5) == 0) { al_str_from(dest, envhome); path += 5; - al_str_cat(dest, al_str_cr(path)); + al_str_cat(dest, &al_str_cr(path)); } else { al_str_from(dest, path); } @@ -52,20 +57,40 @@ static void parse_config_json(struct config *conf, char *envhome, json_t *root) json_t *assets = json_object_get(directories, "assets"); if (!assets || !json_is_string(assets) || al_strlen(json_string_value(assets)) == 0) { - al_log_error("taro", "asset directory not set or invalid"); + error("asset directory not set or invalid"); conf->valid = false; goto out; } json_t *wallpapers = json_object_get(directories, "wallpapers"); - if (!wallpapers || !json_is_string(wallpapers) || al_strlen(json_string_value(wallpapers)) == 0) { - al_log_error("taro", "wallpaper directory not set or invalid"); + if (!wallpapers) { + conf->valid = false; + goto out; + } + size_t index; + json_t *value; + json_array_foreach(wallpapers, index, value) { + if (!json_is_string(value) || al_strlen(json_string_value(value)) == 0) { + error("invalid wallpaper directory (index: %zu)", index); + continue; + } + str path; + maybe_replace_home_in_path(&path, envhome, json_string_value(value)); + if (!nn_dir_exists(&path)) { + warn("directory does not exist %.*s", al_str_x(&path)); + al_str_free(&path); + continue; + } + al_array_push(conf->wallpaper_dirs, path); + } + + if (conf->wallpaper_dirs.count == 0) { + error("no valid wallpaper directories"); conf->valid = false; goto out; } maybe_replace_home_in_path(&conf->asset_dir, envhome, json_string_value(assets)); - maybe_replace_home_in_path(&conf->wallpaper_dir, envhome, json_string_value(wallpapers)); out: json_decref(root); @@ -79,7 +104,7 @@ static bool open_config(struct config *conf, bool create) if (!envhome) goto err; al_str_from(&conf->config_dir, envhome); - al_str_cat(&conf->config_dir, al_str_c(CONFIG_PATH)); + al_str_cat(&conf->config_dir, &al_str_c(CONFIG_PATH)); if (!nn_dir_exists(&conf->config_dir)) { if (!create || !nn_dir_create(&conf->config_dir)) { goto err; @@ -87,13 +112,13 @@ static bool open_config(struct config *conf, bool create) } al_str_clone(&conf->config_path, &conf->config_dir); - al_str_cat(&conf->config_path, al_str_c("/config.json")); + al_str_cat(&conf->config_path, &al_str_c("/config.json")); if (!nn_file_exists(&conf->config_path)) { if (nn_file_create(&conf->config_path)) { json_t *blank = json_object(); json_t *directories = json_object(); json_object_set_new(directories, "assets", json_string_nocheck("")); - json_object_set_new(directories, "wallpapers", json_string_nocheck("")); + json_object_set_new(directories, "wallpapers", json_array()); json_object_set_new(blank, "directories", directories); nn_dump_json_and_decref(&conf->config_path, blank); } @@ -102,6 +127,8 @@ static bool open_config(struct config *conf, bool create) json_t *root = nn_file_open_as_json(&conf->config_path); if (!root) goto err; + al_array_init(conf->wallpaper_dirs); + parse_config_json(conf, envhome, root); return true; @@ -110,4 +137,4 @@ err: return false; } -AL_UNUSED_FUNCTION_POP +AL_IGNORE_WARNING_END diff --git a/taro/daemon.c b/taro/daemon.c index 6641b0b..4a70ceb 100644 --- a/taro/daemon.c +++ b/taro/daemon.c @@ -1,7 +1,6 @@ -#include -#include -#include +#define AL_LOG_SECTION "tarod" #include +#include #include #include #include @@ -10,6 +9,7 @@ #include #include #include +#include #include "file_ext.h" #include "config.h" @@ -54,26 +54,24 @@ struct daemon { struct nn_fs_event conf_event; }; -static s32 parse_and_set_props(str *selection, str *wallpaper_path, bool *is_package) +static s32 parse_and_set_props(str *selection, str *wallpaper_id, bool *is_package) { - s32 index = al_str_find(selection, '|'); - s32 rindex = al_str_rfind(selection, '|'); - if (index == -1 || rindex == -1 || index == rindex) { + u32 index = al_str_find(selection, '|'); + u32 rindex = al_str_rfind(selection, '|'); + if (index == AL_STR_NPOS || rindex == AL_STR_NPOS || index == rindex) { return -1; } - // index < 2 or id->len == 0 is expected behavior - // when a monitor has no wallpaper set. + // index < 2 or id->length == 0 is expected 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; + str id = al_str_substr(selection, 0, index - 2); + str type = al_str_substr(selection, index - 2, index); + if (id.length == 0) return 1; - al_str_cat(wallpaper_path, id); - - if (al_str_eq(type, al_str_c("_p"))) { + al_str_clone(wallpaper_id, &id); + if (al_str_eq(&type, &al_str_c("_p"))) { *is_package = true; - } else if (al_str_eq(type, al_str_c("_d"))) { + } else if (al_str_eq(&type, &al_str_c("_d"))) { *is_package = false; } else { return -1; @@ -85,7 +83,7 @@ static s32 parse_and_set_props(str *selection, str *wallpaper_path, bool *is_pac 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, nn_strerror(errno)); + error("kill(%d) failed (%s)", pid, nn_strerror(errno)); return false; } return true; @@ -105,11 +103,11 @@ 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..."); + error("fatal: failed to fork process, exiting..."); exit(EXIT_FAILURE); } else if (pid == 0) { if (execvp(args[0], args) == -1) { - al_log_error("tarod", "fatal: failed to run '%s', exiting...", args[0]); + error("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); @@ -131,12 +129,34 @@ static void maybe_kill_wallpaper(struct monitor *monitor) } } +static bool search_wallpaper_dirs(struct daemon *dmon, str *id, str *path) +{ + bool found = false; + debug("searching for wallpaper of id %.*s", al_str_x(id)); + str *dir; + al_array_foreach_ptr(dmon->conf.wallpaper_dirs, i, dir) { + debug("checking dir %.*s", al_str_x(dir)); + al_str_clone(path, dir); + if (al_str_at(path, 1) != '/') al_str_cat(path, &al_str_c("/")); + al_str_cat(path, id); + if (nn_dir_exists(path)) { + debug("found wallpaper"); + found = true; + break; + } + al_str_free(path); + } + if (!found) error("failed to find wallpaper of id %.*s", al_str_x(id)); + al_str_free(id); + return found; +} + static bool run_wallpaper(struct daemon *dmon, struct monitor *monitor) { maybe_kill_wallpaper(monitor); if (!dmon->conf.valid) { - al_log_error("tarod", "config invalid, not setting wallpaper on %s", monitor->os->name); + error("config invalid, not setting wallpaper on %s", monitor->os->name); return false; } @@ -147,37 +167,42 @@ static bool run_wallpaper(struct daemon *dmon, struct monitor *monitor) return false; } - if (selection.len == 0) { - al_log_error("tarod", "config for %s is empty", monitor->os->name); + if (selection.length == 0) { + error("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("/")); + str wallpaper_id = al_str_null(); bool is_package; - s32 ret = parse_and_set_props(&selection, &wallpaper_path, &is_package); + s32 ret = parse_and_set_props(&selection, &wallpaper_id, &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); + error("failed to set properties for wallpaper on %s", monitor->os->name); } - al_str_free(&wallpaper_path); + al_str_free(&wallpaper_id); return false; } - if (is_package) { - al_str_cat(&wallpaper_path, al_str_c("/scene.pkg")); + str wallpaper_path = al_str_null(); + if (!search_wallpaper_dirs(dmon, &wallpaper_id, &wallpaper_path)) { + return false; } - if ((is_package && !nn_file_exists(&wallpaper_path)) || (!is_package && !nn_dir_exists(&wallpaper_path))) { - al_log_error("tarod", "wallpaper doesn't exist at %.*s", AL_STR_PRINTF(&wallpaper_path)); - al_str_free(&wallpaper_path); - return false; + if (is_package) { + al_str_cat(&wallpaper_path, &al_str_c("/scene.pkg")); + if (!nn_file_exists(&wallpaper_path)) { + al_str_insert(&wallpaper_path, wallpaper_path.length - 9, &al_str_c("gif")); + if (!nn_file_exists(&wallpaper_path)) { + error("wallpaper at %.*s has no package file", al_str_x(&wallpaper_path)); + al_str_free(&wallpaper_path); + return false; + } + } } - al_log_info("tarod", "setting wallpaper %.*s on %s", AL_STR_PRINTF(&wallpaper_path), monitor->os->name); + info("setting wallpaper %.*s on %s", al_str_x(&wallpaper_path), monitor->os->name); char width[8], height[8]; al_snprintf(width, sizeof(width), "%d", monitor->os->width / monitor->os->scale); @@ -187,10 +212,10 @@ static bool run_wallpaper(struct daemon *dmon, struct monitor *monitor) char *c_str1 = al_str_to_c_str(&dmon->conf.asset_dir); if (is_package) { monitor->pid = exec_or_exit((char *[]){ - "mauri", "-p", c_str0, "-a", c_str1, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); + "./mauri", "-p", c_str0, "-a", c_str1, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); } else { monitor->pid = exec_or_exit((char *[]){ - "mauri", "-d", c_str0, "-a", c_str1, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); + "./mauri", "-d", c_str0, "-a", c_str1, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); } al_str_free(&wallpaper_path); al_free(c_str0); @@ -215,43 +240,21 @@ static void selection_fs_event_callback(void *userdata, struct inotify_event *ev run_wallpaper(dmon, monitor); } -static void output_discovered_callback(void *userdata, struct stl_monitor *os_monitor) +static void create_monitor(struct daemon *dmon, str *dir, 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 monitor_dir; - al_str_clone(&monitor_dir, &dmon->conf.config_dir); - al_str_cat(&monitor_dir, al_str_c("/")); - al_str_cat(&monitor_dir, al_str_cr(os_monitor->name)); - - if (!nn_dir_exists(&monitor_dir)) { - if (!nn_dir_create(&monitor_dir)) return; - } - - monitor = al_alloc_object(struct monitor); + struct monitor *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, &monitor_dir); - al_str_free(&monitor_dir); + al_str_clone(&monitor->config_dir, dir); al_str_clone(&monitor->selection_path, &monitor->config_dir); - al_str_cat(&monitor->selection_path, al_str_c("/selection")); + al_str_cat(&monitor->selection_path, &al_str_c("/selection")); if (!nn_file_exists(&monitor->selection_path)) { nn_file_create(&monitor->selection_path); - nn_file_open_and_replace(&monitor->selection_path, al_str_c("||"), false); + nn_file_open_and_replace(&monitor->selection_path, &al_str_c("||"), false); } monitor->pid = -1; @@ -261,11 +264,36 @@ static void output_discovered_callback(void *userdata, struct stl_monitor *os_mo run_wallpaper(dmon, monitor); monitor->self_event_skip = false; - nn_fs_event_init(&monitor->selection_event, &monitor->selection_path, - NNWT_CLOSE_WRITE, selection_fs_event_callback, monitor); + nn_fs_event_init(&monitor->selection_event, &monitor->selection_path, NNWT_CLOSE_WRITE, selection_fs_event_callback, monitor); nn_fs_event_start(&monitor->selection_event, &monitor->dmon->loop); } +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 monitor_dir; + al_str_clone(&monitor_dir, &dmon->conf.config_dir); + al_str_cat(&monitor_dir, &al_str_c("/")); + al_str_cat(&monitor_dir, &al_str_cr(os_monitor->name)); + + if (nn_dir_exists(&monitor_dir) || nn_dir_create(&monitor_dir)) { + create_monitor(dmon, &monitor_dir, os_monitor); + } + + al_str_free(&monitor_dir); +} + // https://vi.stackexchange.com/questions/25030/vim-not-firing-inotify-events-when-writing-file static void conf_fs_event_callback(void *userdata, struct inotify_event *event) { @@ -273,7 +301,7 @@ static void conf_fs_event_callback(void *userdata, struct inotify_event *event) (void)event; close_config(&dmon->conf); if (!open_config(&dmon->conf, false)) { - al_log_error("tarod", "config became unavailable, exiting..."); + error("config became unavailable, exiting..."); exit(EXIT_FAILURE); } struct monitor *monitor; @@ -292,20 +320,20 @@ static void close_monitor(struct monitor *monitor) 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"); + info("successfully subscribed to sway background covered events"); } else { - al_log_info("tarod", "failed to subscribe to sway background covered events"); + info("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); + debug("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); + error("failed to parse sway-ipc json payload (%s)", error.text); goto done; } @@ -322,7 +350,7 @@ static void ipc_handle_response(struct daemon *dmon, u8 *buf, u32 size) 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 (monitor->pid == -1) break; if (covered && !monitor->paused && signal_monitor(monitor, SIGSTOP)) { monitor->paused = true; } else if (!covered && monitor->paused && signal_monitor(monitor, SIGCONT)) { @@ -336,6 +364,25 @@ done: if (root) json_decref(root); } +static bool ipc_prepare_payload(struct daemon *dmon) +{ + json_t *payload = json_array(); + json_array_append_new(payload, json_string_nocheck("background_covered")); + size_t length = json_dumpb(payload, NULL, 0, 0); + if (length == 0) return false; + size_t bufsize = SWAY_IPC_HEADER_LENGTH + length; + dmon->out.buf = al_malloc(bufsize); + al_memcpy(dmon->out.buf, "i3-ipc", al_strlen("i3-ipc")); + al_memcpy(dmon->out.buf + 6, &length, sizeof(s32)); + s32 type = SWAY_IPC_SUBSCRIBE; + al_memcpy(dmon->out.buf + 10, &type, sizeof(s32)); + length = json_dumpb(payload, (char *)dmon->out.buf + SWAY_IPC_HEADER_LENGTH, bufsize - SWAY_IPC_HEADER_LENGTH, 0); + json_decref(payload); + dmon->out.size = bufsize; + dmon->out.index = 0; + return true; +} + static void ensure_ipc_buf_size(struct daemon *dmon, ssize_t size) { if (dmon->ipc.alloc < size) { @@ -351,11 +398,10 @@ static void ipc_poll_callback(void *userdata, s32 revents) if (revents & NNWT_POLL_WRITE) { al_assert(!dmon->ipc_connected); if (!dmon->ipc_connected) { - al_log_info("tarod", "connected to sway ipc socket"); + info("connected to sway ipc socket"); dmon->ipc_connected = true; } - ssize_t ret = nn_socket_write(&dmon->ipc_socket, - dmon->out.buf + dmon->out.index, dmon->out.size - dmon->out.index); + ssize_t ret = nn_socket_write(&dmon->ipc_socket, dmon->out.buf + dmon->out.index, dmon->out.size - dmon->out.index); if (ret <= 0) { nn_signal_send(&dmon->quit_signal); return; @@ -370,29 +416,26 @@ static void ipc_poll_callback(void *userdata, s32 revents) if (revents & NNWT_POLL_READ) { if (dmon->ipc.index < SWAY_IPC_HEADER_LENGTH) { - ssize_t ret = nn_socket_read(&dmon->ipc_socket, - dmon->ipc.buf + dmon->ipc.index, SWAY_IPC_HEADER_LENGTH - dmon->ipc.index); + ssize_t ret = nn_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index, SWAY_IPC_HEADER_LENGTH - dmon->ipc.index); if (ret <= 0) { nn_signal_send(&dmon->quit_signal); return; } dmon->ipc.index += ret; } - if (dmon->ipc.index == SWAY_IPC_HEADER_LENGTH && !dmon->ipc.size) { - dmon->ipc.size = *((s32 *)&dmon->ipc.buf[6]) + SWAY_IPC_HEADER_LENGTH; + if (!dmon->ipc.size && dmon->ipc.index == SWAY_IPC_HEADER_LENGTH) { + dmon->ipc.size = *(s32 *)(dmon->ipc.buf + 6) + SWAY_IPC_HEADER_LENGTH; ensure_ipc_buf_size(dmon, dmon->ipc.size); } if (dmon->ipc.size) { - ssize_t ret = nn_socket_read(&dmon->ipc_socket, - dmon->ipc.buf + dmon->ipc.index, dmon->ipc.size - dmon->ipc.index); + ssize_t ret = nn_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index, dmon->ipc.size - dmon->ipc.index); if (ret <= 0) { nn_signal_send(&dmon->quit_signal); return; } dmon->ipc.index += ret; if (dmon->ipc.index == dmon->ipc.size) { - ipc_handle_response(dmon, dmon->ipc.buf + SWAY_IPC_HEADER_LENGTH, - dmon->ipc.size - SWAY_IPC_HEADER_LENGTH); + ipc_handle_response(dmon, dmon->ipc.buf + SWAY_IPC_HEADER_LENGTH, dmon->ipc.size - SWAY_IPC_HEADER_LENGTH); dmon->ipc.size = 0; dmon->ipc.index = 0; } @@ -413,15 +456,10 @@ static void quit_signal_callback(void *userdata) struct monitor *monitor; al_array_foreach(dmon->monitors, i, monitor) { nn_fs_event_stop(&monitor->selection_event); - maybe_kill_wallpaper(monitor); } nn_fs_event_stop(&dmon->conf_event); - if (!dmon->ipc_errored) { - nn_poll_stop(&dmon->ipc_poll); - } - if (dmon->context_poll_fd != -1) { - nn_poll_stop(&dmon->context_poll); - } + if (!dmon->ipc_errored) nn_poll_stop(&dmon->ipc_poll); + if (dmon->context_poll_fd != -1) nn_poll_stop(&dmon->context_poll); nn_signal_stop(&dmon->quit_signal); } @@ -444,7 +482,9 @@ static void sigint_handler(s32 signum) s32 main(void) { - if (!nn_common_init() || !open_config(&dmon.conf, true)) return EXIT_FAILURE; + if (!nn_common_init() || !open_config(&dmon.conf, true)) { + return EXIT_FAILURE; + } signal(SIGINT, sigint_handler); signal(SIGCHLD, sigchld_handler); @@ -470,13 +510,13 @@ s32 main(void) 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")); + 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")); + al_str_cat(&monitors_path, &al_str_c("/monitors")); bool monitors_exists = nn_file_exists(&monitors_path) || nn_file_create(&monitors_path); if (monitors_exists) { nn_file_open_and_replace(&monitors_path, &monitors, false); @@ -490,48 +530,27 @@ s32 main(void) dmon.ipc.index = 0; dmon.ipc.alloc = 0; ensure_ipc_buf_size(&dmon, SWAY_IPC_HEADER_LENGTH); - - size_t bufsize = SWAY_IPC_HEADER_LENGTH + 64; - dmon.out.buf = al_malloc(bufsize); - al_memcpy(dmon.out.buf, "i3-ipc", al_strlen("i3-ipc")); - s32 type = SWAY_IPC_SUBSCRIBE; - al_memcpy(&dmon.out.buf[10], &type, sizeof(s32)); - - json_t *payload = json_array(); - json_array_append_new(payload, json_string_nocheck("background_covered")); - s32 len = json_dumpb(payload, (char *)&dmon.out.buf[SWAY_IPC_HEADER_LENGTH], - bufsize - SWAY_IPC_HEADER_LENGTH, 0); - json_decref(payload); - al_memcpy(&dmon.out.buf[6], &len, sizeof(s32)); - - dmon.out.size = SWAY_IPC_HEADER_LENGTH + len; - dmon.out.index = 0; - + ipc_prepare_payload(&dmon); + dmon.ipc_connected = false; + dmon.ipc_errored = false; dmon.ipc_socket.type = NNWT_SOCKET_UNIX; nn_socket_init(&dmon.ipc_socket, NNWT_SOCKET_NONBLOCKING); nn_poll_init(&dmon.ipc_poll, ipc_poll_callback, &dmon); nn_poll_set(&dmon.ipc_poll, nn_socket_get_fd(&dmon.ipc_socket), NNWT_POLL_WRITE); - dmon.ipc_connected = false; - dmon.ipc_errored = false; char *swaysock = getenv("SWAYSOCK"); - if (swaysock && nn_socket_connect(&dmon.ipc_socket, al_str_cr(swaysock), 0)) { + if (swaysock && nn_socket_connect(&dmon.ipc_socket, &al_str_cr(swaysock), 0)) { nn_poll_start(&dmon.ipc_poll, &dmon.loop); } else { dmon.ipc_errored = true; - al_log_warn("tarod", "couldn't connect to sway ipc socket"); + warn("couldn't connect to sway ipc socket"); } - nn_fs_event_init(&dmon.conf_event, &dmon.conf.config_path, - NNWT_CLOSE_WRITE, conf_fs_event_callback, &dmon); + nn_fs_event_init(&dmon.conf_event, &dmon.conf.config_path, NNWT_CLOSE_WRITE, conf_fs_event_callback, &dmon); nn_fs_event_start(&dmon.conf_event, &dmon.loop); nn_event_loop_run(&dmon.loop); - al_array_foreach(dmon.monitors, i, monitor) { - close_monitor(monitor); - al_free(monitor); - } - al_array_free(dmon.monitors); + ret = EXIT_SUCCESS; nn_fs_event_free(&dmon.conf_event); @@ -539,11 +558,16 @@ s32 main(void) al_free(dmon.ipc.buf); nn_socket_close(&dmon.ipc_socket); - nn_event_loop_destroy(&dmon.loop); +out: + al_array_foreach(dmon.monitors, i, monitor) { + maybe_kill_wallpaper(monitor); + close_monitor(monitor); + al_free(monitor); + } + al_array_free(dmon.monitors); - ret = EXIT_SUCCESS; + nn_event_loop_destroy(&dmon.loop); -out: close_config(&dmon.conf); stl_global_close(); diff --git a/taro/db.c b/taro/db.c index f3e5ede..da9af8a 100644 --- a/taro/db.c +++ b/taro/db.c @@ -1,3 +1,4 @@ +#define AL_LOG_SECTION "db" #include #include "db.h" @@ -9,125 +10,122 @@ static bool load_entry(struct entry *e) { struct nn_dir dir; - if (!nn_dir_open(&dir, &e->path)) return false; + if (!nn_dir_open(&dir, &e->path)) { + return false; + } - struct nn_dir_entry entry; - while (nn_dir_read(&dir, &entry)) { - if (entry.entry->d_type != DT_REG) { - continue; - } + 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_c("project.json")); - s32 index = al_str_rfind(al_str_cr(entry.entry->d_name), '.'); - if (index == -1) { - continue; - } + json_t *root = nn_file_open_as_json(&project_path); + al_str_free(&project_path); + if (!root) { + return false; + } - 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 = nn_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 = nn_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)); - } + e->loaded = true; - 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 = nn_file_exists(&e->preview_path); - } + str package_path; + al_str_clone(&package_path, &e->path); + al_str_cat(&package_path, &al_str_c("/scene.pkg")); + e->has_package = nn_file_exists(&package_path); + if (!e->has_package) { + al_str_insert(&package_path, package_path.length - 9, &al_str_c("gif")); + e->has_package = nn_file_exists(&package_path); + } + al_str_free(&package_path); - json_t *general = json_object_get(root, "general"); - if (!general) { - continue; - } + json_t *title = json_object_get(root, "title"); + if (title) { + al_wstr_from_cstr(&e->title, (char *)json_string_value(title)); + } else { + al_wstr_from_cstr(&e->title, ""); + } - 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) { + 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 = nn_file_exists(&e->preview_path); + } else { + e->has_preview = false; + } + + json_t *general = json_object_get(root, "general"); + if (general) { + 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 = { 0 }; + 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. + //warn("improperly handled prop (%.*s)", al_str_x(&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) { + warn("improperly handled bool (%.*s)", al_str_x(&e->id)); 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)); + 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) { + warn("improperly handled slider (%.*s)", al_str_x(&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; + 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 { - //al_log_warn("db", "unhandled type. (%.*s)", AL_STR_PRINTF(type_str)); + // Assume the default step is 1.0. + slider->step = 1.0; } + p.type = PROP_TYPE_SLIDER; + } else { + //warn("unhandled type. (%.*s)", al_str_x(type_str)); + } - al_array_push(e->props, p); + al_array_push(e->props, p); - iter = json_object_iter_next(properties, iter); - } + iter = json_object_iter_next(properties, iter); } } } - nn_dir_close(&dir); + json_decref(root); return e->loaded; } @@ -143,18 +141,24 @@ bool db_ensure_entry_loaded(struct db *db, struct entry *e) break; } } + return false; } - return e->loaded; + al_assert(e->loaded); + return true; +} + +void db_init(struct db *db) +{ + al_array_init(db->entries); } static s32 wallpaper_id_compare(const void *a, const void *b) { - // Non-integer ids will be at the end. - 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; - return 0; + s64 ac = ((struct entry *)a)->compare; + s64 bc = ((struct entry *)b)->compare; + if (ac > bc) return 1; + else if (bc > ac) return -1; + else return 0; } bool db_load_entries(struct db *db, str *path) @@ -162,39 +166,50 @@ bool db_load_entries(struct db *db, str *path) struct nn_dir dir; if (!nn_dir_open(&dir, path)) return false; - al_array_init(db->entries); struct nn_dir_entry entry; while (nn_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) { + if (!(entry.entry->d_type == DT_DIR || entry.entry->d_type == DT_UNKNOWN) || + (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 (!nn_dir_exists(&e.path)) { + nn_dir_entry_get_name(&entry, &e.id); + bool dupe = false; + struct entry *re; + al_array_foreach_ptr(db->entries, i, re) { + if (al_str_eq(&re->id, &e.id)) { + dupe = true; + break; + } + } + if (dupe) { al_str_free(&e.id); - al_str_free(&e.path); continue; } + nn_dir_entry_get_path(&entry, &dir, &e.path); + bool error; + e.compare = al_str_to_long(&e.id, 10, &error); + if (error) e.compare = -1; + e.loaded = false; al_array_push(db->entries, e); } - al_array_sort(db->entries, struct entry, wallpaper_id_compare); + nn_dir_close(&dir); return true; } +void db_sort(struct db *db) +{ + al_array_sort(db->entries, struct entry, wallpaper_id_compare); +} + void db_unload(struct db *db) { struct entry *e; al_array_foreach_ptr(db->entries, i, e) { + al_str_free(&e->id); al_str_free(&e->path); al_str_free(&e->preview_path); al_wstr_free(&e->title); diff --git a/taro/db.h b/taro/db.h index 78f4c77..abc0d03 100644 --- a/taro/db.h +++ b/taro/db.h @@ -22,9 +22,10 @@ struct prop { }; struct entry { - bool loaded; str id; str path; + s64 compare; + bool loaded; bool has_package; str preview_path; bool has_preview; @@ -36,6 +37,8 @@ struct db { array(struct entry) entries; }; +void db_init(struct db *db); bool db_load_entries(struct db *db, str *path); +void db_sort(struct db *db); bool db_ensure_entry_loaded(struct db *db, struct entry *e); void db_unload(struct db *db); diff --git a/taro/file_ext.c b/taro/file_ext.c index 1d1bbdc..78fdba8 100644 --- a/taro/file_ext.c +++ b/taro/file_ext.c @@ -1,3 +1,4 @@ +#define AL_LOG_SECTION "file_ext" #include #include "file_ext.h" @@ -31,9 +32,9 @@ json_t *nn_file_open_as_json(str *path) return NULL; } json_error_t error; - json_t *root = json_loadb(s.data, s.len, 0, &error); + json_t *root = json_loadb(s.data, s.length, 0, &error); if (!root) { - al_log_error("file_ext", "json_loadb(%.*s, %u) failed (%s)", AL_STR_PRINTF(path), s.len, error.text); + error("json_loadb(%.*s, %u) failed (%s)", al_str_x(path), s.length, error.text); } al_str_free(&s); return root; @@ -44,8 +45,8 @@ bool nn_dump_json_and_decref(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) { + s.length = json_dumpb(root, s.data, size, JSON_INDENT(4)); + if (s.length == 0) { json_decref(root); return false; } diff --git a/taro/meson.build b/taro/meson.build index 487c671..ffee872 100644 --- a/taro/meson.build +++ b/taro/meson.build @@ -4,9 +4,11 @@ if not notcurses.found() or not notcurses_core.found() notcurses_opts = cmake.subproject_options() notcurses_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : 'Release' }) notcurses_opts.add_cmake_defines({ 'USE_CXX': false }) + notcurses_opts.add_cmake_defines({ 'USE_DOCTEST': false }) notcurses_opts.add_cmake_defines({ 'USE_PANDOC': false }) - notcurses_opts.add_cmake_defines({ 'USE_POC': false }) notcurses_opts.add_cmake_defines({ 'BUILD_EXECUTABLES': false }) + notcurses_opts.add_cmake_defines({ 'BUILD_FFI_LIBRARY': false }) + notcurses_opts.add_cmake_defines({ 'USE_POC': false }) notcurses_opts.add_cmake_defines({ 'USE_STATIC': false }) notcurses_opts.add_cmake_defines({ 'USE_MULTIMEDIA': 'ffmpeg' }) notcurses_proj = cmake.subproject('notcurses', options: notcurses_opts) diff --git a/taro/ui.c b/taro/ui.c index 95ce402..8df6370 100644 --- a/taro/ui.c +++ b/taro/ui.c @@ -1,3 +1,4 @@ +#define AL_LOG_SECTION "taro" #include #include #include @@ -22,7 +23,7 @@ #define UI_RESERVE_HEIGHT 2 #define GRID_TILE_WIDTH 18 // img = 16 -#define GRID_TILE_HEIGHT 10 // img = 8 +#define GRID_TILE_HEIGHT 9 // img = 8 #define GRID_TEXT_WIDTH (GRID_TILE_WIDTH - 4) @@ -33,7 +34,7 @@ // INFO_HEIGHT = term_rows + UI_RESERVE_HEIGHT #define INFO_IMG_WIDTH 24 -#define INFO_IMG_HEIGHT 12 +#define INFO_IMG_HEIGHT 11 #define INFO_TEXT_WIDTH (GRID_TEXT_WIDTH + 6) @@ -141,12 +142,12 @@ static s32 get_entry_from_config(struct ui *u) s32 ret = -1; str config; if (nn_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); + u32 index = al_str_find(&config, '|'); + if (index != AL_STR_NPOS && 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)) { + if (al_str_eq(&e->id, &id)) { ret = i; break; } @@ -163,14 +164,11 @@ static void select_monitor(struct ui *u) 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("/")); + 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")); + 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) @@ -182,22 +180,24 @@ static void write_selected_entry_to_config(struct ui *u) } else { al_str_clone(&config, &u->n.e->id); if (u->n.e->has_package) { - al_str_cat(&config, al_str_c("_p")); + al_str_cat(&config, &al_str_c("_p")); } else { - al_str_cat(&config, al_str_c("_d")); + al_str_cat(&config, &al_str_c("_d")); } - al_str_cat(&config, al_str_c("|false,100|")); + 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")); + case PROP_TYPE_BOOL: { + bool value = *((bool *)p->value); + al_str_cat(&config, value ? &al_str_c("true") : &al_str_c("false")); break; } - al_str_cat(&config, al_str_c(",")); + } + al_str_cat(&config, &al_str_c(",")); } - if (al_str_at(&config, config.len - 1) == ',') { - config.len--; + if (al_str_at(&config, config.length - 1) == ',') { + config.length--; } } nn_file_open_and_replace(&u->m.selection_path, &config, true); @@ -207,69 +207,6 @@ static void write_selected_entry_to_config(struct ui *u) // Interface // --------- -static bool wide_string_to_width(wstr *w, u32 index, s32 max_width, void (*func)(void *, wchar_t), void *userdata) -{ - u32 i = index; - s32 width = 0; - do { - wchar_t wc = al_wstr_at(w, i); - width += al_wchar_width(wc); - if (width >= max_width) { - break; - } - func(userdata, wc); - } while (++i < w->len); - return i == w->len; -} - -static void set_scrolling_text(struct scrolling_text *text, wstr *s, s32 y, s32 x, s32 width) -{ - text->s = s; - text->y = y; - text->x = x; - text->width = width; - text->index = 0; - text->skip = al_wchar_width(al_wstr_at(s, text->index)); -} - -static void draw_func(void *userdata, wchar_t wc) -{ - struct ncplane *p = (struct ncplane *)userdata; - ncplane_putwc(p, wc); -} - -static void draw_scrolling_text(struct ncplane *p, struct scrolling_text *text, bool update) -{ - ncplane_cursor_move_yx(p, text->y, text->x); - bool end = wide_string_to_width(text->s, text->index, text->width, draw_func, p); - if (update && !--text->skip) { - text->skip = al_wchar_width(al_wstr_at(text->s, text->index)); - text->index = end ? 0 : text->index + 1; - } -} - -static void set_info_entry(struct ui *u, struct info *n) -{ - 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, INFO_IMG_HEIGHT + 1, tx, INFO_TEXT_WIDTH); - - n->selected_prop = 0; - n->props.size = 0; - struct prop *p; - struct info_prop *ip; - al_array_foreach_ptr(u->n.e->props, i, p) { - al_array_push(n->props, (struct info_prop){ 0 }); - ip = &al_array_last(n->props); - ip->p = p; - s32 ty = INFO_IMG_HEIGHT + 3 + ((i % n->max_props) * 2); - set_scrolling_text(&ip->text, &p->title, ty, 0, PROP_TEXT_WIDTH); - } -} - static bool init_info(struct info *n) { al_array_init(n->props); @@ -300,19 +237,19 @@ 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")); + al_str_cat(&monitors_path, &al_str_c("/monitors")); str monitors; bool ret = nn_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, has `tarod` run?"); + error("failed to open monitor list, has `tarod` run?"); return false; } str monitor_dir; - str monitor = al_str_zero(); + str monitor = al_str_null(); 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, &al_str_c("/")); al_str_cat(&monitor_dir, &monitor); if (nn_dir_exists(&monitor_dir)) { char *c_str = al_str_to_c_str(&monitor); @@ -320,23 +257,33 @@ static bool init_monitors(struct ui *u, struct monitors *m) } al_str_free(&monitor_dir); } - if (!u->m.monitors.size) { + if (!u->m.monitors.count) { u->m.set_monitor = -1; u->m.selected_monitor = -1; } al_array_sort(m->monitors, char *, monitor_cmp); u->g.set_index = -1; + u->g.selected_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; + if (u->db.entries.count > 0) { + // set_index will be set in set_grid_tiles() otherwise. + if (u->g.set_index == -1) u->g.selected_index = 0; } else { - u->g.selected_index = -1; + u->selected_menu = MENU_MONITORS; } return true; } +static s32 void_print_callback(void *userdata, u8 level, char *message) +{ + (void)userdata; + (void)level; + (void)message; + return 0; +} + static bool init_ui(struct ui *u) { if (!init_monitors(u, &u->m)) return false; @@ -344,9 +291,10 @@ static bool init_ui(struct ui *u) 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"); + error("failed to initialize notcurses"); return false; } + al_set_print(void_print_callback, NULL); return true; } @@ -360,21 +308,16 @@ struct image *get_image_from_pool(struct image_pool *pool, str *path) } } - 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) { + if (pool->images.count >= pool->max) { for (u32 i = 0; i < pool->chunk_size; i++) { ncvisual_destroy(al_array_at(pool->images, i).v); } al_array_remove_range(pool->images, 0, pool->chunk_size); } + u32 index = al_str_rfind(path, '.'); + bool gif = index != AL_STR_NPOS && al_str_cmp(path, &al_str_c(".gif"), index, 4) == 0; + char *c_str = al_str_to_c_str(path); struct image img = { .hash = hash, @@ -422,6 +365,48 @@ static s32 resize_cb(struct ncplane *p) return 0; } +static bool wide_string_to_width(wstr *w, u32 index, s32 max_width, + void (*func)(void *, wchar_t), void *userdata) +{ + u32 i = index; + s32 width = 0; + do { + wchar_t wc = al_wstr_at(w, i); + width += al_wchar_width(wc); + if (width >= max_width) { + break; + } + func(userdata, wc); + } while (++i < w->length); + return i == w->length; +} + +static void set_scrolling_text(struct scrolling_text *text, wstr *s, s32 y, s32 x, s32 width) +{ + text->s = s; + text->y = y; + text->x = x; + text->width = width; + text->index = 0; + text->skip = al_wchar_width(al_wstr_at(s, text->index)); +} + +static void draw_func(void *userdata, wchar_t wc) +{ + struct ncplane *p = (struct ncplane *)userdata; + ncplane_putwc(p, wc); +} + +static void draw_scrolling_text(struct ncplane *p, struct scrolling_text *text, bool update) +{ + ncplane_cursor_move_yx(p, text->y, text->x); + bool end = wide_string_to_width(text->s, text->index, text->width, draw_func, p); + if (update && !--text->skip) { + text->skip = al_wchar_width(al_wstr_at(text->s, text->index)); + text->index = end ? 0 : text->index + 1; + } +} + static void maybe_destroy_info(struct info *n) { if (n->ip) { @@ -430,6 +415,28 @@ static void maybe_destroy_info(struct info *n) } } +static void set_info_entry(struct ui *u, struct info *n) +{ + 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, INFO_IMG_HEIGHT + 1, tx, INFO_TEXT_WIDTH); + + n->selected_prop = 0; + n->props.count = 0; + struct prop *p; + struct info_prop *ip; + al_array_foreach_ptr(u->n.e->props, i, p) { + al_array_push(n->props, (struct info_prop){ 0 }); + ip = &al_array_last(n->props); + ip->p = p; + s32 ty = INFO_IMG_HEIGHT + 3 + ((i % n->max_props) * 2); + set_scrolling_text(&ip->text, &p->title, ty, 0, PROP_TEXT_WIDTH); + } +} + static bool layout_info(struct ui *u, struct info *n, struct ncplane *parent, s32 height) { maybe_destroy_info(n); @@ -467,14 +474,15 @@ static void maybe_destroy_grid(struct grid *g) ncplane_destroy(t->p); t->p = NULL; } - g->tiles.size = 0; + g->tiles.count = 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)); + u32 selected = (g->selected_index >= 0) ? g->selected_index : g->set_index; + u32 index = (selected - (selected % g->tiles_per_page)); g->min_index = index; g->max_index = index; @@ -486,16 +494,22 @@ static void set_grid_tiles(struct ui *u, struct grid *g) t->blitted = false; ncplane_erase(t->p); - while (g->max_index < (s32)u->db.entries.size) { + while (g->max_index < (s32)u->db.entries.count) { t->e = &al_array_at(u->db.entries, g->max_index); if (db_ensure_entry_loaded(&u->db, t->e)) { break; } + // Entry failed to load. + if (g->set_index == g->max_index) { + g->set_index = -1; + } else if (g->set_index > g->max_index) { + g->set_index--; + } } - al_assert(g->max_index <= (s32)u->db.entries.size); + al_assert(g->max_index <= (s32)u->db.entries.count); - if (g->max_index == (s32)u->db.entries.size) { + if (g->max_index == (s32)u->db.entries.count) { t->e = NULL; t->text.s = NULL; ncplane_erase(t->ip); @@ -503,18 +517,31 @@ static void set_grid_tiles(struct ui *u, struct grid *g) } s32 width = al_wstr_width(&t->e->title); - s32 tx = 2 + (width < GRID_TEXT_WIDTH ? (GRID_TEXT_WIDTH - width) / 2 : 0); + s32 tx = 2 + ((width < GRID_TEXT_WIDTH) ? (GRID_TEXT_WIDTH - width) / 2 : 0); set_scrolling_text(&t->text, &t->e->title, GRID_TILE_HEIGHT - 1, tx, GRID_TEXT_WIDTH); g->max_index++; }} + if (g->selected_index == -1) { + if (g->set_index == -1) { + // set_index failed to load above. + g->selected_index = 0; + set_grid_tiles(u, g); + return; + } else { + g->selected_index = g->set_index; + } + } + g->max_index--; // This can happen in grid_next_page() and shuffle. Any other case where - // `selected_index` goes over `max_index` is undefined. Handling it this way - // saves us from a lot of pre-loaded. - if (g->selected_index > g->max_index) g->selected_index = g->max_index; + // selected_index goes over max_index is undefined. + // Handling entry load failure in this way saves us from a lot of preloading. + if (g->selected_index > g->max_index) { + g->selected_index = g->max_index; + } } static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s32 width, s32 height) @@ -588,7 +615,7 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 al_array_push(g->tiles, t); }} - if (g->selected_index >= 0) { + if (u->db.entries.count > 0) { set_grid_tiles(u, g); } @@ -622,8 +649,8 @@ bool layout_ui(struct ui *u) { bool even = u->term_cols % 2 == 0; - u32 rwidth = (even) ? UI_RESERVE_WIDTH + 1 : UI_RESERVE_WIDTH; - u32 rheight = (even) ? UI_RESERVE_HEIGHT + 1 : UI_RESERVE_HEIGHT; + u32 rwidth = even ? UI_RESERVE_WIDTH + 1 : UI_RESERVE_WIDTH; + u32 rheight = even ? UI_RESERVE_HEIGHT + 1 : UI_RESERVE_HEIGHT; if (u->term_cols < GRID_TILE_WIDTH + rwidth || u->term_rows < GRID_TILE_HEIGHT + rheight) { return false; @@ -699,30 +726,48 @@ 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; + const char *no_monitors = "NO MONITORS"; + + s32 width = 0; + if (u->m.selected_monitor == -1) { + width = al_strlen(no_monitors); + } else { + 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) { + + if (u->m.selected_monitor == -1) { + if (u->selected_menu == MENU_MONITORS) { ncplane_set_bg_palindex(u->mp, 8); ncplane_set_fg_palindex(u->mp, 0); - } else if (i == (u32)m->set_monitor) { + } else { ncplane_set_bg_palindex(u->mp, 7); ncplane_set_fg_palindex(u->mp, 0); - m->flash -= MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f); - } else { - ncplane_set_fg_default(u->mp); - ncplane_set_bg_default(u->mp); } - ncplane_cursor_move_rel(u->mp, 0, 1); - ncplane_putstr(u->mp, monitor); + ncplane_putstr(u->mp, no_monitors); + } else { + 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_palindex(u->mp, 8); + ncplane_set_fg_palindex(u->mp, 0); + } else if (i == (u32)m->set_monitor) { + ncplane_set_bg_palindex(u->mp, 7); + ncplane_set_fg_palindex(u->mp, 0); + m->flash -= MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f); + } else { + ncplane_set_fg_default(u->mp); + ncplane_set_bg_default(u->mp); + } + ncplane_cursor_move_rel(u->mp, 0, 1); + ncplane_putstr(u->mp, monitor); + } } } @@ -733,7 +778,7 @@ void draw_info(struct ui *u, struct info *n, bool update_text) ncplane_erase(u->ip); struct ncvisual_options vopts = { 0 }; - vopts.scaling = NCSCALE_STRETCH; + vopts.scaling = PIXEL_BLIT ? NCSCALE_SCALE_HIRES : NCSCALE_STRETCH; 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) { @@ -749,6 +794,8 @@ void draw_info(struct ui *u, struct info *n, bool update_text) u->delay = 100000; } } + } else if (!img) { + ncplane_erase(n->ip); } ncplane_set_fg_default(u->ip); @@ -759,7 +806,7 @@ void draw_info(struct ui *u, struct info *n, bool update_text) u32 i, index; for (i = 0; i < n->max_props; i++) { index = i + selected_index; - if (index >= n->props.size) { + if (index >= n->props.count) { break; } ip = &al_array_at(n->props, index); @@ -782,7 +829,7 @@ void draw_info(struct ui *u, struct info *n, bool update_text) } } } - if (ip && i == n->max_props && i != n->props.size) { + if (ip && i == n->max_props && i != n->props.count) { ncplane_putstr_yx(u->ip, ip->text.y + 1, (INFO_IMG_WIDTH - 1) / 2, "V"); } } @@ -810,13 +857,15 @@ 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_STRETCH; + vopts.scaling = PIXEL_BLIT ? NCSCALE_SCALE_HIRES : NCSCALE_STRETCH; 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 && t->e->has_preview) { + ncplane_erase(t->p); + 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; @@ -824,7 +873,6 @@ void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text) t->blitted = true; } } - ncplane_erase(t->p); if (index == g->selected_index && g->flash <= 0.f && u->selected_menu == MENU_GRID) { set_tile_highlight(t, 8, 0); } else if (index == g->set_index && g->set_index >= 0) { @@ -836,7 +884,7 @@ void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text) ncplane_set_bg_alpha(t->p, NCALPHA_TRANSPARENT); ncplane_set_styles(t->p, NCSTYLE_NONE); } - if (t->text.s && t->text.s->len) { + if (t->text.s && !al_wstr_is_empty(t->text.s)) { draw_scrolling_text(t->p, &t->text, update_text); } index++; @@ -849,14 +897,6 @@ static bool handle_input_monitors(struct ui *u, struct monitors *m, struct ncinp 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) { @@ -865,7 +905,16 @@ static bool handle_input_monitors(struct ui *u, struct monitors *m, struct ncinp m->selected_monitor--; should_redraw_monitors = true; break; + case NCKEY_RIGHT: + case 'l': + if ((u32)(m->selected_monitor + 1) >= m->monitors.count) { + return false; + } + m->selected_monitor++; + should_redraw_monitors = true; + break; case NCKEY_RETURN: + case NCKEY_SPACE: { select_monitor(u); m->flash = FLASH_DURATION; should_redraw_monitors = true; @@ -873,6 +922,7 @@ static bool handle_input_monitors(struct ui *u, struct monitors *m, struct ncinp should_redraw_info = true; break; } + } if (should_redraw_monitors) { draw_monitors(u, m); } @@ -891,14 +941,6 @@ static bool handle_input_info(struct ui *u, struct info *n, struct ncinput *inpu { 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) { @@ -907,9 +949,17 @@ static bool handle_input_info(struct ui *u, struct info *n, struct ncinput *inpu n->selected_prop--; should_redraw_info = true; break; + case NCKEY_DOWN: + case 'j': + if ((u32)(n->selected_prop + 1) >= n->props.count) { + return false; + } + n->selected_prop++; + should_redraw_info = true; + break; case NCKEY_RETURN: case NCKEY_SPACE: { - if (!n->props.size) return false; + if (!n->props.count) 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); @@ -922,7 +972,7 @@ static bool handle_input_info(struct ui *u, struct info *n, struct ncinput *inpu case 'h': case NCKEY_RIGHT: case 'l': - if (!n->props.size) return false; + if (!n->props.count) 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; @@ -956,8 +1006,10 @@ static bool handle_input_info(struct ui *u, struct info *n, struct ncinput *inpu static bool grid_next_page(struct ui *u, struct grid *g, s32 offset) { - if (u->db.entries.size == 0) return false; - s32 max = (s32)u->db.entries.size - 1; + if (!u->db.entries.count) { + return false; + } + s32 max = (s32)u->db.entries.count - 1; if (g->selected_index == max) { return false; } @@ -986,19 +1038,6 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu 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) { @@ -1012,12 +1051,16 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu should_redraw_info = true; should_redraw_grid = true; break; - case NCKEY_DOWN: - case 'j': - if (g->selected_index + g->tiles_x > g->max_index) { + case NCKEY_RIGHT: + case 'l': + if (g->selected_index + 1 >= (s32)u->db.entries.count) { return false; } - g->selected_index += g->tiles_x; + 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; @@ -1030,15 +1073,24 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu should_redraw_info = true; should_redraw_grid = true; break; - case 'b': - if (grid_previous_page(&u->g, 0)) { + 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 'f': + if (grid_next_page(u, &u->g, 0)) { should_update_grid = true; should_redraw_info = true; should_redraw_grid = true; } break; - case 'f': - if (grid_next_page(u, &u->g, 0)) { + case 'b': + if (grid_previous_page(&u->g, 0)) { should_update_grid = true; should_redraw_info = true; should_redraw_grid = true; @@ -1051,7 +1103,10 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu should_redraw_info = true; break; case 's': { - s32 max = (s32)u->db.entries.size - 1; + if (!u->db.entries.count) { + return false; + } + s32 max = (s32)u->db.entries.count - 1; g->selected_index = al_rand() % max; should_update_grid = true; should_redraw_info = true; @@ -1099,18 +1154,14 @@ static void input_poll_callback(void *userdata, s32 revents) if (input.id == NCKEY_TAB) { switch (u->selected_menu) { case MENU_GRID: - if (u->n.props.size > 0) { + if (u->n.props.count > 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; - } + u->selected_menu = MENU_MONITORS; break; case MENU_MONITORS: u->selected_menu = MENU_GRID; @@ -1174,20 +1225,23 @@ s32 main(void) if (!nn_common_init()) return EXIT_FAILURE; if (!open_config(&u.conf, false)) { - al_log_error("taro", "failed to open config, run `tarod` at least once to generate needed config"); + error("failed to open config, run `tarod` at least once to generate needed config"); return EXIT_FAILURE; } s32 ret = EXIT_FAILURE; - if (!db_load_entries(&u.db, &u.conf.wallpaper_dir)) { - goto out; - } + db_init(&u.db); - if (!init_ui(&u)) { - goto out; + str *dir; + al_array_foreach_ptr(u.conf.wallpaper_dirs, i, dir) { + if (!db_load_entries(&u.db, dir)) goto out; } + db_sort(&u.db); + + if (!init_ui(&u)) goto out; + notcurses_stddim_yx(u.nc, &u.term_rows, &u.term_cols); ncplane_set_resizecb(notcurses_stdplane(u.nc), resize_cb); @@ -1195,7 +1249,7 @@ s32 main(void) if (!layout_ui(&u)) { notcurses_stop(u.nc); - al_log_error("taro", "failed to layout ui, your terminal window is probably too small"); + error("failed to layout ui, your terminal window is probably too small"); goto out; } u.layed_out = true; -- cgit v1.2.3-101-g0448