Mercurial > minori
changeset 366:886f66775f31
animone: add preliminary AT-SPI stuff
anime_list: finish the regular singular right click menu
author | Paper <paper@tflc.us> |
---|---|
date | Sun, 17 Nov 2024 19:56:01 -0500 |
parents | f81bed4e04ac |
children | 8d45d892be88 |
files | dep/animone/CMakeLists.txt dep/animone/include/animone/a11y.h dep/animone/include/animone/a11y/atspi.h dep/animone/include/animone/a11y/win32.h dep/animone/include/animone/types.h dep/animone/src/a11y.cc dep/animone/src/a11y/atspi.cc dep/animone/src/a11y/win32.cc dep/animone/src/strategist.cc include/library/library.h src/gui/pages/anime_list.cc src/gui/window.cc src/library/library.cc src/track/media.cc |
diffstat | 14 files changed, 310 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/dep/animone/CMakeLists.txt Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/CMakeLists.txt Sun Nov 17 19:56:01 2024 -0500 @@ -87,6 +87,14 @@ list(APPEND INCLUDE_DIRS ${XCB_INCLUDE_DIRS}) list(APPEND SRC_FILES src/win/x11.cc) endif() # XCB_FOUND + + pkg_check_modules(ATSPI atspi-2) + if (ATSPI_FOUND) + list(APPEND DEFINES USE_ATSPI) + list(APPEND LIBRARIES ${ATSPI_LINK_LIBRARIES}) + list(APPEND INCLUDE_DIRS ${ATSPI_INCLUDE_DIRS}) + list(APPEND SRC_FILES src/a11y/atspi.cc) + endif() # ATSPI_FOUND endif() # PKG_CONFIG_FOUND add_library(animia SHARED ${SRC_FILES})
--- a/dep/animone/include/animone/a11y.h Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/include/animone/a11y.h Sun Nov 17 19:56:01 2024 -0500 @@ -22,7 +22,7 @@ using web_browser_proc_t = std::function<void(const WebBrowserInformation&)>; -bool GetWebBrowserInformation(const Window& window, web_browser_proc_t web_browser_proc); +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc); } // namespace internal } // namespace animone
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animone/include/animone/a11y/atspi.h Sun Nov 17 19:56:01 2024 -0500 @@ -0,0 +1,13 @@ +#ifndef ANIMONE_ANIMONE_A11Y_ATSPI_H_ +#define ANIMONE_ANIMONE_A11Y_ATSPI_H_ + +#include "animone.h" +#include "animone/a11y.h" + +namespace animone::internal::atspi { + +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc); + +} // namespace animone::internal::atspi + +#endif // ANIMONE_ANIMONE_A11Y_ATSPI_H_
--- a/dep/animone/include/animone/a11y/win32.h Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/include/animone/a11y/win32.h Sun Nov 17 19:56:01 2024 -0500 @@ -7,7 +7,7 @@ namespace animone::internal::win32 { -bool GetWebBrowserInformation(const Window& window, web_browser_proc_t web_browser_proc); +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc); } // namespace animone::internal::win32
--- a/dep/animone/include/animone/types.h Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/include/animone/types.h Sun Nov 17 19:56:01 2024 -0500 @@ -29,7 +29,7 @@ /* different window systems have different sized IDs */ union ANIMONE_API wid_t { - std::uintptr_t win32; + std::uintptr_t win32; // XXX this ought to be a `void *` std::int64_t quartz; // FIXME is this correct? std::uint32_t x11; };
--- a/dep/animone/src/a11y.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/src/a11y.cc Sun Nov 17 19:56:01 2024 -0500 @@ -4,13 +4,21 @@ # include "animone/a11y/win32.h" #endif +#ifdef USE_ATSPI +# include "animone/a11y/atspi.h" +#endif + namespace animone::internal { -bool GetWebBrowserInformation(const Window& window, web_browser_proc_t web_browser_proc) { +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc) { bool success = false; #ifdef USE_WIN32 - success ^= win32::GetWebBrowserInformation(window, web_browser_proc); + success ^= win32::GetWebBrowserInformation(result, web_browser_proc); +#endif + +#ifdef USE_ATSPI + success ^= atspi::GetWebBrowserInformation(result, web_browser_proc); #endif return success;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animone/src/a11y/atspi.cc Sun Nov 17 19:56:01 2024 -0500 @@ -0,0 +1,77 @@ +#include <functional> +#include <string> +#include <vector> +#include <memory> + +#include "animone.h" +#include "animone/a11y/atspi.h" + +#include <atspi/atspi.h> + +namespace animone::internal::atspi { + +/* deleters */ +template<typename T> +struct g_object_del { + void operator()(T* p) const { ::g_object_unref(p); }; +}; + +template<typename T> +using GObjectPtr = std::unique_ptr<T, g_object_del<T>>; + +/* ----------------------------------------------------------------- */ + +// FIXME | atspi_exit() +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc) { + GObjectPtr<AtspiAccessible> desktop; + GObjectPtr<AtspiAccessible> application; + + { + int err = atspi_init(); + if (err != 0 && err != 1) + return false; + } + + // Currently only one desktop is supported, so this is equivalent to doing + // just atspi_get_desktop(0). However it's nice to futureproof where possible. + for (gint i = 0; i < atspi_get_desktop_count(); i++) { + desktop.reset(atspi_get_desktop(i)); + if (!desktop) + return false; + + for (gint j = 0; j < atspi_accessible_get_child_count(application.get(), nullptr); j++) { + application.reset(atspi_accessible_get_child_at_index(desktop.get(), j, nullptr)); + if (!application) + return false; + + GError *error = NULL; + + std::uint32_t pid = atspi_accessible_get_process_id(application.get(), &error); + if (error) { + ::g_error_free(error); + return false; + } + + if (pid == result.process.pid) + goto found; // found it + } + } + + // didn't get anything... lol + return false; + +found: + // found a matching application + + gchar *title = atspi_accessible_get_name(application.get(), NULL); + if (title) { + web_browser_proc({WebBrowserInformationType::Title, title}); + ::g_free(title); + } + + // TODO need to find address and tab? idk + + return true; +} + +}
--- a/dep/animone/src/a11y/win32.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/src/a11y/win32.cc Sun Nov 17 19:56:01 2024 -0500 @@ -235,14 +235,14 @@ /* ------------------------------------------------------------------------------------ */ -bool GetWebBrowserInformation(const Window& window, web_browser_proc_t web_browser_proc) { +bool GetWebBrowserInformation(const Result& result, web_browser_proc_t web_browser_proc) { if (!web_browser_proc) return false; if (!InitializeUIAutomation()) return false; - ComInterface<Element> parent(GetElementFromHandle(reinterpret_cast<HWND>(window.id.win32))); + ComInterface<Element> parent(GetElementFromHandle(reinterpret_cast<HWND>(result.window.id.win32))); if (!parent) return false;
--- a/dep/animone/src/strategist.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/dep/animone/src/strategist.cc Sun Nov 17 19:56:01 2024 -0500 @@ -122,7 +122,7 @@ } }; - success |= GetWebBrowserInformation(result.window, web_browser_proc); + success |= GetWebBrowserInformation(result, web_browser_proc); } return success;
--- a/include/library/library.h Wed Oct 02 23:06:43 2024 -0400 +++ b/include/library/library.h Sun Nov 17 19:56:01 2024 -0500 @@ -3,16 +3,23 @@ #include "library/library.h" +#include <optional> #include <filesystem> #include <unordered_map> namespace Library { -class Database { +class Database final { public: + std::optional<std::filesystem::path> GetAnimeFolder(int id); void Refresh(); + void Refresh(int id); + // Anime episodes. Indexed as `folders[id][episode]' std::unordered_map<int, std::unordered_map<int, std::filesystem::path>> items; + +private: + void Refresh(std::optional<int> find_id); }; extern Database db;
--- a/src/gui/pages/anime_list.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/src/gui/pages/anime_list.cc Sun Nov 17 19:56:01 2024 -0500 @@ -14,6 +14,7 @@ #include "core/session.h" #include "core/strings.h" #include "core/time.h" +#include "library/library.h" #include "gui/dialog/information.h" #include "gui/translate/anime.h" #include "services/services.h" @@ -30,8 +31,11 @@ #include <QThreadPool> #include <QRunnable> #include <QTreeView> +#include <QDesktopServices> +#include <QUrl> -#include <set> +#include <iostream> +#include <vector> AnimeListPageUpdateEntryThread::AnimeListPageUpdateEntryThread(QObject* parent) : QThread(parent) {} @@ -272,7 +276,7 @@ if (checked && (tree_view->columnWidth(i) <= 5)) tree_view->resizeColumnToContents(i); - // SaveSettings(); + // FIXME save the state of this }); action->setCheckable(true); @@ -290,7 +294,7 @@ } void AnimeListPage::DisplayListMenu() { - QMenu* menu = new QMenu(this); + QMenu *const menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); menu->setToolTipsVisible(true); @@ -303,14 +307,71 @@ for (const auto& index : selection.indexes()) { if (!index.isValid()) continue; - Anime::Anime* anime = source_model->GetAnimeFromIndex(index); + + Anime::Anime *const anime = source_model->GetAnimeFromIndex(index); if (!anime) continue; + animes.insert(&Anime::db.items[anime->GetId()]); } - menu->addAction(tr("Information"), [this, animes] { - for (auto& anime : animes) { + if (animes.size() > 1) { + // menu in Taiga: + // + // Set date started -> + // Clear + // Set to date started airing + // Set date completed -> + // Clear + // Set to date finished airing + // Set to last updated + // Set episode... + // Set score -> + // 0 + // 10 + // ... + // 100 + // Set status -> + // Currently watching + // ... + // Plan to watch + // Set notes... + // ---------------- + // Invert selection + // ---------------- + // Delete from list... <Del> + } else if (animes.size() > 0) { + // menu in Taiga: + // + // Information + // Search -> + // AniDB + // AniList + // Anime News Network + // Kitsu + // MyAnimeList + // Reddit + // Wikipedia + // YouTube + // ---------------- + // Custom RSS feed + // Nyaa.si + // ---------------- + // Edit + // Delete from list... <Del> + // ---------------- + // Open folder <Ctrl+O> + // Scan available episodes <F5> + // ---------------- + // Play episode -> + // grid of episodes (dunno how to implement this) + // Play last episode (#<episode>) + // Play next episode (#<episode>) <Ctrl+N> + // Play random episode <Ctrl+R> (why?) + + Anime::Anime *anime = *animes.begin(); + + menu->addAction(tr("Information"), [this, anime] { InformationDialog* dialog = new InformationDialog( anime, [this](Anime::Anime* anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); @@ -318,11 +379,11 @@ dialog->raise(); dialog->activateWindow(); connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater); - } - }); - menu->addSeparator(); - menu->addAction(tr("Edit"), [this, animes] { - for (auto& anime : animes) { + }); + + menu->addSeparator(); + + menu->addAction(tr("Edit"), [this, anime] { InformationDialog* dialog = new InformationDialog( anime, [this](Anime::Anime* anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST, this); @@ -330,14 +391,80 @@ dialog->raise(); dialog->activateWindow(); connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater); + }); + menu->addAction(tr("Delete from list..."), [this, anime] { + RemoveAnime(anime->GetId()); + }, QKeySequence(QKeySequence::Delete)); + + menu->addSeparator(); + + menu->addAction(tr("Open folder"), [this, anime] { + std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId()); + if (!path) // ... + return; + + QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string()))); + }); + menu->addAction(tr("Scan available episodes"), [this, anime] { + Library::db.Refresh(anime->GetId()); + }); + + menu->addSeparator(); + + { + QMenu *submenu = menu->addMenu(tr("Play episode")); + + // this submenu actually uses win32 API magic to + // make a *grid* of episodes (weird!) + + (void)submenu; } - }); - menu->addAction(tr("Delete from list..."), [this, animes] { - for (auto& anime : animes) { - RemoveAnime(anime->GetId()); + + const int progress = anime->GetUserProgress(); + const int episodes = anime->GetEpisodes(); + + // I think this is right? + if (progress > 0) { + menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] { + const int id = anime->GetId(); + + if (Library::db.items.find(id) == Library::db.items.end() + || Library::db.items[id].find(progress) == Library::db.items[id].end()) + return; + + QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string()))); + }); } - }); - menu->popup(QCursor::pos()); + + if (progress < episodes) { + menu->addAction(tr("Play next episode (#%1)").arg(progress + 1), [this, anime, progress] { + const int id = anime->GetId(); + + if (Library::db.items.find(id) == Library::db.items.end() + || Library::db.items[id].find(progress + 1) == Library::db.items[id].end()) + return; + + QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string()))); + }, QKeySequence(Qt::CTRL | Qt::Key_N)); + } + + menu->addAction(tr("Play random episode"), [this, anime, episodes] { + const int id = anime->GetId(); + + std::uniform_int_distribution<int> distrib(1, episodes); + const int episode = distrib(session.gen); + + if (Library::db.items.find(id) == Library::db.items.end() + || Library::db.items[id].find(episode) == Library::db.items[id].end()) + return; + + QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string()))); + }, QKeySequence(Qt::CTRL | Qt::Key_R)); + + menu->popup(QCursor::pos()); + } else { + // Where are we now? + } } void AnimeListPage::ItemDoubleClicked() {
--- a/src/gui/window.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/src/gui/window.cc Sun Nov 17 19:56:01 2024 -0500 @@ -140,7 +140,9 @@ statusBar()->showMessage(Strings::ToQString(message), 2000); } -/* Does the main part of what Qt's generic "RetranslateUI" function would do */ +/* FIXME: + * ALL of the pages need to have a retranslate function. This would require + * huge amounts of refactoring hence why it hasn't been done yet. */ void MainWindow::AddMainWidgets() { int page = sidebar_.GetCurrentItem();
--- a/src/library/library.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/src/library/library.cc Sun Nov 17 19:56:01 2024 -0500 @@ -13,7 +13,31 @@ namespace Library { -void Database::Refresh() { +std::optional<std::filesystem::path> Database::GetAnimeFolder(int id) { + // this function sucks, but it's the most I can really do for now. + // + // in the future the Refresh() function should look for directories + // as well that fit the anime name and *also* have episodes in them. + // it should give each of these directories a rating by how many + // episodes are contained in them. whichever directory has more episodes + // wins, or the first found if there is an equal amount. + + for (const auto& [anime_id, episodes] : items) { + if (id != anime_id) + continue; + + for (const auto& [episode, path] : episodes) { + return path.parent_path(); + break; + } + + break; + } + + return std::nullopt; +} + +void Database::Refresh(std::optional<int> find_id) { items.clear(); for (const auto& folder : session.config.library.paths) { @@ -32,7 +56,7 @@ const std::string title = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); const int id = Anime::db.LookupAnimeTitle(title); - if (id <= 0) + if (id <= 0 || (find_id && find_id.value() != id)) continue; const int episode = Strings::ToInt<int>(Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber))); @@ -43,6 +67,16 @@ } } +void Database::Refresh() { + Refresh(std::nullopt); +} + +void Database::Refresh(int id) { + Refresh(std::optional<int>(id)); +} + +// TODO export to JSON + Database db; } // namespace Library
--- a/src/track/media.cc Wed Oct 02 23:06:43 2024 -0400 +++ b/src/track/media.cc Sun Nov 17 19:56:01 2024 -0500 @@ -29,7 +29,11 @@ return animone::GetResults(players, results); } -/* meh */ +/* The results from this function are KIND OF guaranteed to be UTF-8; + * more specifically any files returned are UTF-8 as required by the C++ + * standard. However, window titles are not, and for some obscure X11 + * window managers, WILL not be in UTF-8. I don't care enough about this + * to do anything about it though. */ bool GetCurrentlyPlaying(std::vector<std::string>& vec) { std::vector<animone::Result> results;