From a3eb2dd9dc2399ce9c22cd3b07f482f080976440 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 11 Jul 2024 15:11:28 +1000 Subject: WIP improve bluetooth api and settings screen --- src/tangara/lua/property.cpp | 30 +++++++++++------------------- src/tangara/lua/property.hpp | 4 ++-- 2 files changed, 13 insertions(+), 21 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/property.cpp b/src/tangara/lua/property.cpp index 2b93809d..7b4f0b97 100644 --- a/src/tangara/lua/property.cpp +++ b/src/tangara/lua/property.cpp @@ -289,13 +289,14 @@ static void pushTrack(lua_State* L, const audio::TrackInfo& track) { lua_settable(L, -3); } -static void pushDevice(lua_State* L, const drivers::bluetooth::Device& dev) { +static void pushDevice(lua_State* L, + const drivers::bluetooth::MacAndName& dev) { lua_createtable(L, 0, 4); lua_pushliteral(L, "address"); auto* mac = reinterpret_cast( lua_newuserdata(L, sizeof(drivers::bluetooth::mac_addr_t))); - *mac = dev.address; + *mac = dev.mac; lua_rawset(L, -3); // What I just did there was perfectly safe. Look, I can prove it: @@ -308,14 +309,8 @@ static void pushDevice(lua_State* L, const drivers::bluetooth::Device& dev) { lua_pushlstring(L, dev.name.data(), dev.name.size()); lua_rawset(L, -3); - // FIXME: This field deserves a little more structure. - lua_pushliteral(L, "class"); - lua_pushinteger(L, dev.class_of_device); - lua_rawset(L, -3); - - lua_pushliteral(L, "signal_strength"); - lua_pushinteger(L, dev.signal_strength); - lua_rawset(L, -3); + // FIXME: Plumbing through device classes to here could be useful if we ever + // want to show cute little icons. } auto Property::pushValue(lua_State& s) -> int { @@ -332,10 +327,12 @@ auto Property::pushValue(lua_State& s) -> int { lua_pushstring(&s, arg.c_str()); } else if constexpr (std::is_same_v) { pushTrack(&s, arg); - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { pushDevice(&s, arg); } else if constexpr (std::is_same_v< - T, std::vector>) { + T, + std::vector>) { lua_createtable(&s, arg.size(), 0); size_t i = 1; for (const auto& dev : arg) { @@ -364,15 +361,10 @@ auto popRichType(lua_State* L) -> LuaValue { lua_pushliteral(L, "name"); lua_gettable(L, -2); - std::pmr::string name = lua_tostring(L, -1); + std::string name = lua_tostring(L, -1); lua_pop(L, 1); - return drivers::bluetooth::Device{ - .address = mac, - .name = name, - .class_of_device = 0, - .signal_strength = 0, - }; + return drivers::bluetooth::MacAndName{.mac = mac, .name = name}; } return std::monostate{}; diff --git a/src/tangara/lua/property.hpp b/src/tangara/lua/property.hpp index 9f925766..d45821bd 100644 --- a/src/tangara/lua/property.hpp +++ b/src/tangara/lua/property.hpp @@ -24,8 +24,8 @@ using LuaValue = std::variant>; + drivers::bluetooth::MacAndName, + std::vector>; using LuaFunction = std::function; -- cgit v1.2.3 From f78de39a750d58bfe883a789aa6cc4b0a5d9b9e7 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 12 Jul 2024 14:40:54 +1000 Subject: Give Bluetooth settings a bit of a refresh It's now a bit more responsive to stuff happening, gives you more information, and remembers your previously paired devices for faster switching between them. --- src/tangara/lua/property.cpp | 51 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 25 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/property.cpp b/src/tangara/lua/property.cpp index 7b4f0b97..1be1fd2d 100644 --- a/src/tangara/lua/property.cpp +++ b/src/tangara/lua/property.cpp @@ -371,33 +371,34 @@ auto popRichType(lua_State* L) -> LuaValue { } auto Property::popValue(lua_State& s) -> bool { - LuaValue new_val; - switch (lua_type(&s, 2)) { - case LUA_TNIL: - new_val = std::monostate{}; - break; - case LUA_TNUMBER: - if (lua_isinteger(&s, 2)) { - new_val = lua_tointeger(&s, 2); - } else { - new_val = static_cast(std::round(lua_tonumber(&s, 2))); - } - break; - case LUA_TBOOLEAN: - new_val = static_cast(lua_toboolean(&s, 2)); - break; - case LUA_TSTRING: - new_val = lua_tostring(&s, 2); - break; - default: - if (lua_istable(&s, 2)) { - new_val = popRichType(&s); - if (std::holds_alternative(new_val)) { + LuaValue new_val{std::monostate{}}; + if (lua_gettop(&s) >= 2) { + switch (lua_type(&s, 2)) { + case LUA_TNIL: + break; + case LUA_TNUMBER: + if (lua_isinteger(&s, 2)) { + new_val = lua_tointeger(&s, 2); + } else { + new_val = static_cast(std::round(lua_tonumber(&s, 2))); + } + break; + case LUA_TBOOLEAN: + new_val = static_cast(lua_toboolean(&s, 2)); + break; + case LUA_TSTRING: + new_val = lua_tostring(&s, 2); + break; + default: + if (lua_istable(&s, 2)) { + new_val = popRichType(&s); + if (std::holds_alternative(new_val)) { + return false; + } + } else { return false; } - } else { - return false; - } + } } return set(new_val); -- cgit v1.2.3 From b34959917446ac5d47ddec7bb6d98a6397045558 Mon Sep 17 00:00:00 2001 From: ailurux Date: Tue, 30 Jul 2024 04:36:48 +0000 Subject: daniel/playlist-queue (#84) Support for playlist files being opened along side the queue's own playlist. Playlists can be opened from the file browser, if the file ends in ".playlist" (will add support for .m3u as well eventually) Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/84 Co-authored-by: ailurux Co-committed-by: ailurux --- src/tangara/lua/file_iterator.cpp | 41 +++++++++++++++-------- src/tangara/lua/file_iterator.hpp | 4 ++- src/tangara/lua/lua_filesystem.cpp | 68 ++++++++++++++++++++------------------ src/tangara/lua/lua_queue.cpp | 14 ++++++++ 4 files changed, 79 insertions(+), 48 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/file_iterator.cpp b/src/tangara/lua/file_iterator.cpp index c3d63a16..823775e8 100644 --- a/src/tangara/lua/file_iterator.cpp +++ b/src/tangara/lua/file_iterator.cpp @@ -15,8 +15,8 @@ namespace lua { [[maybe_unused]] static const char* kTag = "FileIterator"; -FileIterator::FileIterator(std::string filepath) - : original_path_(filepath), current_(), offset_(-1) { +FileIterator::FileIterator(std::string filepath, bool showHidden) + : original_path_(filepath), show_hidden_(showHidden), current_(), offset_(-1) { const TCHAR* path = static_cast(filepath.c_str()); FRESULT res = f_opendir(&dir_, path); if (res != FR_OK) { @@ -33,7 +33,16 @@ auto FileIterator::value() const -> const std::optional& { } auto FileIterator::next() -> void { - iterate(false); + size_t prev_index = -1; + if (current_) { + prev_index = current_->index; + } + do { + bool res = iterate(show_hidden_); + if (!res) { + break; + } + } while (!current_ || current_->index == prev_index); } auto FileIterator::prev() -> void { @@ -45,11 +54,11 @@ auto FileIterator::prev() -> void { auto new_offset = offset_ - 1; offset_ = -1; for (int i = 0; i <= new_offset; i++) { - iterate(false); + iterate(show_hidden_); } } -auto FileIterator::iterate(bool reverse) -> bool { +auto FileIterator::iterate(bool show_hidden) -> bool { FILINFO info; auto res = f_readdir(&dir_, &info); if (res != FR_OK) { @@ -60,18 +69,22 @@ auto FileIterator::iterate(bool reverse) -> bool { // End of directory // Set value to nil current_.reset(); + return false; } else { // Update current value offset_++; - current_ = FileEntry{ - .index = offset_, - .isHidden = (info.fattrib & AM_HID) > 0, - .isDirectory = (info.fattrib & AM_DIR) > 0, - .isTrack = false, // TODO - .filepath = original_path_ + (original_path_.size() > 0 ? "/" : "") + - info.fname, - - }; + bool hidden = (info.fattrib & AM_HID) > 0 || info.fname[0] == '.'; + if (!hidden || show_hidden) { + current_ = FileEntry{ + .index = offset_, + .isHidden = hidden, + .isDirectory = (info.fattrib & AM_DIR) > 0, + .isTrack = false, // TODO + .filepath = original_path_ + (original_path_.size() > 0 ? "/" : "") + + info.fname, + .name = info.fname, + }; + } } return true; } diff --git a/src/tangara/lua/file_iterator.hpp b/src/tangara/lua/file_iterator.hpp index b803062c..c4071445 100644 --- a/src/tangara/lua/file_iterator.hpp +++ b/src/tangara/lua/file_iterator.hpp @@ -21,11 +21,12 @@ struct FileEntry { bool isDirectory; bool isTrack; std::string filepath; + std::string name; }; class FileIterator { public: - FileIterator(std::string filepath); + FileIterator(std::string filepath, bool showHidden); ~FileIterator(); auto value() const -> const std::optional&; @@ -35,6 +36,7 @@ class FileIterator { private: FF_DIR dir_; std::string original_path_; + bool show_hidden_; std::optional current_; int offset_; diff --git a/src/tangara/lua/lua_filesystem.cpp b/src/tangara/lua/lua_filesystem.cpp index de51f555..e3a3018d 100644 --- a/src/tangara/lua/lua_filesystem.cpp +++ b/src/tangara/lua/lua_filesystem.cpp @@ -21,29 +21,21 @@ struct LuaFileEntry { bool isHidden; bool isDirectory; bool isTrack; - size_t path_size; - char path[]; + std::string path; + std::string name; }; -static_assert(std::is_trivially_destructible()); -static_assert(std::is_trivially_copy_assignable()); - static auto push_lua_file_entry(lua_State* L, const lua::FileEntry& r) -> void { - // Create and init the userdata. - LuaFileEntry* file_entry = reinterpret_cast( - lua_newuserdata(L, sizeof(LuaFileEntry) + r.filepath.size())); + lua::FileEntry** entry = reinterpret_cast( + lua_newuserdata(L, sizeof(uintptr_t))); + *entry = new lua::FileEntry(r); luaL_setmetatable(L, kFileEntryMetatable); +} - // Init all the fields - *file_entry = { - .isHidden = r.isHidden, - .isDirectory = r.isDirectory, - .isTrack = r.isTrack, - .path_size = r.filepath.size(), - }; - - // Copy the string data across. - std::memcpy(file_entry->path, r.filepath.data(), r.filepath.size()); +auto check_file_entry(lua_State* L, int stack_pos) -> lua::FileEntry* { + lua::FileEntry* entry = *reinterpret_cast( + luaL_checkudata(L, stack_pos, kFileEntryMetatable)); + return entry; } auto check_file_iterator(lua_State* L, int stack_pos) -> lua::FileIterator* { @@ -56,7 +48,7 @@ static auto push_iterator(lua_State* state, const lua::FileIterator& it) -> void { lua::FileIterator** data = reinterpret_cast( lua_newuserdata(state, sizeof(uintptr_t))); - *data = new lua::FileIterator(it); // TODO... + *data = new lua::FileIterator(it); luaL_setmetatable(state, kFileIteratorMetatable); } @@ -108,45 +100,55 @@ static const struct luaL_Reg kFileIteratorFuncs[] = {{"next", fs_iterate}, {NULL, NULL}}; static auto file_entry_path(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushlstring(state, data->path, data->path_size); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushlstring(state, entry->filepath.c_str(), entry->filepath.size()); return 1; } static auto file_entry_is_dir(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isDirectory); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isDirectory); return 1; } static auto file_entry_is_hidden(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isHidden); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isHidden); return 1; } static auto file_entry_is_track(lua_State* state) -> int { - LuaFileEntry* data = reinterpret_cast( - luaL_checkudata(state, 1, kFileEntryMetatable)); - lua_pushboolean(state, data->isTrack); + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushboolean(state, entry->isTrack); + return 1; +} + +static auto file_entry_name(lua_State* state) -> int { + lua::FileEntry* entry = check_file_entry(state, 1); + lua_pushlstring(state, entry->name.c_str(), entry->name.size()); + return 1; +} + +static auto file_entry_gc(lua_State* state) -> int { + lua::FileEntry* entry = check_file_entry(state, 1); + delete entry; return 1; } static const struct luaL_Reg kFileEntryFuncs[] = {{"filepath", file_entry_path}, + {"name", file_entry_name}, {"is_directory", file_entry_is_dir}, {"is_hidden", file_entry_is_hidden}, {"is_track", file_entry_is_track}, - {"__tostring", file_entry_path}, + {"__tostring", file_entry_name}, + {"__gc", file_entry_gc}, {NULL, NULL}}; static auto fs_new_iterator(lua_State* state) -> int { // Takes a filepath as a string and returns a new FileIterator // on that directory std::string filepath = luaL_checkstring(state, -1); - lua::FileIterator iter(filepath); + lua::FileIterator iter(filepath, false); push_iterator(state, iter); return 1; } diff --git a/src/tangara/lua/lua_queue.cpp b/src/tangara/lua/lua_queue.cpp index bc393aa5..9e2002e6 100644 --- a/src/tangara/lua/lua_queue.cpp +++ b/src/tangara/lua/lua_queue.cpp @@ -57,8 +57,22 @@ static auto queue_clear(lua_State* state) -> int { return 0; } +static auto queue_open_playlist(lua_State* state) -> int { + Bridge* instance = Bridge::Get(state); + audio::TrackQueue& queue = instance->services().track_queue(); + size_t len = 0; + const char* str = luaL_checklstring(state, 1, &len); + if (!str) { + return 0; + } + queue.clear(); + queue.openPlaylist(str); + return 0; +} + static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, {"clear", queue_clear}, + {"open_playlist", queue_open_playlist}, {NULL, NULL}}; static auto lua_queue(lua_State* state) -> int { -- cgit v1.2.3 From d719f9c5017ad8006c21b6d546a5d70e846e9502 Mon Sep 17 00:00:00 2001 From: ailurux Date: Mon, 12 Aug 2024 03:19:03 +0000 Subject: daniel/theme-setting (#87) - Themes can be loaded from disk and built-in - Themes can be selected in a new themes menu of the settings screen - Some touch-ups to existing themes - The saved theme is persisted in nvs Reviewed-on: https://codeberg.org/cool-tech-zone/tangara-fw/pulls/87 Reviewed-by: cooljqln Co-authored-by: ailurux Co-committed-by: ailurux --- src/tangara/lua/lua_theme.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/lua_theme.cpp b/src/tangara/lua/lua_theme.cpp index 5edde104..03578778 100644 --- a/src/tangara/lua/lua_theme.cpp +++ b/src/tangara/lua/lua_theme.cpp @@ -75,8 +75,41 @@ static auto set_theme(lua_State* L) -> int { return 0; } + +static auto load_theme(lua_State* L) -> int { + std::string filename = luaL_checkstring(L, -1); + // Set the theme filename in non-volatile storage + Bridge* instance = Bridge::Get(L); + // Load the theme using lua + auto status = luaL_loadfile(L, filename.c_str()); + if (status != LUA_OK) { + lua_pushboolean(L, false); + return 1; + } + status = lua::CallProtected(L, 0, 1); + if (status == LUA_OK) { + ui::themes::Theme::instance()->Reset(); + set_theme(L); + instance->services().nvs().InterfaceTheme(filename); + lua_pushboolean(L, true); + } else { + lua_pushboolean(L, false); + } + + return 1; +} + +static auto theme_filename(lua_State* L) -> int { + Bridge* instance = Bridge::Get(L); + auto file = instance->services().nvs().InterfaceTheme().value_or("/lua/theme_light.lua"); + lua_pushstring(L, file.c_str()); + return 1; +} + static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {"set_style", set_style}, + {"load_theme", load_theme}, + {"theme_filename", theme_filename}, {NULL, NULL}}; static auto lua_theme(lua_State* L) -> int { -- cgit v1.2.3 From af7a70450e3ceaaf291aa09b9383b50b879759d9 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 28 Aug 2024 15:30:25 +1000 Subject: Support adding filepaths to the track queue --- src/tangara/lua/lua_queue.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/lua_queue.cpp b/src/tangara/lua/lua_queue.cpp index 9e2002e6..7eb32c62 100644 --- a/src/tangara/lua/lua_queue.cpp +++ b/src/tangara/lua/lua_queue.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-only */ +#include "audio/audio_events.hpp" #include "lua/lua_database.hpp" #include @@ -39,6 +40,14 @@ static auto queue_add(lua_State* state) -> int { audio::TrackQueue& queue = instance->services().track_queue(); queue.append(id); }); + } else if (lua_isstring(state, 1)) { + size_t len; + const char* str = luaL_checklstring(state, 1, &len); + std::string path{str, len}; + instance->services().bg_worker().Dispatch([=]() { + audio::TrackQueue& queue = instance->services().track_queue(); + queue.append(path); + }); } else { database::Iterator* it = db_check_iterator(state, 1); instance->services().bg_worker().Dispatch([=]() { @@ -70,10 +79,11 @@ static auto queue_open_playlist(lua_State* state) -> int { return 0; } -static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, - {"clear", queue_clear}, - {"open_playlist", queue_open_playlist}, - {NULL, NULL}}; +static const struct luaL_Reg kQueueFuncs[] = { + {"add", queue_add}, + {"clear", queue_clear}, + {"open_playlist", queue_open_playlist}, + {NULL, NULL}}; static auto lua_queue(lua_State* state) -> int { luaL_newlib(state, kQueueFuncs); -- cgit v1.2.3 From 3421bd652c39b253872e43d3b6e43664bd0b66e2 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 28 Aug 2024 15:30:53 +1000 Subject: When clicking a track in the file browser, play it Includes adding a `playback.is_playable` for working out whether or not a particular file is able to be played --- src/tangara/lua/file_iterator.cpp | 1 - src/tangara/lua/file_iterator.hpp | 3 +-- src/tangara/lua/lua_filesystem.cpp | 7 ------- 3 files changed, 1 insertion(+), 10 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/file_iterator.cpp b/src/tangara/lua/file_iterator.cpp index 823775e8..71daf2d8 100644 --- a/src/tangara/lua/file_iterator.cpp +++ b/src/tangara/lua/file_iterator.cpp @@ -79,7 +79,6 @@ auto FileIterator::iterate(bool show_hidden) -> bool { .index = offset_, .isHidden = hidden, .isDirectory = (info.fattrib & AM_DIR) > 0, - .isTrack = false, // TODO .filepath = original_path_ + (original_path_.size() > 0 ? "/" : "") + info.fname, .name = info.fname, diff --git a/src/tangara/lua/file_iterator.hpp b/src/tangara/lua/file_iterator.hpp index c4071445..2d5c2d7d 100644 --- a/src/tangara/lua/file_iterator.hpp +++ b/src/tangara/lua/file_iterator.hpp @@ -19,7 +19,6 @@ struct FileEntry { int index; bool isHidden; bool isDirectory; - bool isTrack; std::string filepath; std::string name; }; @@ -44,4 +43,4 @@ class FileIterator { auto iterate(bool reverse = false) -> bool; }; -} // namespace lua \ No newline at end of file +} // namespace lua diff --git a/src/tangara/lua/lua_filesystem.cpp b/src/tangara/lua/lua_filesystem.cpp index e3a3018d..9c2ea880 100644 --- a/src/tangara/lua/lua_filesystem.cpp +++ b/src/tangara/lua/lua_filesystem.cpp @@ -117,12 +117,6 @@ static auto file_entry_is_hidden(lua_State* state) -> int { return 1; } -static auto file_entry_is_track(lua_State* state) -> int { - lua::FileEntry* entry = check_file_entry(state, 1); - lua_pushboolean(state, entry->isTrack); - return 1; -} - static auto file_entry_name(lua_State* state) -> int { lua::FileEntry* entry = check_file_entry(state, 1); lua_pushlstring(state, entry->name.c_str(), entry->name.size()); @@ -139,7 +133,6 @@ static const struct luaL_Reg kFileEntryFuncs[] = {{"filepath", file_entry_path}, {"name", file_entry_name}, {"is_directory", file_entry_is_dir}, {"is_hidden", file_entry_is_hidden}, - {"is_track", file_entry_is_track}, {"__tostring", file_entry_name}, {"__gc", file_entry_gc}, {NULL, NULL}}; -- cgit v1.2.3 From 91eaed4b37c7cda29103d3478df3e2c6356f8396 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 29 Aug 2024 15:52:34 +1000 Subject: use snake_case consistently in lua function names --- src/tangara/lua/lua_screen.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/tangara/lua') diff --git a/src/tangara/lua/lua_screen.cpp b/src/tangara/lua/lua_screen.cpp index 8d87eebd..6bb26ec1 100644 --- a/src/tangara/lua/lua_screen.cpp +++ b/src/tangara/lua/lua_screen.cpp @@ -56,9 +56,9 @@ static auto screen_true(lua_State* state) -> int { } static const struct luaL_Reg kScreenFuncs[] = { - {"new", screen_new}, {"createUi", screen_noop}, - {"onShown", screen_noop}, {"onHidden", screen_noop}, - {"canPop", screen_true}, {NULL, NULL}}; + {"new", screen_new}, {"create_ui", screen_noop}, + {"on_show", screen_noop}, {"on_hide", screen_noop}, + {"can_pop", screen_true}, {NULL, NULL}}; static auto lua_screen(lua_State* state) -> int { luaL_newlib(state, kScreenFuncs); -- cgit v1.2.3