diff --git a/lib/fuzzy.hpp b/lib/fuzzy.hpp index b40d5ce..e648c10 100644 --- a/lib/fuzzy.hpp +++ b/lib/fuzzy.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/lib/ui/AppListModel.cpp b/lib/ui/AppListModel.cpp index ab8947a..4cfce29 100644 --- a/lib/ui/AppListModel.cpp +++ b/lib/ui/AppListModel.cpp @@ -29,7 +29,8 @@ QVariant AppListModel::data(const QModelIndex &index, int role) const const ListItemPtr &item = m_items[itemIndex]; - switch (role) { + switch (role) + { case NameRole: return item->name(); case DescriptionRole: @@ -57,11 +58,11 @@ void AppListModel::loadItems() { beginResetModel(); m_items.clear(); - + // Get items from all registered plugins - auto& pluginManager = plugins::PluginManager::instance(); + auto &pluginManager = plugins::PluginManager::instance(); m_items = pluginManager.getAllItems(); - + updateFilteredItems(); endResetModel(); } @@ -70,7 +71,7 @@ void AppListModel::executeItem(int index) { if (index < 0 || index >= static_cast(m_filteredIndexes.size())) return; - + int itemIndex = m_filteredIndexes[index]; if (itemIndex >= static_cast(m_items.size())) return; @@ -90,26 +91,21 @@ void AppListModel::setSearchText(const QString &searchText) m_searchText = searchText; emit searchTextChanged(); - + beginResetModel(); - + // Use plugin manager for search - auto& pluginManager = plugins::PluginManager::instance(); + auto &pluginManager = plugins::PluginManager::instance(); m_items = pluginManager.search(searchText); updateFilteredItems(); - - endResetModel(); -} -void AppListModel::addDesktopApps() -{ - // This method is deprecated - plugins now handle data loading - // Kept for backward compatibility but does nothing + endResetModel(); } void AppListModel::addItems(const std::vector &items) { - for (const auto &item : items) { + for (const auto &item : items) + { m_items.push_back(item); } } @@ -117,9 +113,10 @@ void AppListModel::addItems(const std::vector &items) void AppListModel::updateFilteredItems() { m_filteredIndexes.clear(); - + // Since PluginManager now handles filtering, just show all items - for (size_t i = 0; i < m_items.size(); ++i) { + for (size_t i = 0; i < m_items.size(); ++i) + { m_filteredIndexes.push_back(static_cast(i)); } } \ No newline at end of file diff --git a/lib/ui/AppListModel.hpp b/lib/ui/AppListModel.hpp index c9f1c5c..7407dfd 100644 --- a/lib/ui/AppListModel.hpp +++ b/lib/ui/AppListModel.hpp @@ -11,7 +11,8 @@ class AppListModel : public QAbstractListModel Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged) public: - enum ItemRoles { + enum ItemRoles + { NameRole = Qt::UserRole + 1, DescriptionRole, IconRole, @@ -26,9 +27,8 @@ public: Q_INVOKABLE void loadItems(); Q_INVOKABLE void executeItem(int index); - + // Add items from different sources - void addDesktopApps(); // Deprecated - use PluginManager instead void addItems(const std::vector &items); QString searchText() const; diff --git a/lib/ui/GenericListItem.hpp b/lib/ui/GenericListItem.hpp index 6f2b9da..e737cb9 100644 --- a/lib/ui/GenericListItem.hpp +++ b/lib/ui/GenericListItem.hpp @@ -58,8 +58,9 @@ namespace ListItems { return std::make_shared( name, exec, icon, "app", [exec]() { - // Basic app execution - plugins can override for complex logic - QProcess::startDetached("/bin/sh", QStringList() << "-c" << exec); + // Use nohup and redirect output to /dev/null for proper detachment + QString detachedCommand = QString("nohup %1 >/dev/null 2>&1 &").arg(exec); + QProcess::startDetached("/bin/sh", QStringList() << "-c" << detachedCommand); } ); } @@ -72,7 +73,9 @@ namespace ListItems { return std::make_shared( filename, path, icon, "file", [path]() { - QProcess::startDetached("xdg-open", QStringList() << path); + // Use nohup and redirect output to /dev/null for proper detachment + QString detachedCommand = QString("nohup xdg-open \"%1\" >/dev/null 2>&1 &").arg(path); + QProcess::startDetached("/bin/sh", QStringList() << "-c" << detachedCommand); } ); } diff --git a/src/main.cpp b/src/main.cpp index c2a7101..84916cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #include "ui/AppListModel.hpp" #include "plugins/PluginManager.hpp" #include "plugins/DesktopAppPlugin.hpp" +#include "plugins/FileSearch/FileSearchPlugin.hpp" #include #include #include @@ -24,65 +25,66 @@ int main(int argc, char *argv[]) { - fuzzy::FuzzyFinder fuzzy; - std::string home = std::getenv("HOME"); - std::cout << "Home Directory: " << home << '\n'; - auto projects = files::findAllFiles(home.append("/Documents/wallpapers"), 1); - auto pathMatches = fuzzy.find(projects, "sun", 10); - std::cout << "Path fuzzy search for 'anime':\n"; - for (const auto &match : pathMatches) - { - std::cout << " " << match.text << " (score: " << match.score << ")\n"; - } - std::cout << "\nAll files found:\n"; - for (const auto &p : projects) - { - std::cout << " " << p.string() << std::endl; - } + // fuzzy::FuzzyFinder fuzzy; + // std::string home = std::getenv("HOME"); + // std::cout << "Home Directory: " << home << '\n'; + // auto projects = files::findAllFiles(home.append("/Documents/wallpapers"), 1); + // auto pathMatches = fuzzy.find(projects, "sun", 10); + // std::cout << "Path fuzzy search for 'anime':\n"; + // for (const auto &match : pathMatches) + // { + // std::cout << " " << match.text << " (score: " << match.score << ")\n"; + // } + // std::cout << "\nAll files found:\n"; + // for (const auto &p : projects) + // { + // std::cout << " " << p.string() << std::endl; + // } // ACTUAL APP CODE - // QGuiApplication app(argc, argv); - // QCoreApplication::setApplicationName("waycast"); + QGuiApplication app(argc, argv); + QCoreApplication::setApplicationName("waycast"); - // // Initialize plugin system - // auto& pluginManager = plugins::PluginManager::instance(); + // Initialize plugin system + auto &pluginManager = plugins::PluginManager::instance(); // pluginManager.registerPlugin(std::make_unique()); + pluginManager.registerPlugin(std::make_unique()); - // // Enable system theme support - // app.setDesktopSettingsAware(true); + // Enable system theme support + app.setDesktopSettingsAware(true); - // // Ensure we follow system color scheme - // app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true); + // Ensure we follow system color scheme + app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true); - // QQmlApplicationEngine engine; + QQmlApplicationEngine engine; - // // Register the AppListModel type with QML - // qmlRegisterType("WayCast", 1, 0, "AppListModel"); + // Register the AppListModel type with QML + qmlRegisterType("WayCast", 1, 0, "AppListModel"); - // // Set up layer shell before creating any windows - // QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() - // { QCoreApplication::exit(-1); }); + // Set up layer shell before creating any windows + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() + { QCoreApplication::exit(-1); }); - // engine.loadFromModule("WayCast", "Main"); + engine.loadFromModule("WayCast", "Main"); - // // Get the root objects and configure layer shell - // auto rootObjects = engine.rootObjects(); - // if (!rootObjects.isEmpty()) - // { - // QWindow *window = qobject_cast(rootObjects.first()); - // if (window) - // { - // LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window); - // if (layerWindow) - // { - // layerWindow->setLayer(LayerShellQt::Window::LayerTop); - // layerWindow->setAnchors({}); - // layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); + // Get the root objects and configure layer shell + auto rootObjects = engine.rootObjects(); + if (!rootObjects.isEmpty()) + { + QWindow *window = qobject_cast(rootObjects.first()); + if (window) + { + LayerShellQt::Window *layerWindow = LayerShellQt::Window::get(window); + if (layerWindow) + { + layerWindow->setLayer(LayerShellQt::Window::LayerTop); + layerWindow->setAnchors({}); + layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); - // // Now show the window after layer shell is configured - // window->show(); - // } - // } - // } - // return app.exec(); + // Now show the window after layer shell is configured + window->show(); + } + } + } + return app.exec(); } diff --git a/src/plugins/DesktopAppPlugin.hpp b/src/plugins/DesktopAppPlugin.hpp index ed2f16a..49cce04 100644 --- a/src/plugins/DesktopAppPlugin.hpp +++ b/src/plugins/DesktopAppPlugin.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../../lib/plugins/PluginInterface.hpp" -#include "../../lib/dmenu.hpp" +#include "PluginInterface.hpp" +#include "dmenu.hpp" #include "DesktopAppPlugin/DesktopAppListItem.hpp" #include diff --git a/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp index bc66056..daba9aa 100644 --- a/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp +++ b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp @@ -1,9 +1,9 @@ #pragma once -#include "../../../lib/ui/ListItem.hpp" +#include "ListItem.hpp" #undef signals -#include "../../../lib/dmenu.hpp" +#include "dmenu.hpp" #define signals public class DesktopAppListItem : public ListItem diff --git a/src/plugins/FileSearch/FileSearchExample.hpp b/src/plugins/FileSearch/FileSearchExample.hpp new file mode 100644 index 0000000..1cc1b21 --- /dev/null +++ b/src/plugins/FileSearch/FileSearchExample.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "FileSearchPlugin.hpp" +#include + +namespace plugins +{ + // Example configurations for the FileSearchPlugin + + // Configuration 1: Document searcher + inline std::unique_ptr createDocumentSearcher() { + std::vector searchDirs = { + QDir::homePath().toStdString() + "/Documents", + QDir::homePath().toStdString() + "/Desktop" + }; + + std::vector ignoreDirs = { + QDir::homePath().toStdString() + "/.cache", + QDir::homePath().toStdString() + "/.local/share/Trash" + }; + + return std::make_unique( + searchDirs, + ignoreDirs, + 2, // max depth + 500 // max files + ); + } + + // Configuration 2: Code project searcher + inline std::unique_ptr createCodeSearcher() { + std::vector searchDirs = { + QDir::homePath().toStdString() + "/projects", + QDir::homePath().toStdString() + "/dev", + QDir::homePath().toStdString() + "/code" + }; + + std::vector ignoreDirs = { + QDir::homePath().toStdString() + "/projects/node_modules", + QDir::homePath().toStdString() + "/projects/.git", + QDir::homePath().toStdString() + "/projects/build", + QDir::homePath().toStdString() + "/projects/target" + }; + + return std::make_unique( + searchDirs, + ignoreDirs, + 4, // deeper search for code projects + 1000 // more files for code projects + ); + } + + // Configuration 3: Media searcher + inline std::unique_ptr createMediaSearcher() { + std::vector searchDirs = { + QDir::homePath().toStdString() + "/Pictures", + QDir::homePath().toStdString() + "/Music", + QDir::homePath().toStdString() + "/Videos", + QDir::homePath().toStdString() + "/Downloads" + }; + + std::vector ignoreDirs = { + QDir::homePath().toStdString() + "/.thumbnails" + }; + + return std::make_unique( + searchDirs, + ignoreDirs, + 3, // medium depth + 2000 // lots of media files + ); + } +} \ No newline at end of file diff --git a/src/plugins/FileSearch/FileSearchIntegrationExample.cpp b/src/plugins/FileSearch/FileSearchIntegrationExample.cpp new file mode 100644 index 0000000..e35b58d --- /dev/null +++ b/src/plugins/FileSearch/FileSearchIntegrationExample.cpp @@ -0,0 +1,49 @@ +// Example showing how to integrate FileSearchPlugin into main.cpp +// This is not compiled - it's just a reference for how to use the plugin + +/* +#include "plugins/FileSearchPlugin.hpp" +#include "plugins/FileSearchExample.hpp" + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + QCoreApplication::setApplicationName("waycast"); + + // Initialize plugin system + auto& pluginManager = plugins::PluginManager::instance(); + + // Register desktop app plugin + pluginManager.registerPlugin(std::make_unique()); + + // Register file search plugins with different configurations + + // Option 1: Use default configuration + pluginManager.registerPlugin(std::make_unique()); + + // Option 2: Use custom configuration + std::vector customSearchDirs = { + "/home/user/Documents", + "/home/user/Projects" + }; + std::vector customIgnoreDirs = { + "/home/user/.cache", + "/home/user/Projects/node_modules" + }; + pluginManager.registerPlugin(std::make_unique( + customSearchDirs, + customIgnoreDirs, + 3, // max depth + 1000 // max files + )); + + // Option 3: Use predefined configurations + pluginManager.registerPlugin(plugins::createDocumentSearcher()); + pluginManager.registerPlugin(plugins::createMediaSearcher()); + + // Continue with rest of Qt application setup... + // ... + + return app.exec(); +} +*/ \ No newline at end of file diff --git a/src/plugins/FileSearch/FileSearchPlugin.hpp b/src/plugins/FileSearch/FileSearchPlugin.hpp new file mode 100644 index 0000000..5949415 --- /dev/null +++ b/src/plugins/FileSearch/FileSearchPlugin.hpp @@ -0,0 +1,292 @@ +#pragma once + +#include "PluginInterface.hpp" +#include "GenericListItem.hpp" +#include "files.hpp" +#include "fuzzy.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace plugins +{ + class FileSearchPlugin : public SearchPlugin + { + public: + // Constructor with configurable search and ignore directories + explicit FileSearchPlugin( + const std::vector &searchDirectories = getDefaultSearchDirs(), + int maxDepth = 3, + size_t maxFiles = 1000) + : m_searchDirectories(searchDirectories), m_maxDepth(maxDepth), m_maxFiles(maxFiles) + { + // Initialize ignore directory names set + m_ignoreDirNames = { + "node_modules", "vendor", ".git", ".svn", ".hg", + "build", "dist", "target", ".cache", "__pycache__", + ".pytest_cache", ".mypy_cache", "coverage", ".coverage", + ".tox", "venv", ".venv", "env", ".env" + }; + + // Pre-load all files from search directories + loadFiles(); + } + + std::vector search(const QString &query) override + { + std::vector results; + + if (query.length() < 2) + { + return results; // Require at least 2 characters to avoid huge result sets + } + + // Convert file paths to strings for fuzzy matching + std::vector filePaths; + filePaths.reserve(m_allFiles.size()); + + for (const auto &file : m_allFiles) + { + filePaths.push_back(file.filename().string()); // Match against filename only + } + + // Use fuzzy finder to get matches + auto fuzzyMatches = m_fuzzyFinder.find(filePaths, query.toStdString(), 50); + + // Convert fuzzy matches back to ListItems + results.reserve(fuzzyMatches.size()); + for (const auto &match : fuzzyMatches) + { + // Find the original file path that corresponds to this match + auto it = std::find_if(m_allFiles.begin(), m_allFiles.end(), + [&match](const std::filesystem::path &path) + { + return path.filename().string() == match.text; + }); + + if (it != m_allFiles.end()) + { + auto item = createFileListItem(*it, match.score); + results.push_back(item); + } + } + + return results; + } + + std::vector getAllItems() override + { + // Don't return all files by default (too many) + // Could return recent files or empty list + return {}; + } + + QString pluginName() const override + { + return "File Search"; + } + + QString pluginDescription() const override + { + return QString("Searches files in specified directories (currently monitoring %1 files)") + .arg(m_allFiles.size()); + } + + int priority() const override + { + return 25; // Lower priority than applications, higher than examples + } + + // Configuration methods + void addSearchDirectory(const std::string &directory) + { + m_searchDirectories.push_back(directory); + loadFiles(); // Reload files + } + + void addIgnoreDirName(const std::string &dirName) + { + m_ignoreDirNames.insert(dirName); + loadFiles(); // Reload files + } + + size_t getFileCount() const { return m_allFiles.size(); } + + private: + // Default search directories + static std::vector getDefaultSearchDirs() + { + std::vector dirs; + + // Add common user directories + QString home = QDir::homePath(); + dirs.push_back((home + "/Documents").toStdString()); + dirs.push_back((home + "/Desktop").toStdString()); + dirs.push_back((home + "/Downloads").toStdString()); + + // Add XDG user directories if they exist + auto xdgDirs = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); + for (const QString &dir : xdgDirs) + { + if (QDir(dir).exists()) + { + dirs.push_back(dir.toStdString()); + } + } + + return dirs; + } + + + void loadFiles() + { + m_allFiles.clear(); + + for (const auto &searchDir : m_searchDirectories) + { + if (!std::filesystem::exists(searchDir)) + { + continue; + } + + try + { + // Get all files from this directory + auto files = files::findAllFiles(searchDir, m_maxDepth); + + for (const auto &file : files) + { + // Check if this file should be ignored + if (shouldIgnoreFile(file)) + { + continue; + } + + m_allFiles.push_back(file); + + // Limit total files to prevent memory issues + if (m_allFiles.size() >= m_maxFiles) + { + return; + } + } + } + catch (const std::filesystem::filesystem_error &e) + { + // Silently skip directories we can't access + continue; + } + } + } + + bool shouldIgnoreFile(const std::filesystem::path &file) const + { + try + { + // Check if file is in any ignored directory (walk up the path) + auto current = file.parent_path(); + while (!current.empty() && current != current.root_path()) + { + std::string dirName = current.filename().string(); + + // Ignore any directory that starts with . + if (!dirName.empty() && dirName[0] == '.') + { + return true; + } + + // Ignore directories by name + if (m_ignoreDirNames.contains(dirName)) + { + return true; + } + + current = current.parent_path(); + } + + // Ignore hidden files (starting with .) + std::string fileName = file.filename().string(); + if (!fileName.empty() && fileName[0] == '.') + { + return true; + } + + // Ignore common temporary file extensions + auto extension = file.extension().string(); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + if (extension == ".tmp" || extension == ".temp" || + extension == ".log" || extension == ".cache") + { + return true; + } + + return false; + } + catch (const std::filesystem::filesystem_error &) + { + return true; // Ignore files we can't access + } + } + + ListItemPtr createFileListItem(const std::filesystem::path &filePath, int fuzzyScore) + { + QString fileName = QString::fromStdString(filePath.filename().string()); + QString fullPath = QString::fromStdString(filePath.string()); + QString parentDir = QString::fromStdString(filePath.parent_path().filename().string()); + + // Create description showing parent directory + QString description = parentDir.isEmpty() ? fullPath : QString("%1/.../%2").arg(parentDir, fileName); + + // Simple icon based on file extension + QUrl icon = getFileIcon(filePath); + + return ListItems::createFile(fileName, fullPath, icon); + } + + QUrl getFileIcon(const std::filesystem::path &filePath) const + { + QString ext = QString::fromStdString(filePath.extension().string()).toLower(); + + // Return basic icon URLs - you could expand this with a proper icon system + if (ext == ".txt" || ext == ".md" || ext == ".rst") + { + return QUrl("qrc:/icons/text-file.svg"); + } + else if (ext == ".pdf") + { + return QUrl("qrc:/icons/pdf-file.svg"); + } + else if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".bmp") + { + return QUrl("qrc:/icons/image-file.svg"); + } + else if (ext == ".mp3" || ext == ".wav" || ext == ".ogg" || ext == ".flac") + { + return QUrl("qrc:/icons/audio-file.svg"); + } + else if (ext == ".mp4" || ext == ".avi" || ext == ".mkv" || ext == ".webm") + { + return QUrl("qrc:/icons/video-file.svg"); + } + else if (ext == ".cpp" || ext == ".hpp" || ext == ".c" || ext == ".h" || ext == ".py" || ext == ".js") + { + return QUrl("qrc:/icons/code-file.svg"); + } + + return QUrl("qrc:/icons/file.svg"); + } + + // Member variables + std::vector m_searchDirectories; + std::unordered_set m_ignoreDirNames; + int m_maxDepth; + size_t m_maxFiles; + + std::vector m_allFiles; + fuzzy::FuzzyFinder m_fuzzyFinder; + }; +} \ No newline at end of file