#include "main_window.hpp" #include "config.hpp" #include "luacxx.hpp" #include #include #include #include #include #include #include #include #include namespace getsuyomi { using fud::FudStatus; using GetEnvResult = fud::Result; using GetConfigResult = fud::Result; constexpr const char* HOME{"HOME"}; std::optional getEnvVar(const char* envVar); void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup); GetEnvResult getEnvironment(); FudStatus createXdgDirectory(const std::string& directoryName) { constexpr mode_t xdgMode = 0700; auto dirStatus = mkdir(directoryName.c_str(), xdgMode); if (dirStatus == 0) { return FudStatus::Success; } if (errno != EEXIST) { return FudStatus::Failure; } struct stat statBuffer {}; dirStatus = stat(directoryName.c_str(), &statBuffer); if (dirStatus != 0) { return FudStatus::Failure; } if ((statBuffer.st_mode & S_IFMT) != S_IFDIR) { return FudStatus::Failure; } return FudStatus::Success; } GetConfigResult getUserConfig() { auto configResult = getEnvironment(); if (configResult.isError()) { return GetConfigResult::error(configResult.getError()); } auto config = configResult.getOkay(); auto dirStatus = createXdgDirectory(config.dataHome); if (dirStatus != FudStatus::Success) { return GetConfigResult::error(dirStatus); } dirStatus = createXdgDirectory(config.configHome); if (dirStatus != FudStatus::Success) { return GetConfigResult::error(dirStatus); } dirStatus = createXdgDirectory(config.stateHome); if (dirStatus != FudStatus::Success) { return GetConfigResult::error(dirStatus); } auto configFileName = std::filesystem::path(config.configHome).append("config.lua"); auto shortcutMap = Shortcuts::fromUserConfig(configFileName); auto binder = [&](ShortcutList& shortcuts, ActionType action) { shortcuts = shortcutListFromSet(shortcutMap.at(action)); }; binder(config.openFileShortcuts, ActionType::OpenFile); binder(config.openDirectoryShortcuts, ActionType::OpenDirectory); binder(config.quitShortcuts, ActionType::Quit); binder(config.settingsShortcuts, ActionType::Configure); binder(config.nextShortcuts, ActionType::Next); binder(config.backShortcuts, ActionType::Back); binder(config.singlePageShortcuts, ActionType::SinglePage); binder(config.dualPageShortcuts, ActionType::DualPage); binder(config.mangaPageShortcuts, ActionType::MangaPage); return GetConfigResult::okay(config); } GetsuyomiApp::GetsuyomiApp() : QMainWindow(nullptr), m_getsuyomi{new Getsuyomi(this)} { readSettings(); } FudStatus GetsuyomiApp::setup() { setCentralWidget(m_getsuyomi); QCoreApplication::setApplicationName(AppName); QCoreApplication::setApplicationVersion(AppVersionString); setWindowTitle(AppName); auto envResult = getUserConfig(); if (envResult.isError()) { qCritical("Failure %s", fud::FudStatusToString(envResult.getError())); return envResult.getError(); } m_config = envResult.getOkay(); createActions(); createMenus(); createToolBar(); constexpr int minimumWidth = 640; constexpr int minimumHeight = 480; setMinimumSize(minimumWidth, minimumHeight); show(); return FudStatus::Success; } void GetsuyomiApp::createActions() { m_openFile = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpen), tr("&Open File"), this); m_openFile->setShortcuts(m_config.openFileShortcuts); m_openFile->setStatusTip(tr("Open a file")); connect(m_openFile, &QAction::triggered, this, &GetsuyomiApp::openFile); m_openDirectory = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::FolderOpen), tr("Open &Directory"), this); m_openDirectory->setShortcuts(m_config.openDirectoryShortcuts); m_openDirectory->setStatusTip(tr("Open a directory")); connect(m_openDirectory, &QAction::triggered, this, &GetsuyomiApp::openDirectory); m_quitAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit), tr("&Quit"), this); m_quitAction->setShortcuts(m_config.quitShortcuts); m_quitAction->setStatusTip(tr("Quit")); connect(m_quitAction, &QAction::triggered, this, &GetsuyomiApp::quit); m_settingsAction = new QAction(QIcon("resources/gear.svg"), tr("&Settings"), this); m_settingsAction->setShortcuts(m_config.settingsShortcuts); m_settingsAction->setStatusTip(tr("Configure getsuyomi")); connect(m_settingsAction, &QAction::triggered, this, &GetsuyomiApp::configure); m_nextAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::GoNext), tr("Next"), this); m_nextAction->setShortcuts(m_config.nextShortcuts); m_nextAction->setStatusTip(tr("Next")); connect(m_nextAction, &QAction::triggered, this, &GetsuyomiApp::next); m_backAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::GoPrevious), tr("Back"), this); m_backAction->setShortcuts(m_config.backShortcuts); m_backAction->setStatusTip(tr("Back")); connect(m_backAction, &QAction::triggered, this, &GetsuyomiApp::back); m_setSinglePageLayout = new QAction(QIcon("../resources/pageSingle.png"), tr("Single Page Layout"), this); m_setSinglePageLayout->setShortcuts(m_config.singlePageShortcuts); m_setSinglePageLayout->setStatusTip(tr("Set Single Page Layout")); connect(m_setSinglePageLayout, &QAction::triggered, this, &GetsuyomiApp::setSinglePageLayout); m_setDualPageLayout = new QAction(QIcon("../resources/pageDual.png"), tr("Dual Page Layout"), this); m_setDualPageLayout->setShortcuts(m_config.dualPageShortcuts); m_setDualPageLayout->setStatusTip(tr("Set Dual Page Layout")); connect(m_setDualPageLayout, &QAction::triggered, this, &GetsuyomiApp::setDualPageLayout); m_setMangaLayout = new QAction(QIcon("../resources/pageManga.png"), tr("Manga Page Layout"), this); m_setMangaLayout->setShortcuts(m_config.mangaPageShortcuts); m_setMangaLayout->setStatusTip(tr("Set Manga Page Layout")); connect(m_setMangaLayout, &QAction::triggered, this, &GetsuyomiApp::setMangaLayout); m_setPageLayoutGroup = new QActionGroup(this); m_setPageLayoutGroup->addAction(m_setSinglePageLayout); m_setPageLayoutGroup->addAction(m_setDualPageLayout); m_setPageLayoutGroup->addAction(m_setMangaLayout); m_setPageLayoutGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive); } void GetsuyomiApp::createMenus() { m_fileMenu = menuBar()->addMenu(tr("&File")); m_fileMenu->addAction(m_openFile); m_fileMenu->addAction(m_openDirectory); m_fileMenu->addAction(m_quitAction); m_SettingsMenu = menuBar()->addMenu(tr("&Settings")); m_SettingsMenu->addAction(m_settingsAction); } void GetsuyomiApp::createToolBar() { m_toolBar = addToolBar(tr("&Navigation")); m_toolBar->setObjectName("Navigation"); // goto first m_toolBar->addAction(m_backAction); m_toolBar->addAction(m_nextAction); // goto last m_toolBar->addSeparator(); m_toolBar->addAction(m_setSinglePageLayout); m_toolBar->addAction(m_setDualPageLayout); m_toolBar->addAction(m_setMangaLayout); } void GetsuyomiApp::openFile() { auto dialog = QFileDialog( this, tr("Open Archive"), m_lastOpenedDirectory, tr("Archive types (*.zip *.cbz *.cbr *.gz)")); dialog.setFileMode(QFileDialog::ExistingFile); QString filename; if (dialog.exec()) { auto filenames = dialog.selectedFiles(); if (filenames.length() == 0) { qWarning("No files selected."); return; } else if (filenames.length() > 1) { qWarning("Too many files selected %llu.", filenames.length()); return; } filename = filenames[0]; m_lastOpenedDirectory = dialog.directory().absolutePath(); qDebug("Last opened directory is %s", qPrintable(m_lastOpenedDirectory)); } else { qWarning("File dialog did not execute"); return; } if (filename.endsWith(".zip")) { auto* archive = new ZipArchive(filename); if (!archive->valid()) { qCritical("Failed to change archive"); } else { m_getsuyomi->setArchive(archive); } } else { qCritical("Unsupported file extension"); } } void GetsuyomiApp::openDirectory() { auto dialog = QFileDialog(this, tr("Open Directory"), m_lastOpenedDirectory); dialog.setFileMode(QFileDialog::Directory); QString directoryName; if (dialog.exec()) { auto filenames = dialog.selectedFiles(); if (filenames.length() == 0) { qWarning("No files selected."); return; } else if (filenames.length() > 1) { qWarning("Too many files selected %llu.", filenames.length()); return; } directoryName = filenames[0]; m_lastOpenedDirectory = dialog.directory().absolutePath(); } else { qWarning("File dialog did not execute"); return; } qDebug("Opened directory %s", qPrintable(directoryName)); } void GetsuyomiApp::quit() { QCoreApplication::quit(); } void GetsuyomiApp::configure() { Settings settings(this, m_config.shortcuts()); if (!settings.valid()) { qCritical("Invalid settings"); return; } settings.exec(); } void GetsuyomiApp::next() { m_getsuyomi->next(); } void GetsuyomiApp::back() { m_getsuyomi->back(); } void GetsuyomiApp::setSinglePageLayout() { m_getsuyomi->setPageLayout(PageLayout::Single); m_setSinglePageLayout->setEnabled(false); m_setDualPageLayout->setEnabled(true); m_setMangaLayout->setEnabled(true); } void GetsuyomiApp::setDualPageLayout() { m_getsuyomi->setPageLayout(PageLayout::Dual); m_setSinglePageLayout->setEnabled(true); m_setDualPageLayout->setEnabled(false); m_setMangaLayout->setEnabled(true); } void GetsuyomiApp::setMangaLayout() { m_getsuyomi->setPageLayout(PageLayout::Manga); m_setSinglePageLayout->setEnabled(true); m_setDualPageLayout->setEnabled(true); m_setMangaLayout->setEnabled(false); } void GetsuyomiApp::closeEvent(QCloseEvent* event) { writeSettings(); QMainWindow::closeEvent(event); } void GetsuyomiApp::readSettings() { QSettings settings{AppVendor, AppName}; restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("windowState").toByteArray()); m_lastOpenedDirectory = settings.value("lastOpenedDirectory", QDir::homePath()).toString(); } void GetsuyomiApp::writeSettings() { QSettings settings{AppVendor, AppName}; settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); settings.setValue("lastOpenedDirectory", m_lastOpenedDirectory); settings.sync(); } std::optional getEnvVar(const char* envVar) { const QByteArray defaultArray{}; if (envVar == nullptr) { return std::nullopt; } QByteArray varArray = qgetenv(envVar); if (varArray == defaultArray) { return std::nullopt; } return varArray.toStdString(); } void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup) { auto envValueOpt = getEnvVar(envVar.c_str()); std::filesystem::path envValuePath{homeVar}; if (envValueOpt == std::nullopt || envValueOpt->length() == 0) { envValuePath.append(backup); } else { envValuePath = *envValueOpt; } envValuePath.append(AppName); envValue = envValuePath; // qDebug("%s is %s", envVar.c_str(), envValue.c_str()); } GetEnvResult getEnvironment() { GetsuyomiConfig config{}; auto homeOpt = getEnvVar(HOME); if (homeOpt == std::nullopt || homeOpt->length() == 0) { qCritical("Error getting home"); return GetEnvResult::error(FudStatus::Failure); } config.home = *homeOpt; // qDebug("Home is %s", config.home.c_str()); /* If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. */ const std::string XdgDataHome{"XDG_DATA_HOME"}; getEnvVar(config.home, XdgDataHome, config.dataHome, ".local/share"); /* If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used. */ const std::string XdgConfigHome{"XDG_CONFIG_HOME"}; getEnvVar(config.home, XdgConfigHome, config.configHome, ".config"); /* If $XDG_STATE_HOME is either not set or empty, a default equal to $HOME/.local/state should be used. */ const std::string XdgStateHome{"XDG_STATE_HOME"}; getEnvVar(config.home, XdgStateHome, config.stateHome, ".local/state"); return GetEnvResult::okay(config); } } // namespace getsuyomi