diff options
| author | 2024-10-19 12:05:15 -0400 | |
|---|---|---|
| committer | 2024-10-19 12:13:26 -0400 | |
| commit | 42e8c4d28b6fe9cc5e041f02e0c85b390d49a218 (patch) | |
| tree | 59080eec3dd79d225bb31fb0f7e646cc22880d39 | |
| parent | 2bb807dd32e1c18a6f1e8f22c25141de3334de9f (diff) | |
| download | mauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.tar.gz mauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.tar.bz2 mauri-42e8c4d28b6fe9cc5e041f02e0c85b390d49a218.zip | |
Attempt to make usable and packageable
Signed-off-by: Andrew Opalach <andrew@akon.city>
| -rw-r--r-- | .gitignore | 16 | ||||
| -rw-r--r-- | README.md | 28 | ||||
| -rw-r--r-- | README.txt | 34 | ||||
| -rw-r--r-- | attic/sway_background_covered.diff (renamed from taro/attic/sway_background_covered.diff) | 12 | ||||
| -rw-r--r-- | attic/sway_testing_config (renamed from taro/attic/sway_test_config) | 0 | ||||
| -rw-r--r-- | flake.lock | 6 | ||||
| -rw-r--r-- | flake.nix | 33 | ||||
| -rw-r--r-- | meson.build | 30 | ||||
| -rw-r--r-- | meson_options.txt | 2 | ||||
| -rwxr-xr-x | scripts/line_count.sh | 2 | ||||
| -rw-r--r-- | src/gl.cc | 22 | ||||
| -rw-r--r-- | src/gl.h | 4 | ||||
| -rw-r--r-- | src/mauri.cc | 2 | ||||
| -rw-r--r-- | src/objects/material.cc | 10 | ||||
| -rw-r--r-- | src/objects/model.cc | 2 | ||||
| -rw-r--r-- | src/objects/model.h | 2 | ||||
| -rw-r--r-- | src/objects/pass.cc | 22 | ||||
| -rw-r--r-- | src/objects/scene.cc | 2 | ||||
| -rw-r--r-- | src/texture.cc | 12 | ||||
| -rw-r--r-- | src/util.h | 1 | ||||
| -rw-r--r-- | subprojects/notcurses.wrap (renamed from taro/subprojects/notcurses.wrap) | 0 | ||||
| -rw-r--r-- | subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff (renamed from taro/subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff) | 0 | ||||
| -rw-r--r-- | subprojects/packagefiles/s3tc-dxt-decompression/dxt1_default_alpha.diff | 13 | ||||
| -rw-r--r-- | subprojects/s3tc-dxt-decompression.wrap | 2 | ||||
| -rw-r--r-- | taro/config.h (renamed from taro/src/config.h) | 74 | ||||
| -rw-r--r-- | taro/daemon.c (renamed from taro/src/daemon.c) | 157 | ||||
| -rw-r--r-- | taro/db.c (renamed from taro/src/db.c) | 15 | ||||
| -rw-r--r-- | taro/db.h (renamed from taro/src/db.h) | 0 | ||||
| -rw-r--r-- | taro/file_ext.c (renamed from taro/src/file_ext.c) | 2 | ||||
| -rw-r--r-- | taro/file_ext.h (renamed from taro/src/file_ext.h) | 2 | ||||
| -rw-r--r-- | taro/meson.build | 27 | ||||
| -rwxr-xr-x | taro/scripts/run_wallpaper.sh | 6 | ||||
| -rw-r--r-- | taro/subprojects/libakiyo.wrap | 4 | ||||
| -rw-r--r-- | taro/subprojects/libalabaster.wrap | 4 | ||||
| -rw-r--r-- | taro/subprojects/stela.wrap | 4 | ||||
| -rw-r--r-- | taro/ui.c (renamed from taro/src/ui.c) | 351 |
36 files changed, 495 insertions, 408 deletions
@@ -6,6 +6,7 @@ subprojects/glfw3.wrap subprojects/glfw3/ subprojects/glm.wrap subprojects/glm/ +subprojects/jansson.wrap subprojects/janus/ subprojects/json/ subprojects/libakiyo @@ -14,23 +15,10 @@ subprojects/libalabaster subprojects/libalabaster/ subprojects/libev.wrap subprojects/lz4/ +subprojects/notcurses/ subprojects/s3tc-dxt-decompression/ subprojects/stb/ subprojects/stela subprojects/stela/ subprojects/wlr-protocols.wrap -taro/subprojects/c89atomic.wrap -taro/subprojects/jansson.wrap -taro/subprojects/jansson/ -taro/subprojects/libakiyo -taro/subprojects/libakiyo/ -taro/subprojects/libalabaster -taro/subprojects/libalabaster/ -taro/subprojects/libcurl.wrap -taro/subprojects/libcurl/ -taro/subprojects/libev.wrap -taro/subprojects/notcurses/ -taro/subprojects/stela -taro/subprojects/stela/ -taro/subprojects/wlr-protocols.wrap workshop_wallpapers*/ diff --git a/README.md b/README.md deleted file mode 100644 index c272921..0000000 --- a/README.md +++ /dev/null @@ -1,28 +0,0 @@ -## mauri: Wallpaper Engine Implementation (for Linux) - -This is quite incomplete, it's not meant to be used. Although, it _is_ usable in the simplest case. -If you find a wallpaper that works well enough and have an X11 environment that supports `_NET_WM_WINDOW_TYPE_DESKTOP`, -you can run it with `./mauri -p <path_to_scene.pkg> -w <width> -h <height> -b true` and it should be set as your background. -If you have multiple monitors, you might need to adjust `context_x11:183` to position the window. - -Most of the work, especially in the wallpaper engine implementation, was done around late 2019-2020 -and was mostly a learning experience for me. I could have made much better use of tools like -renderdoc in the beginning, which would have saved a lot of time. It's also why there's a bit of odd logic in the -implementation code because I was banging rocks together guessing how things worked in wallpaper engine. - -I took one last stab at it for about a week in mid 2023 and fixed a bunch of issues and changed the code style. -Even then, this final version still has many fundemental flaws (and bugs). - -Known Major Issues: -- some gifs are incorrectly positioned -- some objects are not scaled/positioned properly in the scene -- some shaders fail to compile for various reasons - -Not Implemented: -- mouse paralax -- camera shake -- particles -- animations/puppet warp -- javascript scripting -- 3d models -- and much more... diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..aebc4be --- /dev/null +++ b/README.txt @@ -0,0 +1,34 @@ +== mauri: Wallpaper Engine Implementation (for Linux) + +=== Building +==== Dependencies +* C++17 and C99 compiler +* meson +* lz4 +* glm +* nlohmann_json +* fftw3 `optional (audio)` +* notcurses `optional (taro)` + +==== Compiling +[source,sh] +---- +meson setup build -Dwindow=(x11|wayland) -Daudio=(enabled|disabled) -Dtaro=(enabled|disabled) +ninja -C build +---- + +=== Known Issues +- Some gifs are incorrectly positioned. +- Some objects are incorrectly scaled and/or positioned. +- Some shaders fail to compile for various reasons. + +==== Not Implemented: +- Particles +- Mouse Paralax +- Camera Shake +- Animations/Puppet Warp +- Javascript Scripting +- 3d Models +- And much more\... + +// vim: set syntax=asciidoc: diff --git a/taro/attic/sway_background_covered.diff b/attic/sway_background_covered.diff index 0dc9401..3ba8ef4 100644 --- a/taro/attic/sway_background_covered.diff +++ b/attic/sway_background_covered.diff @@ -209,7 +209,7 @@ index 44b941ca..9004c3cb 100644 return output; } diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c -index f8709a4c..36ea5fc0 100644 +index f8709a4c..1234c1cd 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -88,8 +88,12 @@ struct sway_workspace *workspace_create(struct sway_output *output, @@ -233,7 +233,7 @@ index f8709a4c..36ea5fc0 100644 list_free(workspace->current.floating); list_free(workspace->current.tiling); free(workspace); -@@ -616,6 +621,77 @@ bool workspace_is_empty(struct sway_workspace *ws) { +@@ -616,6 +621,75 @@ bool workspace_is_empty(struct sway_workspace *ws) { return true; } @@ -254,10 +254,8 @@ index f8709a4c..36ea5fc0 100644 + return false; +} + -+static int sort_containers_x_cmp(const void *_a, const void *_b) { -+ struct sway_container *a = *(void **)_a; -+ struct sway_container *b = *(void **)_b; -+ return a->current.x - b->current.x; ++static int containers_x_compare(const void *a, const void *b) { ++ return (*(struct sway_container **)a)->current.x - (*(struct sway_container **)b)->current.x; +} + +#define TERMINAL_APP_ID "kitty" @@ -284,7 +282,7 @@ index f8709a4c..36ea5fc0 100644 + ws->sorted->length = 0; + add_con_and_or_children_to_list(ws->sorted, ws->tiling); + add_con_and_or_children_to_list(ws->sorted, ws->floating); -+ list_stable_sort(ws->sorted, sort_containers_x_cmp); ++ list_stable_sort(ws->sorted, containers_x_compare); + ws->background_covered = ws->sorted->length > 0; + struct sway_container *con; + for (int i = 0; i < ws->sorted->length; ++i) { diff --git a/taro/attic/sway_test_config b/attic/sway_testing_config index 1223428..1223428 100644 --- a/taro/attic/sway_test_config +++ b/attic/sway_testing_config @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1728241625, - "narHash": "sha256-yumd4fBc/hi8a9QgA9IT8vlQuLZ2oqhkJXHPKxH/tRw=", + "lastModified": 1729070438, + "narHash": "sha256-KOTTUfPkugH52avUvXGxvWy8ibKKj4genodIYUED+Kc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c31898adf5a8ed202ce5bea9f347b1c6871f32d1", + "rev": "5785b6bb5eaae44e627d541023034e1601455827", "type": "github" }, "original": { @@ -4,7 +4,7 @@ flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem + flake-utils.lib.eachSystem [ "x86_64-linux" "i686-linux" "aarch64-linux" ] (system: let pkgs = import nixpkgs { inherit system; }; @@ -14,7 +14,30 @@ packages.default = stdenv.mkDerivation { name = "mauri"; version = "0.13"; - src = self; + src = fetchgit { + url = "https://git.akon.city/mauri"; + rev = "a14ff7afba7f9758fd1f951fcc5ce94977b52d5b"; + postFetch = '' + export NIX_SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt + + cd "$out" + ${lib.getExe meson} subprojects download + + cd "$out/subprojects/libalabaster" + ${lib.getExe meson} subprojects download + + cd "$out/subprojects/libakiyo" + ${lib.getExe meson} subprojects download + + cd "$out/subprojects/stela" + ${lib.getExe meson} subprojects download + + cd "$out" + + find subprojects -type d -name .git -prune -execdir rm -r {} + + ''; + sha256 = "sUwcHgJ9IGrarjV1DSxyD28wyRjsHabWfWZZd9f1I/s="; + }; nativeBuildInputs = [ git gcc @@ -60,6 +83,7 @@ clang-tools perf-tools valgrind + asciidoctor ]; buildInputs = packages.default.buildInputs ++ [ # sway build @@ -74,6 +98,7 @@ gdk-pixbuf xwayland xorg.xcbutilwm + xorg.xrandr # wlroots build mesa vulkan-headers @@ -87,7 +112,7 @@ shellHook = '' export NIX_SHELL="mauri" unset SOURCE_DATE_EPOCH - exec ${pkgs.zsh}/bin/zsh + exec ${pkgs.zsh}/bin/zsh -l ''; }; @@ -117,7 +142,7 @@ shellHook = '' export NIX_SHELL="mauri-mingw64" unset SOURCE_DATE_EPOCH - exec ${pkgs.zsh}/bin/zsh + exec ${pkgs.zsh}/bin/zsh -l ''; }; }); diff --git a/meson.build b/meson.build index 4a09381..458037c 100644 --- a/meson.build +++ b/meson.build @@ -8,15 +8,20 @@ is_debug = get_option('buildtype').startswith('debug') is_windows = host_machine.system() == 'windows' alabaster = subproject('libalabaster').get_variable('alabaster') -akiyo = subproject('libakiyo', default_options: ['event-loop=disabled', 'json=disabled']).get_variable('akiyo') -stela = subproject('stela', - default_options: [ - 'api=opengl', - is_windows ? 'window=glfw' : 'window=wayland', - 'poll=inline', - 'event-buffer=false', - 'pause=false' - ]).get_variable('stela') + +akiyo = subproject('libakiyo', default_options: [ + get_option('taro').enabled() ? 'event-loop=enabled' : 'event-loop=disabled', + 'curl=disabled', + 'json=enabled' +]).get_variable('akiyo') + +stela = subproject('stela', default_options: [ + 'api=opengl', + is_windows ? 'window=glfw' : 'window='+get_option('window'), + 'poll=inline', + 'event-buffer=false', + 'pause=false' +]).get_variable('stela') janus = subproject('janus').get_variable('janus') stb = subproject('stb').get_variable('stb') @@ -68,5 +73,8 @@ if is_windows and meson.is_cross_build() mauri_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows'] endif -executable('mauri', mauri_src, dependencies: mauri_deps, - cpp_args: mauri_cpp_args, link_args: mauri_link_args, install: true) +executable('mauri', mauri_src, dependencies: mauri_deps, cpp_args: mauri_cpp_args, link_args: mauri_link_args, install: true) + +if get_option('taro').enabled() + subdir('taro') +endif diff --git a/meson_options.txt b/meson_options.txt index a6dd436..ea6cec6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,3 @@ +option('window', type: 'combo', choices: ['wayland', 'x11'], value: 'wayland') +option('taro', type: 'feature', value: 'enabled') option('audio', type: 'feature', value: 'disabled') diff --git a/scripts/line_count.sh b/scripts/line_count.sh index 1598b1a..8de6ef8 100755 --- a/scripts/line_count.sh +++ b/scripts/line_count.sh @@ -1,2 +1,2 @@ #! /usr/bin/env sh -find ./src ./taro/src -type f | xargs wc -l +find ./src ./taro/src -type f -exec wc -l {} + @@ -350,22 +350,20 @@ RenderTexture::RenderTexture(bool no_interp, bool clamp, bool filtering, s32 mm_ glBindTexture(GL_TEXTURE_2D, 0); } -auto RenderTexture::upload(byte *data, s32 width, s32 height, s32 level, bool dxt) -> void +auto RenderTexture::upload(const void *ptr, s32 width, s32 height, s32 level, s32 iformat) -> void { - if (asset_manager()->prepare_output && data && level == 0) + if (asset_manager()->prepare_output && ptr && level == 0) { - this->save_pixels(data, width, height); + this->save_pixels(ptr, width, height, iformat == GL_UNSIGNED_INT_8_8_8_8); } if (!context()->is_draw_enabled()) return; this->bind(0); - s32 iformat = (dxt) ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_BYTE; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, width, height, 0, - format, iformat, data); + format, iformat, ptr); } auto RenderTexture::bind(s32 index) -> void @@ -395,10 +393,18 @@ static auto pack_red(byte r) -> u32 return (255 << 24 | 0 << 16 | 0 << 8 | r); } -auto RenderTexture::save_pixels(byte *data, s32 width, s32 height) -> void +auto RenderTexture::save_pixels(const void *ptr, s32 width, s32 height, bool dxt) -> void { this->pixels.resize(width * height); - byte *dp = data; + if (dxt) + { + for (s32 i = 0; i < width * height; i++) + { + this->pixels[i] = aki_htonl(((u32 *)ptr)[i]); + } + return; + } + byte *dp = (byte *)ptr; switch (format) { case GL_RGBA: @@ -95,11 +95,11 @@ class RenderTexture std::vector<u32> pixels; - auto upload(byte *data, s32 width, s32 height, s32 level, bool dxt = false) -> void; + auto upload(const void *ptr, s32 width, s32 height, s32 level, s32 iformat = GL_UNSIGNED_BYTE) -> void; auto bind(s32 index) -> void; private: - auto save_pixels(byte *data, s32 width, s32 height) -> void; + auto save_pixels(const void *ptr, s32 width, s32 height, bool dxt) -> void; }; class RenderFramebuffer diff --git a/src/mauri.cc b/src/mauri.cc index 737af65..74e2fb1 100644 --- a/src/mauri.cc +++ b/src/mauri.cc @@ -68,7 +68,7 @@ auto wmain(s32 argc, wchar_t **argv) -> s32 return EXIT_FAILURE; } - if (!stl_global_init()) return EXIT_FAILURE; + if (!stl_global_init(false)) return EXIT_FAILURE; if (args.found("package")) { diff --git a/src/objects/material.cc b/src/objects/material.cc index 7c36d13..8b582ff 100644 --- a/src/objects/material.cc +++ b/src/objects/material.cc @@ -31,11 +31,6 @@ auto Material::load(Engine *engine, Object *object, MaterialType type, Pass *pas { switch (type) { - case MATERIAL_EFFECT: - { - pass0->load(engine, MATERIAL_PASS, pass); - break; - } case MATERIAL_MODEL: { Pass model_pass; @@ -59,6 +54,11 @@ auto Material::load(Engine *engine, Object *object, MaterialType type, Pass *pas break; } + case MATERIAL_EFFECT: + { + pass0->load(engine, MATERIAL_PASS, pass); + break; + } } } } diff --git a/src/objects/model.cc b/src/objects/model.cc index f5ce9c4..1eb958d 100644 --- a/src/objects/model.cc +++ b/src/objects/model.cc @@ -16,6 +16,8 @@ auto Model::parse(Parser &pr, const std::string &path) -> Model * pr.map_value<bool>(root, "fullscreen", &model->fullscreen, false); pr.map_value<bool>(root, "passthrough", &model->passthrough, false); + pr.map_value<vec2>(root, "cropoffset", &model->cropoffset, vec2(0.f)); + pr.map_value<f32>(root, "width", &model->width, 0.f); pr.map_value<f32>(root, "height", &model->height, 0.f); diff --git a/src/objects/model.h b/src/objects/model.h index d000bd7..d34130b 100644 --- a/src/objects/model.h +++ b/src/objects/model.h @@ -16,6 +16,8 @@ class Model bool fullscreen = false; bool passthrough = false; + vec2 cropoffset; + f32 width; f32 height; diff --git a/src/objects/pass.cc b/src/objects/pass.cc index a7cfc09..afe7c8a 100644 --- a/src/objects/pass.cc +++ b/src/objects/pass.cc @@ -1,4 +1,5 @@ #include "../gl.h" +#include "../context.h" #include "pass.h" @@ -199,10 +200,6 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) if (this->model && i == 0 && this->textures[0]) { - if (this->textures[0]->is_gif()) - { - pass->combos.push_back(Combo("SPRITESHEET", 1)); - } this->object->create_objects(engine, this->textures[0]); objects_created = true; } @@ -222,7 +219,7 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) { // Take value as reference because "uniform_override->value" points // to the value stored in the parser. This allows changing that value - // to have an instant effect. + // to have an immediate effect. uniform->value = &uniform_override->value; break; } @@ -276,9 +273,9 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) #ifdef _DEBUG_ debug("PASS (%s)", this->command == COPY ? "copy" : "draw"); - debug(" object: %s (passthrough: %d, fullscreen %d)", this->object->name.c_str(), - this->object->passthrough, this->object->fullscreen); - debug(" visible: %d", this->object->visible); + debug(" object: %s (passthrough: %s, fullscreen %s)", this->object->name.c_str(), + AL_BOOLSTR(this->object->passthrough), AL_BOOLSTR(this->object->fullscreen)); + debug(" visible: %s", AL_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); @@ -287,7 +284,7 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) switch (this->texture_types[i]) { case STATIC: - if (pass->textures[i].empty() && !this->shader->origin()->textures[i].empty()) + 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()); } @@ -307,8 +304,8 @@ RenderPass::RenderPass(Engine *engine, Pass *pass) debug(" %s %s %s", uniform_type_to_string(uniform->type).c_str(), uniform->uname.c_str(), glm::to_string(*uniform->value).c_str()); } - debug(" model: %d", this->model); - debug(" combine: %d", this->combine); + debug(" model: %s", AL_BOOLSTR(this->model)); + debug(" combine: %s", AL_BOOLSTR(this->combine)); #endif } @@ -343,8 +340,7 @@ auto RenderPass::draw(Engine *engine) -> void Render::color_mask(1.f, 1.f, 1.f, this->combine ? 0.f : 1.f); - engine->view.default_viewport( - adjusted_target->buffer->width, adjusted_target->buffer->height); + engine->view.default_viewport(adjusted_target->buffer->width, adjusted_target->buffer->height); this->render_object->draw(); diff --git a/src/objects/scene.cc b/src/objects/scene.cc index 832cea6..21a2449 100644 --- a/src/objects/scene.cc +++ b/src/objects/scene.cc @@ -57,7 +57,6 @@ auto Scene::get_object_by_id(u32 id) -> Object * auto Scene::load(Engine *engine) -> void { - /* if (this->objects.size() > 0) { for (auto it = this->objects.begin(); it != this->objects.end();) @@ -66,7 +65,6 @@ auto Scene::load(Engine *engine) -> void else it++; } } - */ for (Object *object : this->objects) { diff --git a/src/texture.cc b/src/texture.cc index 8b7aa23..ea29bc0 100644 --- a/src/texture.cc +++ b/src/texture.cc @@ -108,25 +108,23 @@ Texture::Texture(Asset *asset) { case RGBA8888: texture->format = GL_RGBA; - texture->upload(buffer, mm_width, mm_height, i, false); + texture->upload(buffer, mm_width, mm_height, i); break; case DXT5: { - u32 *decompressed = new u32[decompressed_byte_count]; + u32 *decompressed = new u32[(mm_width + 3) * (mm_height + 3)]; BlockDecompressImageDXT5(mm_width, mm_height, buffer, decompressed); texture->format = GL_RGBA; - assert(decompressed); - texture->upload((byte *)decompressed, mm_width, mm_height, i, true); + texture->upload((byte *)decompressed, mm_width, mm_height, i, GL_UNSIGNED_INT_8_8_8_8); delete[] decompressed; break; } case DXT1: { - u32 *decompressed = new u32[decompressed_byte_count * 2]; + u32 *decompressed = new u32[(mm_width + 3) * (mm_height + 3)]; BlockDecompressImageDXT1(mm_width, mm_height, buffer, decompressed); texture->format = GL_RGBA; - assert(decompressed); - texture->upload((byte *)decompressed, mm_width, mm_height, i, true); + texture->upload((byte *)decompressed, mm_width, mm_height, i, GL_UNSIGNED_INT_8_8_8_8); delete[] decompressed; break; } @@ -3,6 +3,7 @@ extern "C" { #include <al/types.h> +#include <aki/socket.h> } #include "log.h" diff --git a/taro/subprojects/notcurses.wrap b/subprojects/notcurses.wrap index 64f6731..64f6731 100644 --- a/taro/subprojects/notcurses.wrap +++ b/subprojects/notcurses.wrap diff --git a/taro/subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff b/subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff index 71680f7..71680f7 100644 --- a/taro/subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff +++ b/subprojects/packagefiles/notcurses/duration_and_ffmpeg_fix.diff diff --git a/subprojects/packagefiles/s3tc-dxt-decompression/dxt1_default_alpha.diff b/subprojects/packagefiles/s3tc-dxt-decompression/dxt1_default_alpha.diff new file mode 100644 index 0000000..8527f9b --- /dev/null +++ b/subprojects/packagefiles/s3tc-dxt-decompression/dxt1_default_alpha.diff @@ -0,0 +1,13 @@ +diff --git a/s3tc.cpp b/s3tc.cpp +index f2642fc..88ea870 100644 +--- a/s3tc.cpp ++++ b/s3tc.cpp +@@ -83,7 +83,7 @@ void DecompressBlockDXT1(uint32_t x, uint32_t y, uint32_t width, const uint8_t * + finalColor = PackRGBA((r0+r1)/2, (g0+g1)/2, (b0+b1)/2, 255); + break; + case 3: +- finalColor = PackRGBA(0, 0, 0, 255); ++ finalColor = PackRGBA(0, 0, 0, 0); + break; + } + } diff --git a/subprojects/s3tc-dxt-decompression.wrap b/subprojects/s3tc-dxt-decompression.wrap index 2a0c5d4..cc70c7c 100644 --- a/subprojects/s3tc-dxt-decompression.wrap +++ b/subprojects/s3tc-dxt-decompression.wrap @@ -3,4 +3,4 @@ url = https://github.com/Benjamin-Dobell/s3tc-dxt-decompression.git revision = 17074c22c7857cfc4e843a07e99519a09c3a563f depth = 1 patch_directory = s3tc-dxt-decompression -diff_files = s3tc-dxt-decompression/explicit_type_sizes.diff, s3tc-dxt-decompression/compiler_warnings.diff +diff_files = s3tc-dxt-decompression/explicit_type_sizes.diff, s3tc-dxt-decompression/compiler_warnings.diff, s3tc-dxt-decompression/dxt1_default_alpha.diff diff --git a/taro/src/config.h b/taro/config.h index ddf3430..1a1f10d 100644 --- a/taro/src/config.h +++ b/taro/config.h @@ -1,6 +1,7 @@ #pragma once #include <al/str.h> +#include <al/log.h> #include "file_ext.h" @@ -19,37 +20,15 @@ static void close_config(struct config *conf) al_str_free(&conf->wallpaper_dir); } -static bool open_config(struct config *conf) +static bool parse_config_json(struct config *conf, char *envhome, json_t *root) { - conf->config_dir = al_str_zero(); - conf->wallpaper_dir = al_str_zero(); - - char *envhome = getenv("HOME"); - if (!envhome) return false; - al_str_from(&conf->config_dir, envhome); - al_str_cat(&conf->config_dir, al_str_c(CONFIG_PATH)); - if (!aki_dir_exists(&conf->config_dir)) { - if (!aki_dir_create(&conf->config_dir)) { - close_config(conf); - return false; - } - } - - str config_path; - al_str_clone(&config_path, &conf->config_dir); - al_str_cat(&config_path, al_str_c("/config.json")); - json_t *root = aki_file_open_as_json(&config_path); - al_str_free(&config_path); - if (!root) { - close_config(conf); - return false; - } + bool valid = true; json_t *wallpaper_dir = json_object_get(root, "wallpaper_directory"); - if (!wallpaper_dir) { - json_decref(root); - close_config(conf); - return false; + if (!wallpaper_dir || !json_is_string(wallpaper_dir) || al_strlen(json_string_value(wallpaper_dir)) == 0) { + al_log_error("taro", "'wallpaper_directory' not set or invalid"); + valid = false; + goto out; } const char *dir_str = json_string_value(wallpaper_dir); @@ -61,9 +40,48 @@ static bool open_config(struct config *conf) al_str_from(&conf->wallpaper_dir, dir_str); } +out: json_decref(root); + return valid; +} + +static bool open_config(struct config *conf, bool create) +{ + al_memset(conf, 0, sizeof(struct config)); + + char *envhome = getenv("HOME"); + if (!envhome) goto err; + + al_str_from(&conf->config_dir, envhome); + al_str_cat(&conf->config_dir, al_str_c(CONFIG_PATH)); + if (!aki_dir_exists(&conf->config_dir)) { + if (!create || !aki_dir_create(&conf->config_dir)) { + goto err; + } + } + + str config_path; + al_str_clone(&config_path, &conf->config_dir); + al_str_cat(&config_path, al_str_c("/config.json")); + if (!aki_file_exists(&config_path)) { + if (aki_file_create(&config_path)) { + json_t *blank = json_object(); + json_object_set_new(blank, "wallpaper_directory", json_string_nocheck("")); + aki_dump_json_and_decref(&config_path, blank); + } + } + + json_t *root = aki_file_open_as_json(&config_path); + al_str_free(&config_path); + if (!root) goto err; + + if (!parse_config_json(conf, envhome, root)) goto err; + return true; +err: + close_config(conf); + return false; } AL_UNUSED_FUNCTION_POP diff --git a/taro/src/daemon.c b/taro/daemon.c index 45eb599..d632e3d 100644 --- a/taro/src/daemon.c +++ b/taro/daemon.c @@ -5,6 +5,7 @@ #include <al/str.h> #include <aki/common.h> #include <aki/event_loop.h> +#include <aki/signal.h> #include <aki/fs_event.h> #include <aki/socket.h> #include <stl/window.h> @@ -25,11 +26,12 @@ struct monitor { struct daemon *dmon; }; -#define IPC_HEADER_LENGTH 14 -#define IPC_MESSAGE_BUF_SIZE 64 +#define SWAY_IPC_HEADER_LENGTH 14 +#define SWAY_IPC_SUBSCRIBE 2 struct daemon { struct aki_event_loop loop; + struct aki_signal quit_signal; struct aki_poll context_poll; array(struct monitor *) monitors; struct aki_socket ipc_socket; @@ -86,6 +88,16 @@ static bool send_signal_to_pid(pid_t pid, s32 signal) return true; } +static bool signal_monitor(struct monitor *monitor, s32 signal) +{ + if (monitor->pid == -1) return false; + if (!send_signal_to_pid(monitor->pid, signal)) { + monitor->pid = -1; + return false; + } + return true; +} + static pid_t exec_or_exit(char *args[]) { pid_t pid = fork(); @@ -98,6 +110,7 @@ static pid_t exec_or_exit(char *args[]) // 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); + exit(EXIT_FAILURE); } } return pid; @@ -108,13 +121,11 @@ static void maybe_kill_wallpaper(struct monitor *monitor) if (monitor->pid == -1) return; if (monitor->paused) { - if (!send_signal_to_pid(monitor->pid, SIGCONT)) { - send_signal_to_pid(monitor->pid, SIGKILL); - } + signal_monitor(monitor, SIGCONT); monitor->paused = false; } - send_signal_to_pid(monitor->pid, SIGTERM); + signal_monitor(monitor, SIGTERM); monitor->pid = -1; } @@ -149,19 +160,28 @@ static bool run_wallpaper(struct daemon *dmon, struct monitor *monitor) return false; } + if (is_package) { + al_str_cat(&wallpaper_path, al_str_c("/scene.pkg")); + } + al_log_info("tarod", "setting wallpaper %.*s on %s", AL_STR_PRINTF(&wallpaper_path), monitor->os->name); char width[8], height[8]; - al_sprintf(width, "%d", monitor->os->width / monitor->os->scale); - al_sprintf(height, "%d", monitor->os->height / monitor->os->scale); + al_snprintf(width, sizeof(width), "%d", monitor->os->width / monitor->os->scale); + al_snprintf(height, sizeof(height), "%d", monitor->os->height / monitor->os->scale); char *c_str0 = al_str_to_c_str(&wallpaper_path); al_str_free(&wallpaper_path); - monitor->pid = exec_or_exit((char *[]){ "./run_wallpaper.sh", - AL_BOOLSTR(is_package), c_str0, monitor->os->name, width, height, NULL }); + if (is_package) { + monitor->pid = exec_or_exit((char *[]){ + "mauri", "-p", c_str0, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); + } else { + monitor->pid = exec_or_exit((char *[]){ + "mauri", "-d", c_str0, "-m", monitor->os->name, "-w", width, "-h", height, NULL}); + } al_free(c_str0); - if (monitor->covered && send_signal_to_pid(monitor->pid, SIGSTOP)) { + if (monitor->covered && signal_monitor(monitor, SIGSTOP)) { monitor->paused = true; } @@ -194,13 +214,13 @@ static void output_discovered_callback(void *userdata, struct stl_monitor *os_mo } } - str config_dir; - al_str_clone(&config_dir, &dmon->conf.config_dir); - al_str_cat(&config_dir, al_str_c("/")); - al_str_cat(&config_dir, al_str_cr(os_monitor->name)); + 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 (!aki_dir_exists(&config_dir)) { - if (!aki_dir_create(&config_dir)) return; + if (!aki_dir_exists(&monitor_dir)) { + if (!aki_dir_create(&monitor_dir)) return; } monitor = al_alloc_object(struct monitor); @@ -208,8 +228,8 @@ static void output_discovered_callback(void *userdata, struct stl_monitor *os_mo monitor->dmon = dmon; al_array_push(dmon->monitors, monitor); - al_str_clone(&monitor->config_dir, &config_dir); - al_str_free(&config_dir); + al_str_clone(&monitor->config_dir, &monitor_dir); + al_str_free(&monitor_dir); al_str_clone(&monitor->selection_path, &monitor->config_dir); al_str_cat(&monitor->selection_path, al_str_c("/selection")); @@ -273,9 +293,9 @@ static void ipc_handle_response(struct daemon *dmon, u8 *buf, u32 size) if (strcmp(monitor->os->name, json_string_value(output)) != 0) continue; monitor->covered = covered; if (monitor->pid == -1) goto done; - if (covered && !monitor->paused && send_signal_to_pid(monitor->pid, SIGSTOP)) { + if (covered && !monitor->paused && signal_monitor(monitor, SIGSTOP)) { monitor->paused = true; - } else if (!covered && monitor->paused && send_signal_to_pid(monitor->pid, SIGCONT)) { + } else if (!covered && monitor->paused && signal_monitor(monitor, SIGCONT)) { monitor->paused = false; } break; @@ -289,7 +309,7 @@ done: static void ensure_ipc_buf_size(struct daemon *dmon, ssize_t size) { if (dmon->ipc.alloc < size) { - dmon->ipc.buf = realloc(dmon->ipc.buf, size); + dmon->ipc.buf = al_realloc(dmon->ipc.buf, size); dmon->ipc.alloc = size; } } @@ -316,20 +336,20 @@ static void ipc_poll_callback(void *userdata, s32 revents) } if (revents & AKI_POLL_READ) { - if (dmon->ipc.index < IPC_HEADER_LENGTH) { + if (dmon->ipc.index < SWAY_IPC_HEADER_LENGTH) { dmon->ipc.index += aki_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index, - IPC_HEADER_LENGTH - dmon->ipc.index); + SWAY_IPC_HEADER_LENGTH - dmon->ipc.index); } - if (dmon->ipc.index == IPC_HEADER_LENGTH && !dmon->ipc.size) { - dmon->ipc.size = *((s32 *)&dmon->ipc.buf[6]) + IPC_HEADER_LENGTH; + if (dmon->ipc.index == SWAY_IPC_HEADER_LENGTH && !dmon->ipc.size) { + dmon->ipc.size = *((s32 *)&dmon->ipc.buf[6]) + SWAY_IPC_HEADER_LENGTH; ensure_ipc_buf_size(dmon, dmon->ipc.size); } if (dmon->ipc.size) { dmon->ipc.index += aki_socket_read(&dmon->ipc_socket, dmon->ipc.buf + dmon->ipc.index, dmon->ipc.size - dmon->ipc.index); if (dmon->ipc.index == dmon->ipc.size) { - ipc_handle_response(dmon, dmon->ipc.buf + IPC_HEADER_LENGTH, - dmon->ipc.size - IPC_HEADER_LENGTH); + 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; } @@ -344,39 +364,56 @@ static void context_poll_callback(void *userdata, s32 revents) stl_global_process_events(); } +static void quit_signal_callback(void *userdata) +{ + struct daemon *dmon = (struct daemon *)userdata; + aki_event_loop_break_one(&dmon->loop); +} + +static struct daemon dmon = { 0 }; // https://stackoverflow.com/a/59071370 -static void handle_sigchld(s32 sig) +static void sigchld_handler(s32 signum) { - (void)sig; + (void)signum; s32 saved_errno = errno; while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {} errno = saved_errno; } -#define SWAY_IPC_SUBSCRIBE 2 +static void sigint_handler(s32 signum) +{ + (void)signum; + aki_signal_send(&dmon.quit_signal); +} -static struct daemon dmon = { 0 }; +// TODO: +// - daemon quit +// - systemd? and related testing s32 main(void) { - if (!aki_common_init()) return EXIT_FAILURE; + if (!aki_common_init() || !open_config(&dmon.conf, true)) return EXIT_FAILURE; - signal(SIGCHLD, handle_sigchld); + signal(SIGINT, sigint_handler); + signal(SIGCHLD, sigchld_handler); s32 ret = EXIT_FAILURE; - if (!open_config(&dmon.conf)) goto out; - aki_event_loop_init(&dmon.loop); - al_array_init(dmon.monitors); + aki_signal_init(&dmon.quit_signal, quit_signal_callback, &dmon); + aki_signal_start(&dmon.quit_signal, &dmon.loop); + al_array_init(dmon.monitors); stl_set_output_discovered_callback(output_discovered_callback, &dmon); - if (!stl_global_init()) return EXIT_FAILURE; - aki_poll_init(&dmon.context_poll, context_poll_callback, &dmon); - aki_poll_set(&dmon.context_poll, stl_global_get_poll_fd(), AKI_POLL_READ); - aki_poll_start(&dmon.context_poll, &dmon.loop); + if (!stl_global_init(true)) goto out; + s32 poll_fd = stl_global_get_poll_fd(); + if (poll_fd != -1) { + aki_poll_init(&dmon.context_poll, context_poll_callback, &dmon); + aki_poll_set(&dmon.context_poll, poll_fd, AKI_POLL_READ); + aki_poll_start(&dmon.context_poll, &dmon.loop); + } str monitors; al_str_from(&monitors, ""); @@ -389,28 +426,35 @@ s32 main(void) str monitors_path; al_str_clone(&monitors_path, &dmon.conf.config_dir); al_str_cat(&monitors_path, al_str_c("/monitors")); - if (!aki_file_exists(&monitors_path)) { - aki_file_create(&monitors_path); + bool monitors_exists = aki_file_exists(&monitors_path) || aki_file_create(&monitors_path); + if (monitors_exists) { + aki_file_open_and_replace(&monitors_path, &monitors, false); } - aki_file_open_and_replace(&monitors_path, &monitors, false); + al_str_free(&monitors_path); + al_str_free(&monitors); + if (!monitors_exists) goto out; dmon.ipc.buf = NULL; dmon.ipc.size = 0; dmon.ipc.index = 0; dmon.ipc.alloc = 0; - ensure_ipc_buf_size(&dmon, IPC_HEADER_LENGTH); + ensure_ipc_buf_size(&dmon, SWAY_IPC_HEADER_LENGTH); - json_t *payload = json_array(); - json_array_append(payload, json_string("background_covered")); - dmon.out.buf = al_malloc(IPC_MESSAGE_BUF_SIZE); - al_memcpy(dmon.out.buf, "i3-ipc", strlen("i3-ipc")); + 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)); - s32 len = json_dumpb(payload, (char *)&dmon.out.buf[14], IPC_MESSAGE_BUF_SIZE - IPC_HEADER_LENGTH, 0); + + 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 = IPC_HEADER_LENGTH + len; + + dmon.out.size = SWAY_IPC_HEADER_LENGTH + len; dmon.out.index = 0; - json_decref(payload); dmon.ipc_socket.type = AKI_SOCKET_UNIX; aki_socket_init(&dmon.ipc_socket); @@ -422,7 +466,7 @@ s32 main(void) if (swaysock && aki_socket_connect(&dmon.ipc_socket, al_str_cr(swaysock), 0)) { aki_poll_start(&dmon.ipc_poll, &dmon.loop); } else { - al_log_error("tarod", "failed to connect to sway ipc socket"); + al_log_warn("tarod", "couldn't connect to sway ipc socket"); } aki_event_loop_run(&dmon.loop); @@ -431,15 +475,18 @@ s32 main(void) close_monitor(monitor); } al_array_free(dmon.monitors); + al_free(dmon.ipc.buf); + aki_socket_close(&dmon.ipc_socket); aki_event_loop_destroy(&dmon.loop); - close_config(&dmon.conf); - ret = EXIT_SUCCESS; out: + close_config(&dmon.conf); + + stl_global_close(); aki_common_close(); return ret; diff --git a/taro/src/db.c b/taro/db.c index 9d2ffa2..5202df8 100644 --- a/taro/src/db.c +++ b/taro/db.c @@ -147,17 +147,14 @@ bool db_ensure_entry_loaded(struct db *db, struct entry *e) return e->loaded; } -static s32 id_compare(void *a, void *b) +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; - } else { - return 0; - } + if (aid > bid) return 1; + else if (aid < bid) return -1; + return 0; } bool db_load_entries(struct db *db, str *path) @@ -189,7 +186,7 @@ bool db_load_entries(struct db *db, str *path) al_array_push(db->entries, e); } - al_array_sort(db->entries, struct entry, id_compare); + al_array_sort(db->entries, struct entry, wallpaper_id_compare); return true; } diff --git a/taro/src/db.h b/taro/db.h index 78f4c77..78f4c77 100644 --- a/taro/src/db.h +++ b/taro/db.h diff --git a/taro/src/file_ext.c b/taro/file_ext.c index 48dffce..3091fa4 100644 --- a/taro/src/file_ext.c +++ b/taro/file_ext.c @@ -39,7 +39,7 @@ json_t *aki_file_open_as_json(str *path) return root; } -bool aki_file_dump_and_decref_json(str *path, json_t *root) +bool aki_dump_json_and_decref(str *path, json_t *root) { str s; size_t size = json_dumpb(root, NULL, 0, JSON_INDENT(4)); diff --git a/taro/src/file_ext.h b/taro/file_ext.h index ddaceba..9398eb3 100644 --- a/taro/src/file_ext.h +++ b/taro/file_ext.h @@ -4,4 +4,4 @@ bool aki_file_open_and_read_wholly(str *path, str *out, bool lock); bool aki_file_open_and_replace(str *path, str *buf, bool lock); json_t *aki_file_open_as_json(str *path); -bool aki_file_dump_and_decref_json(str *path, json_t *root); +bool aki_dump_json_and_decref(str *path, json_t *root); diff --git a/taro/meson.build b/taro/meson.build index 0291648..935678e 100644 --- a/taro/meson.build +++ b/taro/meson.build @@ -1,10 +1,3 @@ -project('taro', 'c', version: '0.01', - default_options: ['warning_level=2', 'c_std=c99']) - -cmake = import('cmake') - -is_debug = get_option('buildtype').startswith('debug') - notcurses = dependency('notcurses', required: false, allow_fallback: false) notcurses_core = dependency('notcurses-core', required: false, allow_fallback: false) if not notcurses.found() or not notcurses_core.found() @@ -19,27 +12,11 @@ if not notcurses.found() or not notcurses_core.found() notcurses_core = notcurses_proj.dependency('notcurses-core') endif -akiyo = subproject('libakiyo', - default_options: [ - 'curl=disabled', - 'json=enabled', - 'event-loop=enabled' - ]).get_variable('akiyo') - -stela = subproject('stela', - default_options: [ - 'api=null', - 'window=wayland', - 'poll=fd', - 'event-buffer=false', - 'pause=false' - ]).get_variable('stela') - taro_deps = [akiyo, notcurses, notcurses_core] -taro_src = ['src/ui.c', 'src/db.c', 'src/file_ext.c'] +taro_src = ['ui.c', 'db.c', 'file_ext.c'] tarod_deps = [akiyo, stela] -tarod_src = ['src/daemon.c', 'src/file_ext.c'] +tarod_src = ['daemon.c', 'file_ext.c'] executable('taro', taro_src, dependencies: taro_deps, install: true) executable('tarod', tarod_src, dependencies: tarod_deps, install: true) diff --git a/taro/scripts/run_wallpaper.sh b/taro/scripts/run_wallpaper.sh deleted file mode 100755 index a274889..0000000 --- a/taro/scripts/run_wallpaper.sh +++ /dev/null @@ -1,6 +0,0 @@ -#! /usr/bin/env sh -if [ "$1" = "true" ]; then - exec mauri -p $2/scene.pkg -m $3 -w $4 -h $5 -else - exec mauri -d $2 -m $3 -w $4 -h $5 -fi diff --git a/taro/subprojects/libakiyo.wrap b/taro/subprojects/libakiyo.wrap deleted file mode 100644 index 2cd2caa..0000000 --- a/taro/subprojects/libakiyo.wrap +++ /dev/null @@ -1,4 +0,0 @@ -[wrap-git] -url = https://git.akon.city/libakiyo -revision = master -depth = 1 diff --git a/taro/subprojects/libalabaster.wrap b/taro/subprojects/libalabaster.wrap deleted file mode 100644 index c0358cf..0000000 --- a/taro/subprojects/libalabaster.wrap +++ /dev/null @@ -1,4 +0,0 @@ -[wrap-git] -url = https://git.akon.city/libalabaster -revision = master -depth = 1 diff --git a/taro/subprojects/stela.wrap b/taro/subprojects/stela.wrap deleted file mode 100644 index 7807a8c..0000000 --- a/taro/subprojects/stela.wrap +++ /dev/null @@ -1,4 +0,0 @@ -[wrap-git] -url = https://git.akon.city/stela -revision = master -depth = 1 diff --git a/taro/src/ui.c b/taro/ui.c index b2c700e..5bcaf1d 100644 --- a/taro/src/ui.c +++ b/taro/ui.c @@ -1,9 +1,11 @@ #include <notcurses/notcurses.h> #include <notcurses/nckeys.h> +#include <al/log.h> +#include <al/lib.h> +#include <al/random.h> #include <aki/common.h> #include <aki/event_loop.h> #include <aki/timer.h> -#include <al/log.h> #include "db.h" #include "file_ext.h" @@ -13,36 +15,39 @@ #define TIMER_UPDATE_BASE 1.f -#define TILE_WIDTH 18 -#define TILE_HEIGHT 10 -#define FLASH_DURATION 0.3f +#define TEXT_UPDATE_RATE 0.4f +#define FLASH_DURATION 0.4f + +#define UI_RESERVE_WIDTH 33 +#define UI_RESERVE_HEIGHT 2 -#define TEXT_WIDTH (TILE_WIDTH - 4) -#define TEXT_UPDATE_RATE 0.3f; +#define GRID_TILE_WIDTH 18 // img = 16 +#define GRID_TILE_HEIGHT 10 // img = 8 -#define INFO_RES_WIDTH 33 -#define INFO_RES_HEIGHT 2 +#define GRID_TEXT_WIDTH (GRID_TILE_WIDTH - 4) #define INFO_X_OFFSET 2 #define INFO_Y_OFFSET 1 #define INFO_WIDTH 30 -// INFO_HEIGHT = term_rows + INFO_RES_HEIGHT +// INFO_HEIGHT = term_rows + UI_RESERVE_HEIGHT -#define INFO_TILE_WIDTH 24 -#define INFO_TILE_HEIGHT 12 +#define INFO_IMG_WIDTH 24 +#define INFO_IMG_HEIGHT 12 -#define INFO_TEXT_WIDTH (TEXT_WIDTH + 4) +#define INFO_TEXT_WIDTH (GRID_TEXT_WIDTH + 6) #define PROP_TEXT_WIDTH 16 -#define MAX_MONITOR_NAME_LEN 255 +#define MAX_MONITOR_NAME 255 struct scrolling_text { wstr *s; + s32 y; + s32 x; s32 width; - s32 index, max; - s32 x, y; + u32 index; + s32 skip; }; struct image { @@ -75,13 +80,14 @@ struct monitors { struct grid { struct ncplane *p; - s32 tiles_x, tiles_y; + s32 tiles_x; + s32 tiles_y; s32 min_index; s32 max_index; s32 selected_index; s32 set_index; f32 flash; - s32 tiles_per_page; + u32 tiles_per_page; array(struct tile) tiles; }; @@ -114,7 +120,8 @@ struct ui { struct aki_timer timer; bool layed_out; f32 text_update; - u32 term_rows, term_cols; + u32 term_rows; + u32 term_cols; u8 selected_menu; struct ncplane *p, *mp, *gp, *ip; struct monitors m; @@ -200,79 +207,66 @@ static void write_selected_entry_to_config(struct ui *u) // Interface // --------- -static void set_scrolling_text(struct scrolling_text *text, wstr *s, s32 text_width, s32 width, s32 x, s32 y) +static bool wide_string_to_width(wstr *w, u32 index, s32 max_width, void (*func)(void *, wchar_t), void *userdata) { - text->index = 0; - text->width = width; - text->s = s; - if (text_width > text->width) { - text->max = (text_width - text->width); - // If the character at text->index is wider than 1 cell, - // that is the amount the text will scroll by. So, account - // for that here. - for (s32 i = 0; i < text->max; i++) { - s32 wcl = al_wchar_width(al_wstr_at(s, i)); - if (wcl > 1) { - text->max -= wcl - 1; - } + 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; } - text->max++; - } else { - text->max = -1; - } - text->x = x; - text->y = y; + func(userdata, wc); + } while (++i < w->len); + return i == w->len; } -static void draw_scrolling_text(struct ncplane *p, struct scrolling_text *t, bool update) +static void set_scrolling_text(struct scrolling_text *text, wstr *s, s32 y, s32 x, s32 width) { - ncplane_cursor_move_yx(p, t->y, t->x); - if (t->max != -1) { - s32 i = 0; - s32 width = 0; - while (width < t->width) { - wchar_t wc = al_wstr_at(t->s, t->index + i); - width += al_wchar_width(wc); - if (width > t->width) { - break; - } - ncplane_putwc(p, wc); - i++; - } - if (update) { - t->index = (t->index + 1) % t->max; - } - } else { - ncplane_putwstr(p, &al_wstr_at(t->s, 0)); - } + 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 set_info_entry(struct ui *u, struct info *n) +static void draw_func(void *userdata, wchar_t wc) { - n->selected_prop = 0; - n->props.size = 0; + struct ncplane *p = (struct ncplane *)userdata; + ncplane_putwc(p, wc); +} - if (u->g.selected_index == -1 || u->g.selected_index >= (s32)u->db.entries.size) { - n->e = NULL; - return; +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, width, INFO_TEXT_WIDTH, tx, INFO_TILE_HEIGHT + 1); + 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) { - struct info_prop ip = { 0 }; - ip.p = p; - width = al_wstr_width(&p->title); - s32 ty = (INFO_TILE_HEIGHT + 3) + ((i % n->max_props) * 2); - set_scrolling_text(&ip.text, &p->title, width, PROP_TEXT_WIDTH, 0, ty); - al_array_push(n->props, ip); + 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); } } @@ -296,9 +290,9 @@ static bool init_grid(struct grid *g) return true; } -static s32 monitor_cmp(void *va, void *vb) +static s32 monitor_cmp(const void *va, const void *vb) { - return strncmp(*(char **)va, *(char **)vb, MAX_MONITOR_NAME_LEN); + return strncmp(*(char **)va, *(char **)vb, MAX_MONITOR_NAME); } static bool init_monitors(struct ui *u, struct monitors *m) @@ -311,7 +305,7 @@ static bool init_monitors(struct ui *u, struct monitors *m) bool ret = aki_file_open_and_read_wholly(&monitors_path, &monitors, false); al_str_free(&monitors_path); if (!ret) { - al_log_error("taro", "failed to open monitor list, is the daemon configured?"); + al_log_error("taro", "failed to open monitor list, has `tarod` run?"); return false; } str monitor_dir; @@ -337,6 +331,8 @@ static bool init_monitors(struct ui *u, struct monitors *m) } if (u->db.entries.size > 0) { u->g.selected_index = (u->g.set_index >= 0) ? u->g.set_index : 0; + } else { + u->g.selected_index = -1; } return true; } @@ -373,12 +369,10 @@ struct image *get_image_from_pool(struct image_pool *pool, str *path) } if (pool->images.size >= pool->max) { - // al_array_remove_range()... for (u32 i = 0; i < pool->chunk_size; i++) { - struct image img; - al_array_pop_at(pool->images, 0, img); - ncvisual_destroy(img.v); + ncvisual_destroy(al_array_at(pool->images, i).v); } + al_array_remove_range(pool->images, 0, pool->chunk_size); } char *c_str = al_str_to_c_str(path); @@ -436,19 +430,17 @@ static void maybe_destroy_info(struct info *n) } } -static bool layout_info(struct ui *u, struct info *n, struct ncplane *parent, s32 width, s32 height) +static bool layout_info(struct ui *u, struct info *n, struct ncplane *parent, s32 height) { - (void)width; - maybe_destroy_info(n); - n->max_props = (height - INFO_TILE_HEIGHT - 3) / 2; + n->max_props = (height - INFO_IMG_HEIGHT - 3) / 2; if (!n->max_props) return false; struct ncplane_options nopts = { 0 }; - nopts.cols = INFO_TILE_WIDTH; - nopts.rows = INFO_TILE_HEIGHT; + nopts.cols = INFO_IMG_WIDTH; + nopts.rows = INFO_IMG_HEIGHT; nopts.x = 0; nopts.y = 1; @@ -458,7 +450,9 @@ static bool layout_info(struct ui *u, struct info *n, struct ncplane *parent, s3 return false; } - set_info_entry(u, &u->n); + if (u->g.selected_index >= 0) { + set_info_entry(u, &u->n); + } return true; } @@ -492,27 +486,35 @@ static void set_grid_tiles(struct ui *u, struct grid *g) t->blitted = false; ncplane_erase(t->p); - if (g->max_index >= (s32)u->db.entries.size) { + while (g->max_index < (s32)u->db.entries.size) { + t->e = &al_array_at(u->db.entries, g->max_index); + if (db_ensure_entry_loaded(&u->db, t->e)) { + break; + } + } + + al_assert(g->max_index <= (s32)u->db.entries.size); + + if (g->max_index == (s32)u->db.entries.size) { t->e = NULL; t->text.s = NULL; ncplane_erase(t->ip); continue; } - t->e = &al_array_at(u->db.entries, g->max_index); - if (!db_ensure_entry_loaded(&u->db, t->e)) { - x--; - continue; - } - s32 width = al_wstr_width(&t->e->title); - s32 tx = 2 + ((width < TEXT_WIDTH) ? (TEXT_WIDTH - width) / 2 : 0); - set_scrolling_text(&t->text, &t->e->title, width, TEXT_WIDTH, tx, TILE_HEIGHT - 1); + 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++; }} 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; } static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s32 width, s32 height) @@ -522,8 +524,8 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 s32 inner_width = width; s32 inner_height = height; - g->tiles_x = (s32)(width / TILE_WIDTH); - g->tiles_y = (s32)(height / TILE_HEIGHT); + g->tiles_x = width / GRID_TILE_WIDTH; + g->tiles_y = height / GRID_TILE_HEIGHT; if (!g->tiles_x || !g->tiles_y) { // Window is too small. @@ -532,8 +534,8 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 g->tiles_per_page = g->tiles_x * g->tiles_y; - // Allow 4 pages of tiles in the pool, but never shrink. - if (u->pool.chunk_size < (u32)g->tiles_per_page) { + // Reserve space for 4 pages, but never shrink. + if (u->pool.chunk_size < g->tiles_per_page) { u->pool.chunk_size = g->tiles_per_page; u->pool.max = u->pool.chunk_size * 4; al_array_reserve(u->pool.images, u->pool.max); @@ -551,8 +553,8 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 nopts.cols = inner_width; nopts.rows = inner_height; - nopts.x = (width - (g->tiles_x * TILE_WIDTH)) / 2; - nopts.y = (height - (g->tiles_y * TILE_HEIGHT)) / 2; + nopts.x = (width - (g->tiles_x * GRID_TILE_WIDTH)) / 2; + nopts.y = (height - (g->tiles_y * GRID_TILE_HEIGHT)) / 2; g->p = ncplane_create(parent, &nopts); if (!g->p) return false; @@ -561,10 +563,10 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 for (s32 x = 0; x < g->tiles_x; x++) { struct tile t = { 0 }; - nopts.cols = TILE_WIDTH; - nopts.rows = TILE_HEIGHT; - nopts.x = x * TILE_WIDTH; - nopts.y = y * TILE_HEIGHT; + nopts.cols = GRID_TILE_WIDTH; + nopts.rows = GRID_TILE_HEIGHT; + nopts.x = x * GRID_TILE_WIDTH; + nopts.y = y * GRID_TILE_HEIGHT; t.p = ncplane_create(g->p, &nopts); if (!t.p) { @@ -572,8 +574,8 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 return false; } - nopts.cols = TILE_WIDTH - 2; - nopts.rows = TILE_HEIGHT - 2; + nopts.cols = GRID_TILE_WIDTH - 2; + nopts.rows = GRID_TILE_HEIGHT - 2; nopts.x = 1; nopts.y = 1; @@ -586,7 +588,9 @@ static bool layout_grid(struct ui *u, struct grid *g, struct ncplane *parent, s3 al_array_push(g->tiles, t); }} - set_grid_tiles(u, g); + if (g->selected_index >= 0) { + set_grid_tiles(u, g); + } return true; } @@ -597,14 +601,17 @@ static void maybe_destroy_ui(struct ui *u) ncplane_destroy(u->ip); u->ip = NULL; } + if (u->gp) { ncplane_destroy(u->gp); u->gp = NULL; } + if (u->mp) { ncplane_destroy(u->mp); u->mp = NULL; } + if (u->p) { ncplane_destroy(u->p); u->p = NULL; @@ -615,10 +622,10 @@ bool layout_ui(struct ui *u) { bool even = u->term_cols % 2 == 0; - u32 rwidth = (even) ? INFO_RES_WIDTH + 1 : INFO_RES_WIDTH; - u32 rheight = (even) ? INFO_RES_HEIGHT + 1 : INFO_RES_HEIGHT; + u32 rwidth = (even) ? UI_RESERVE_WIDTH + 1 : UI_RESERVE_WIDTH; + u32 rheight = (even) ? UI_RESERVE_HEIGHT + 1 : UI_RESERVE_HEIGHT; - if (u->term_cols < TILE_WIDTH + rwidth || u->term_rows < TILE_HEIGHT + rheight) { + if (u->term_cols < GRID_TILE_WIDTH + rwidth || u->term_rows < GRID_TILE_HEIGHT + rheight) { return false; } @@ -634,8 +641,8 @@ bool layout_ui(struct ui *u) return false; } - s32 cols = u->term_cols - rwidth; - s32 rows = u->term_rows - rheight; + s32 cols = (s32)u->term_cols - rwidth; + s32 rows = (s32)u->term_rows - rheight; if (cols < 0 || rows < 0) { maybe_destroy_ui(u); @@ -669,7 +676,7 @@ bool layout_ui(struct ui *u) return false; } - nopts.x = nopts.cols + ((even) ? INFO_X_OFFSET + 1 : INFO_X_OFFSET); + nopts.x = nopts.cols + (even ? INFO_X_OFFSET + 1 : INFO_X_OFFSET); nopts.y = INFO_Y_OFFSET; nopts.cols = INFO_WIDTH; nopts.rows = u->term_rows - rheight; @@ -680,7 +687,7 @@ bool layout_ui(struct ui *u) return false; } - if (!layout_info(u, &u->n, u->ip, nopts.cols, nopts.rows)) { + if (!layout_info(u, &u->n, u->ip, nopts.rows)) { maybe_destroy_ui(u); return false; } @@ -704,15 +711,15 @@ void draw_monitors(struct ui *u, struct monitors *m) ncplane_cursor_move_yx(u->mp, 0, x); al_array_foreach(m->monitors, i, monitor) { if (m->flash <= 0.f && u->selected_menu == MENU_MONITORS && i == (u32)m->selected_monitor) { - ncplane_set_bg_rgb8(u->mp, 211, 184, 152); - ncplane_set_fg_rgb8(u->mp, 13, 22, 45); + ncplane_set_bg_palindex(u->mp, 8); + ncplane_set_fg_palindex(u->mp, 0); } else if (i == (u32)m->set_monitor) { - ncplane_set_bg_rgb8(u->mp, 242, 233, 215); - ncplane_set_fg_rgb8(u->mp, 13, 22, 45); + ncplane_set_bg_palindex(u->mp, 7); + ncplane_set_fg_palindex(u->mp, 0); m->flash -= AL_MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f); } else { - ncplane_set_fg_rgb8(u->mp, 0xff, 0xff, 0xff); - ncplane_set_bg_palindex(u->mp, 0); + ncplane_set_fg_default(u->mp); + ncplane_set_bg_default(u->mp); } ncplane_cursor_move_rel(u->mp, 0, 1); ncplane_putstr(u->mp, monitor); @@ -726,21 +733,25 @@ void draw_info(struct ui *u, struct info *n, bool update_text) ncplane_erase(u->ip); struct ncvisual_options vopts = { 0 }; - vopts.scaling = NCSCALE_SCALE; + vopts.scaling = 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) { ncplane_erase(n->ip); img->errored = img->gif && ncvisual_decode_loop(img->v) < 0; - u->delay = ncvisual_get_duration(img->v); if (!img->errored) { vopts.n = n->ip; ncvisual_blit(u->nc, img->v, &vopts); n->blitted = !img->gif; + u->delay = ncvisual_get_duration(img->v); + if (u->delay == 10000) { + // Default 100fps gifs to 10fps, seems like some weird wallpaper engine behavior. + u->delay = 100000; + } } } - ncplane_set_fg_rgb8(u->ip, 0xff, 0xff, 0xff); + ncplane_set_fg_default(u->ip); draw_scrolling_text(u->ip, &n->text, update_text); u32 selected_index = n->selected_prop - (n->selected_prop % n->max_props); @@ -753,9 +764,9 @@ void draw_info(struct ui *u, struct info *n, bool update_text) } ip = &al_array_at(n->props, index); if (u->selected_menu == MENU_INFO && index == (u32)n->selected_prop) { - ncplane_set_fg_rgb8(u->ip, 211, 184, 152); + ncplane_set_fg_palindex(u->ip, 8); } else { - ncplane_set_fg_rgb8(u->ip, 0xff, 0xff, 0xff); + ncplane_set_fg_default(u->ip); } draw_scrolling_text(u->ip, &ip->text, update_text); switch (ip->p->type) { @@ -765,31 +776,31 @@ void draw_info(struct ui *u, struct info *n, bool update_text) break; case PROP_TYPE_SLIDER: { char c[16]; - sprintf(c, "%.2f", ((struct slider_value *)ip->p->value)->value); + al_snprintf(c, sizeof(c), "%.2f", ((struct slider_value *)ip->p->value)->value); ncplane_putstr_yx(u->ip, ip->text.y, ip->text.width + 2, c); break; } } } if (ip && i == n->max_props && i != n->props.size) { - ncplane_putstr_yx(u->ip, ip->text.y + 1, (INFO_TILE_WIDTH - 1) / 2, "V"); + ncplane_putstr_yx(u->ip, ip->text.y + 1, (INFO_IMG_WIDTH - 1) / 2, "V"); } } -static void set_highlighted_style(struct tile *t, s32 br, s32 bg, s32 bb, s32 fr, s32 fg, s32 fb) +static void set_tile_highlight(struct tile *t, s32 bindex, s32 findex) { - ncplane_set_fg_rgb8(t->p, br, bg, bb); ncplane_set_bg_alpha(t->p, NCALPHA_TRANSPARENT); - u64 c = 0; - ncchannels_set_bg_rgb8(&c, br, bg, bb); - ncchannels_set_fg_rgb8(&c, br, bg, bb); - c = ncchannels_reverse(c); - ncplane_gradient(t->p, 1, 0, TILE_HEIGHT - 1, TILE_WIDTH, "", 0, c, c, c, c); - for (s32 i = 0; i < TILE_WIDTH; i++) { - ncplane_putwc_yx(t->p, 0, i, L'▂'); + struct nccell ncl; + nccell_init(&ncl); + nccell_load(t->p, &ncl, " "); + nccell_set_bg_palindex(&ncl, bindex); + ncplane_polyfill_yx(t->p, 1, 0, &ncl); + ncplane_set_fg_palindex(t->p, bindex); + for (s32 i = 0; i < GRID_TILE_WIDTH; i++) { + ncplane_putwc_yx(t->p, 0, i, L'▃'); } - ncplane_set_bg_rgb8(t->p, br, bg, bb); - ncplane_set_fg_rgb8(t->p, fr, fg, fb); + ncplane_set_fg_palindex(t->p, findex); + ncplane_set_bg_palindex(t->p, bindex); ncplane_set_bg_alpha(t->p, NCALPHA_OPAQUE); ncplane_set_styles(t->p, NCSTYLE_BOLD); } @@ -799,15 +810,13 @@ void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text) if (!u->layed_out) return; struct ncvisual_options vopts = { 0 }; - vopts.scaling = NCSCALE_SCALE; + vopts.scaling = 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) continue; - if (t->e->has_preview) { + if (t->e && 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; @@ -815,16 +824,17 @@ void draw_grid_tiles(struct ui *u, struct grid *g, bool update_text) t->blitted = true; } } - if (g->flash <= 0.f && u->selected_menu == MENU_GRID && index == g->selected_index) { - set_highlighted_style(t, 211, 184, 152, 13, 22, 45); - } else if (g->set_index >= 0 && index == g->set_index) { - set_highlighted_style(t, 242, 233, 215, 13, 22, 45); + 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) { + set_tile_highlight(t, 7, 0); g->flash -= AL_MAX(TIMER_UPDATE_BASE * (u->delay / 1000000.f), 0.001f); } else { - ncplane_erase(t->p); - ncplane_set_fg_rgb8(t->p, 0xff, 0xff, 0xff); - ncplane_set_bg_palindex(t->p, 0); - ncplane_set_styles(t->p, 0); + ncplane_set_fg_default(t->p); + ncplane_set_bg_default(t->p); + ncplane_set_bg_alpha(t->p, NCALPHA_TRANSPARENT); + ncplane_set_styles(t->p, NCSTYLE_NONE); } if (t->text.s && t->text.s->len) { draw_scrolling_text(t->p, &t->text, update_text); @@ -946,6 +956,7 @@ 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 (g->selected_index == max) { return false; @@ -959,7 +970,7 @@ static bool grid_next_page(struct ui *u, struct grid *g, s32 offset) static bool grid_previous_page(struct grid *g, s32 offset) { - if (g->selected_index == 0) { + if (g->selected_index <= 0) { return false; } g->selected_index -= g->tiles_per_page - offset; @@ -1020,14 +1031,18 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu should_redraw_grid = true; break; case 'b': - should_update_grid = grid_previous_page(&u->g, 0); - should_redraw_info = true; - should_redraw_grid = true; + if (grid_previous_page(&u->g, 0)) { + should_update_grid = true; + should_redraw_info = true; + should_redraw_grid = true; + } break; case 'f': - should_update_grid = grid_next_page(u, &u->g, 0); - should_redraw_info = true; - should_redraw_grid = true; + if (grid_next_page(u, &u->g, 0)) { + should_update_grid = true; + should_redraw_info = true; + should_redraw_grid = true; + } break; case 'r': g->set_index = -1; @@ -1035,6 +1050,14 @@ static bool handle_input_grid(struct ui *u, struct grid *g, struct ncinput *inpu should_redraw_grid = true; should_redraw_info = true; break; + case 's': { + s32 max = (s32)u->db.entries.size - 1; + g->selected_index = al_rand() % max; + should_update_grid = true; + should_redraw_info = true; + should_redraw_grid = true; + break; + } case NCKEY_RETURN: case NCKEY_SPACE: { if (g->selected_index >= 0) { @@ -1152,15 +1175,16 @@ s32 main(void) s32 ret = EXIT_FAILURE; - if (!open_config(&u.conf)) goto out; + if (!open_config(&u.conf, false)) { + al_log_error("taro", "failed to open config, run `tarod` at least once to generate needed config"); + goto out; + } if (!db_load_entries(&u.db, &u.conf.wallpaper_dir)) { - close_config(&u.conf); goto out; } if (!init_ui(&u)) { - close_config(&u.conf); goto out; } @@ -1171,7 +1195,6 @@ s32 main(void) if (!layout_ui(&u)) { notcurses_stop(u.nc); - close_config(&u.conf); al_log_error("taro", "failed to layout ui, your terminal window is probably too small"); goto out; } @@ -1192,11 +1215,11 @@ s32 main(void) notcurses_stop(u.nc); - close_config(&u.conf); - ret = EXIT_SUCCESS; out: + close_config(&u.conf); + aki_common_close(); return ret; |