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 --- .gitignore | 10 +- README.txt | 114 ++++++++++- flake.lock | 6 +- flake.nix | 5 +- meson.build | 2 +- src/assets.cc | 4 +- src/assets.h | 3 + src/context.h | 6 +- src/gl.cc | 4 + src/gl.h | 5 +- src/json.h | 1 - src/log.h | 5 - src/mauri.cc | 100 ++++++---- src/objects/object.cc | 2 + src/objects/pass.cc | 31 ++- src/objects/scene.cc | 6 + src/shader.cc | 7 +- src/shader.h | 4 + src/texture.cc | 1 + src/util.h | 11 -- subprojects/glm.wrap | 4 + subprojects/libalabaster.wrap | 2 +- subprojects/libnaunet.wrap | 2 +- subprojects/lz4.wrap | 2 +- subprojects/stela.wrap | 2 +- 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 ++++++++++++++++++++++++------------------ 32 files changed, 826 insertions(+), 545 deletions(-) create mode 100644 subprojects/glm.wrap diff --git a/.gitignore b/.gitignore index 6fab9ba..b788ee1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,15 @@ .cache/ compile_commands.json subprojects/c89atomic.wrap +subprojects/c89atomic-*/ subprojects/glfw3.wrap -subprojects/glfw3/ -subprojects/glm.wrap +subprojects/glfw3-*/ subprojects/glm/ subprojects/jansson.wrap +subprojects/jansson-*/ subprojects/janus/ +subprojects/jsmn.wrap +subprojects/jsmn/ subprojects/json/ subprojects/libalabaster subprojects/libalabaster/ @@ -22,5 +25,8 @@ subprojects/s3tc-dxt-decompression/ subprojects/stb/ subprojects/stela subprojects/stela/ +subprojects/wayland-protocols.wrap +subprojects/wayland-protocols-*/ subprojects/wlr-protocols.wrap +subprojects/wlr-protocols-*/ workshop_wallpapers*/ diff --git a/README.txt b/README.txt index aebc4be..9824bca 100644 --- a/README.txt +++ b/README.txt @@ -1,3 +1,5 @@ +:source-highlighter: pygments + == mauri: Wallpaper Engine Implementation (for Linux) === Building @@ -9,12 +11,118 @@ * nlohmann_json * fftw3 `optional (audio)` * notcurses `optional (taro)` +* jansson `optional (taro)` +* libev `optional (taro)` + +debian12: sudo apt install gcc g++ meson ninja-build ==== Compiling [source,sh] ---- -meson setup build -Dwindow=(x11|wayland) -Daudio=(enabled|disabled) -Dtaro=(enabled|disabled) -ninja -C build +$ meson setup build -Dwindow=(x11|wayland) -Daudio=(enabled|disabled) -Dtaro=(enabled|disabled) +$ ninja -C build +---- + +=== Installing +==== Local +[source,sh] +---- +$ sudo ninja -C build install +---- + +==== Nix Flake +[source,nix] +---- +inputs = { + mauri = { + url = "git+https://git.akon.city/mauri"; + inputs.nixpkgs.follows = "nixpkgs"; + }; +}; + +nixosSystem { + system = system; + modules = [ + ({ ... }: { + nixpkgs.overlays = [ + (self: super: { mauri = mauri.packages.${system}.default; }) + ]; + }) + ]; +}; +---- + +=== Usage +[source,sh] +---- +$ mauri --help +---- +[source,plain] +---- +Usage: mauri (-p, --package [path_to_scene.pkg]|-d, --directory [path_to_wallpaper]) [OPTIONS] + -a, --assets search path for common assets + -w, --width window width (default: 640) + -h, --height window height (default: 480) + -o, --offset offset of the scene camera (default: 0,0) + -s, --scale scale the window to the scene + -m, --monitor set as the background of this monitor (by name) + -t, --target dump the scene tree into this directory and exit +---- +==== Examples +[source,sh] +---- +# Set as the background of DP-1. +$ ./mauri -p /mnt/hdd/SteamLibrary/steamapps/workshop/content/431960/2452236180/scene.pkg \ + -a /mnt/hdd/SteamLibrary/steamapps/common/wallpaper_engine/assets -w 2560 -h 1440 -m DP-1 + +# Dump the scene into ./output. +$ ./mauri -p /mnt/hdd/SteamLibrary/steamapps/workshop/content/431960/2108234011/scene.pkg \ + -a /mnt/hdd/SteamLibrary/steamapps/common/wallpaper_engine/assets -t ./output +---- + +==== taro +[source,sh] +---- +# You must run tarod once before anything to generate the necessary files for your setup. +$ tarod + +# Open the terminal UI. +$ taro +---- + +===== Keybinds +*←↓↑→/hjkl* - Move Selection, Edit Value + +*Enter/Space* - Confirm Selection + +*Tab* - Cycle Between Grid, Info/Options, and Monitor Selection + +*f/b* - Next/Previous Page + +*r* - Remove Current Wallpaper + + +===== Config +[source,sh] +---- +$ cat $HOME/.config/taro/config.json +---- +[source,json] +---- +{ + "directories": { + "assets": "/mnt/hdd/SteamLibrary/steamapps/common/wallpaper_engine/assets", + "wallpapers": [ + "/mnt/hdd/SteamLibrary/steamapps/workshop/content/431960" + ] + } +} +---- + +===== Sway Config Example +[source,sh] +---- +# Set wallpaper +bindsym Mod4+F1 exec printf '1633608994_p|false,100|' > $HOME/.config/taro/DP-1/selection +# Remove wallpaper +bindsym Mod4+F2 exec printf '||' > $HOME/.config/taro/DP-1/selection +# Run when starting Sway +exec tarod ---- === Known Issues @@ -22,7 +130,7 @@ ninja -C build - Some objects are incorrectly scaled and/or positioned. - Some shaders fail to compile for various reasons. -==== Not Implemented: +==== Not Implemented - Particles - Mouse Paralax - Camera Shake diff --git a/flake.lock b/flake.lock index ae3a73f..49805da 100644 --- a/flake.lock +++ b/flake.lock @@ -19,11 +19,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1734649271, - "narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=", + "lastModified": 1743315132, + "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d70bd19e0a38ad4790d3913bf08fcbfc9eeca507", + "rev": "52faf482a3889b7619003c0daec593a1912fddc1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 96ada27..62d6f15 100644 --- a/flake.nix +++ b/flake.nix @@ -76,7 +76,7 @@ wrapProgram $out/bin/taro --prefix LD_LIBRARY_PATH : "$out/lib64" ''; meta = with lib; { - homepage = "https://akon.city/"; + homepage = "https://git.akon.city/mauri"; description = ""; changelog = ""; license = [ licenses.gpl3Only ]; @@ -94,6 +94,9 @@ perf-tools valgrind asciidoctor + (python3.withPackages (python-pkgs: [ + python-pkgs.pygments + ])) ]; buildInputs = packages.${system}.default.buildInputs ++ [ # sway build diff --git a/meson.build b/meson.build index 7d13da0..7fa43ee 100644 --- a/meson.build +++ b/meson.build @@ -16,7 +16,7 @@ naunet = subproject('libnaunet', default_options: [ ]).get_variable('naunet') stela = subproject('stela', default_options: [ - 'api=opengl', + 'api=gl', is_windows ? 'window=glfw' : 'window='+get_option('window'), 'poll=inline', 'event-buffer=false', diff --git a/src/assets.cc b/src/assets.cc index 8d2ac18..2bc227f 100644 --- a/src/assets.cc +++ b/src/assets.cc @@ -3,6 +3,8 @@ #include "assets.h" #include "texture.h" +//#define AL_LOG_ENABLE_TRACE +#include "log.h" namespace Mauri { @@ -192,7 +194,7 @@ auto AssetManager::get(const std::string &part, AssetTypeHint hint) -> Asset * continue; } asset->hash = hash; - debug("found asset in: (%s)", dir.c_str()); + trace("found asset in: (%s)", dir.c_str()); break; } diff --git a/src/assets.h b/src/assets.h index d1b8144..015564e 100644 --- a/src/assets.h +++ b/src/assets.h @@ -1,6 +1,9 @@ #ifndef _ASSET_H #define _ASSET_H +#include +#include + #include "util.h" namespace Mauri diff --git a/src/context.h b/src/context.h index a64dde7..ab27cb2 100644 --- a/src/context.h +++ b/src/context.h @@ -2,12 +2,12 @@ #define _CONTEXT_H extern "C" { +#include #include #include -#include } -#include "gl.h" +#include namespace Mauri { @@ -31,7 +31,7 @@ class Context auto create_window(s32 width, s32 height, const std::string &name, const std::string monitor) -> bool { - this->window = stl_window_create(NULL); + this->window = stl_window_create(); this->window->pointer_pos_callback = pointer_pos_callback; this->window->should_close_callback = should_close_callback; this->window->userdata = this; diff --git a/src/gl.cc b/src/gl.cc index d753142..6ae8f1f 100644 --- a/src/gl.cc +++ b/src/gl.cc @@ -1,3 +1,7 @@ +extern "C" { +#include +} + #include "gl.h" #include "log.h" #include "context.h" diff --git a/src/gl.h b/src/gl.h index 95d8b84..47c5f8a 100644 --- a/src/gl.h +++ b/src/gl.h @@ -5,14 +5,15 @@ extern "C" { #include } +#include +#include + #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include -#include "util.h" - using namespace glm; namespace Mauri diff --git a/src/json.h b/src/json.h index 300f1b3..0cd7ab9 100644 --- a/src/json.h +++ b/src/json.h @@ -2,7 +2,6 @@ #define _JSON_H #define JSON_NO_IO -#undef array #include #include "gl.h" diff --git a/src/log.h b/src/log.h index b7655ff..43d0e09 100644 --- a/src/log.h +++ b/src/log.h @@ -5,9 +5,4 @@ extern "C" { #include } -#define info(fmt, ...) al_log_info(NULL, fmt, ##__VA_ARGS__) -#define warn(fmt, ...) al_log_warn(NULL, fmt, ##__VA_ARGS__) -#define error(fmt, ...) al_log_error(NULL, fmt, ##__VA_ARGS__) -#define debug(fmt, ...) al_log_debug(NULL, fmt, ##__VA_ARGS__) - #endif // _LOG_H diff --git a/src/mauri.cc b/src/mauri.cc index e562a76..9351320 100644 --- a/src/mauri.cc +++ b/src/mauri.cc @@ -2,12 +2,12 @@ extern "C" { #include #include } - #include -#include "context.h" #include "assets.h" +#include "context.h" #include "engine.h" +#include "log.h" #include "objects/scene.h" @@ -19,33 +19,16 @@ Usage: mauri (-p, --package [path_to_scene.pkg]|-d, --directory [path_to_wallpap -w, --width window width (default: 640)\n\ -h, --height window height (default: 480)\n\ -o, --offset offset of the scene camera (default: 0,0)\n\ + -s, --scale scale the window to the scene\n\ -m, --monitor set as the background of this monitor (by name)\n\ - -t, --target dump scene into this directory and exit\ + -t, --target dump the scene tree into this directory and exit\ " -#ifndef _WIN32 -auto main(s32 argc, char *argv[]) -> s32 -#else -auto wmain(s32 argc, wchar_t **argv) -> s32 -#endif +auto window_system_main(u32 argc, str *argv, void *extra) -> s32 { - if (!nn_common_init()) return EXIT_FAILURE; - - janus::ArgParser args(HELP_TEXT, "0.13"); - - args.newString("package p", ""); - args.newString("directory d", ""); - args.newString("assets a", ""); - args.newInt("width w", 0); - args.newInt("height h", 0); - args.newString("offset o", "0,0"); - args.newString("monitor m", ""); - args.newString("target t", ""); -#ifndef _WIN32 - args.parse(argc, argv); -#else - args.wparse(argc, argv); -#endif + (void)argc; + (void)argv; + janus::ArgParser args = *(janus::ArgParser *)extra; if (!(args.found("package") ^ args.found("directory"))) { @@ -53,7 +36,7 @@ auto wmain(s32 argc, wchar_t **argv) -> s32 return EXIT_FAILURE; } - s32 width = 640; + s32 width = 720; s32 height = 480; if (args.found("width")) width = args.getInt("width"); if (args.found("height")) height = args.getInt("height"); @@ -65,16 +48,22 @@ auto wmain(s32 argc, wchar_t **argv) -> s32 s32 xoffset; s32 yoffset; - if (sscanf(args.getString("offset").c_str(), "%d,%d", &xoffset, &yoffset) != 2) { + if (sscanf(args.getString("offset").c_str(), "%d,%d", &xoffset, &yoffset) != 2) + { error("invalid offset in arguments"); return EXIT_FAILURE; } - if (!stl_global_init(false)) return EXIT_FAILURE; - + bool gifscene = false; if (args.found("package")) { - if (!asset_manager()->arm_package(args.getString("package"), args.found("target"))) + std::string package_path = args.getString("package"); + size_t slash = package_path.rfind("/"); + std::string filename; + if (slash != std::string::npos) filename = package_path.substr(slash + 1); + else filename = package_path; + gifscene = filename == "gifscene.pkg"; + if (!asset_manager()->arm_package(package_path, args.found("target"))) { return EXIT_FAILURE; } @@ -86,15 +75,26 @@ auto wmain(s32 argc, wchar_t **argv) -> s32 asset_manager()->add_search_directory(args.getString("directory")); } - if (args.found("assets")) { + if (args.found("assets")) + { asset_manager()->add_search_directory(args.getString("assets")); } - Scene *scene = Scene::parse("scene.json"); + Scene *scene = Scene::parse(gifscene ? "gifscene.json" : "scene.json"); if (!scene) return EXIT_FAILURE; - if (width <= 0) width = scene->width; - if (height <= 0) height = scene->height; + if (width <= 0 || height <= 0) + { + width = scene->width; + height = scene->height; + } + + if (args.getFlag("scale")) + { + f32 ratio = scene->width / (f32)scene->height; + if (ratio > 1.f) height = width / ratio; + else width = height * ratio; + } Context *new_context = new Context(); if (!new_context->create_window(width, height, "mauri", args.getString("monitor"))) @@ -131,3 +131,35 @@ auto wmain(s32 argc, wchar_t **argv) -> s32 return EXIT_SUCCESS; } + +#ifndef _WIN32 +auto main(s32 argc, char *argv[]) -> s32 +#else +auto wmain(s32 argc, wchar_t **argv) -> s32 +#endif +{ + if (!nn_common_init()) return EXIT_FAILURE; + + janus::ArgParser args(HELP_TEXT, "0.13"); + args.newString("package p", ""); + args.newString("directory d", ""); + args.newString("assets a", ""); + args.newInt("width w", 0); + args.newInt("height h", 0); + args.newString("offset o", "0,0"); + args.newFlag("scale s"); + args.newString("monitor m", ""); + args.newString("target t", ""); +#ifndef _WIN32 + args.parse(argc, argv); +#else + args.wparse(argc, argv); +#endif + + if (!stl_global_init(false)) return EXIT_FAILURE; + + s32 ret = stl_swallow_main(0, NULL, window_system_main, &args); + stl_global_close(); + + return ret; +} diff --git a/src/objects/object.cc b/src/objects/object.cc index 4cf9790..6240c30 100644 --- a/src/objects/object.cc +++ b/src/objects/object.cc @@ -1,3 +1,5 @@ +#include "../log.h" + #include "object.h" #include "scene.h" #include "pass.h" diff --git a/src/objects/pass.cc b/src/objects/pass.cc index 0ed32ae..dc19378 100644 --- a/src/objects/pass.cc +++ b/src/objects/pass.cc @@ -1,5 +1,6 @@ #include "../gl.h" -#include "../context.h" +//#define AL_LOG_ENABLE_TRACE +#include "../log.h" #include "pass.h" @@ -271,14 +272,13 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) } } -#ifdef _DEBUG_ - debug("PASS (%s)", this->command == COPY ? "copy" : "draw"); - debug(" object: %s (passthrough: %s, fullscreen %s)", this->object->name.c_str(), + trace("PASS (%s)", this->command == COPY ? "copy" : "draw"); + trace(" object: %s (passthrough: %s, fullscreen %s)", this->object->name.c_str(), BOOLSTR(this->object->passthrough), BOOLSTR(this->object->fullscreen)); - debug(" visible: %s", BOOLSTR(this->object->visible)); - debug(" shader: %s", this->shader->origin()->name.c_str()); - debug(" target: %s", pass->target.c_str()); - debug(" textures (%u):", this->shader->origin()->texture_count); + trace(" visible: %s", BOOLSTR(this->object->visible)); + trace(" shader: %s", this->shader->origin()->name.c_str()); + trace(" target: %s", pass->target.c_str()); + trace(" textures (%u):", this->shader->origin()->texture_count); for (u32 i = 0; i < this->shader->origin()->texture_count; i++) { switch (this->texture_types[i]) @@ -286,27 +286,26 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) case STATIC: if (!this->shader->origin()->textures[i].empty() && pass->textures[i] == this->shader->origin()->textures[i]) { - debug(" [%u] %s (DEFAULT)", i, this->shader->origin()->textures[i].c_str()); + trace(" [%u] %s (DEFAULT)", i, this->shader->origin()->textures[i].c_str()); } else { - debug(" [%u] %s", i, pass->textures[i].c_str()); + trace(" [%u] %s", i, pass->textures[i].c_str()); } break; case PREVIOUS: - debug(" [%u] previous", i); + trace(" [%u] previous", i); break; } } - debug(" uniforms (%u)", this->shader->uniforms.size()); + trace(" uniforms (%u)", this->shader->uniforms.size()); for (Uniform *uniform : this->shader->uniforms) { - debug(" %s %s %s", uniform_type_to_string(uniform->type).c_str(), + trace(" %s %s %s", uniform_type_to_string(uniform->type).c_str(), uniform->uname.c_str(), glm::to_string(*uniform->value).c_str()); } - debug(" model: %s", BOOLSTR(this->model)); - debug(" combine: %s", BOOLSTR(this->combine)); -#endif + trace(" model: %s", BOOLSTR(this->model)); + trace(" combine: %s", BOOLSTR(this->combine)); } auto RenderPass::draw(Engine *engine) -> void diff --git a/src/objects/scene.cc b/src/objects/scene.cc index b9728c7..fa03622 100644 --- a/src/objects/scene.cc +++ b/src/objects/scene.cc @@ -37,6 +37,12 @@ auto Scene::parse(const std::string &path) -> Scene * if (obj) scene->objects.emplace_back(obj); } + if ((scene->width == 0 || scene->height == 0) && scene->objects.size() > 0) + { + scene->width = scene->objects[0]->size[0]; + scene->height = scene->objects[0]->size[1]; + } + return scene; } diff --git a/src/shader.cc b/src/shader.cc index c8ba7b0..71597da 100644 --- a/src/shader.cc +++ b/src/shader.cc @@ -1,11 +1,11 @@ #include "shader.h" -#include "gl.h" #include "json.h" +#include "gl.h" #include "objects/object.h" +static const char *version = "#version 330\n"; static const char *compat = \ -// "#define mul(x, y) ((y) * (x))\n" "#define max(x, y) max(y, x)\n" "#define frac fract\n" "#define log10 log\n" @@ -25,9 +25,6 @@ static const char *compat = \ "#define GLSL 1\n"; static const char *mul = "#define mul(x, y) ((y) * (x))\n"; -//static const char *mul = ""; - -static const char *version = "#version 330\n"; namespace Mauri { diff --git a/src/shader.h b/src/shader.h index 132b843..ae02887 100644 --- a/src/shader.h +++ b/src/shader.h @@ -1,6 +1,10 @@ #ifndef _SHADER_H #define _SHADER_H +#include +#include +#include + #include "texture.h" #define MAX_TEXTURES 8 diff --git a/src/texture.cc b/src/texture.cc index 4ea7211..3128a23 100644 --- a/src/texture.cc +++ b/src/texture.cc @@ -15,6 +15,7 @@ _Pragma("GCC diagnostic pop") #include #include "texture.h" +#include "log.h" using namespace Mauri; diff --git a/src/util.h b/src/util.h index db954c0..774409a 100644 --- a/src/util.h +++ b/src/util.h @@ -3,19 +3,8 @@ extern "C" { #include -#include } -#include "log.h" - -#undef array -#include -#include -#include -#include -#include -#include - // Pause after every pass. //#define FRAME_STEP diff --git a/subprojects/glm.wrap b/subprojects/glm.wrap new file mode 100644 index 0000000..dd52366 --- /dev/null +++ b/subprojects/glm.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/g-truc/glm.git +revision =7a878c23723a5122078e07b0d870016afd8854ce +depth = 1 diff --git a/subprojects/libalabaster.wrap b/subprojects/libalabaster.wrap index 49f70eb..038be37 100644 --- a/subprojects/libalabaster.wrap +++ b/subprojects/libalabaster.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://git.akon.city/libalabaster -revision = bb4d5418f2173bf9a2fa1c0be31c3eb9f2025a21 +revision = f6a8686b2c209b0e25cd64a587d6e9fb55200ec4 depth = 1 diff --git a/subprojects/libnaunet.wrap b/subprojects/libnaunet.wrap index 6db8ca9..14965e1 100644 --- a/subprojects/libnaunet.wrap +++ b/subprojects/libnaunet.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://git.akon.city/libnaunet -revision = 822e754cafdc5a32c80c97ca015cd6f9008329c3 +revision = de6f6090f94b14ed98dac71956ff1bede48f1d8e depth = 1 diff --git a/subprojects/lz4.wrap b/subprojects/lz4.wrap index 7de9716..cdbcd4c 100644 --- a/subprojects/lz4.wrap +++ b/subprojects/lz4.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/lz4/lz4.git -revision = 4032c8c787e640f6cb217b89ee8057949bea1b01 +revision = 07c526c0afe28866cdcb7f84f4aadcebd0d63ba7 depth = 1 patch_directory = lz4 diff_files = lz4/move_meson_build.diff diff --git a/subprojects/stela.wrap b/subprojects/stela.wrap index a44296c..c6daa98 100644 --- a/subprojects/stela.wrap +++ b/subprojects/stela.wrap @@ -1,4 +1,4 @@ [wrap-git] url = https://git.akon.city/stela -revision = 6fe15ce0d41bd55331cac006134b61c1704775c3 +revision = a3fb42db50542a7e1fca8662d08d3764006c9887 depth = 1 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