diff --git a/CMakeLists.txt b/CMakeLists.txt index 97650f2..6167432 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,10 +28,12 @@ qt_add_executable(waycast) include_directories("${CMAKE_SOURCE_DIR}/lib") include_directories("${CMAKE_SOURCE_DIR}/lib/ui") +include_directories("${CMAKE_SOURCE_DIR}/lib/plugins") file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/*.cpp") file(GLOB_RECURSE LIB_UI_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/lib/ui/*.cpp") -target_sources(waycast PRIVATE ${SRC_FILES} ${LIB_UI_FILES} src/main.cpp) -target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib) +file(GLOB_RECURSE PLUGIN_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/plugins/**/*.cpp") +target_sources(waycast PRIVATE ${SRC_FILES} ${LIB_UI_FILES} ${PLUGIN_FILES} src/main.cpp) +target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib/plugins ${CMAKE_SOURCE_DIR}/src) # target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_CURRENT_BINARY_DIR}) # Bundle QML into the app (no runtime QML path headaches) @@ -62,7 +64,7 @@ endif() # Add -fmodules-ts where needed if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_compile_options(waycast PRIVATE -fmodules-ts) + target_compile_options(waycast PRIVATE -fmodules-ts -mavx2) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_options(waycast PRIVATE -fmodules-ts) + target_compile_options(waycast PRIVATE -fmodules-ts -mavx2) endif() \ No newline at end of file diff --git a/lib/files.hpp b/lib/files.hpp index 005d2cc..ff9309e 100644 --- a/lib/files.hpp +++ b/lib/files.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace files { @@ -40,6 +41,61 @@ namespace files return out; } + inline std::vector findAllFiles(const std::string& path, int max_depth = -1) + { + std::vector out; + std::error_code ec; + fs::path p(path); + + if (!fs::exists(p, ec) || !fs::is_directory(p, ec)) + return out; + + // Use recursive_directory_iterator with depth control + auto options = fs::directory_options::skip_permission_denied; + + try { + if (max_depth < 0) { + // No depth limit - use unlimited recursion + for (const auto& entry : fs::recursive_directory_iterator(p, options, ec)) { + if (ec) { + ec.clear(); + continue; + } + + if (entry.is_regular_file(ec) && !ec) { + out.push_back(entry.path()); + } + ec.clear(); + } + } else { + // Limited depth recursion + std::function recurse = [&](const fs::path& dir_path, int current_depth) { + if (current_depth > max_depth) return; + + for (const auto& entry : fs::directory_iterator(dir_path, options, ec)) { + if (ec) { + ec.clear(); + continue; + } + + if (entry.is_regular_file(ec) && !ec) { + out.push_back(entry.path()); + } else if (entry.is_directory(ec) && !ec && current_depth < max_depth) { + recurse(entry.path(), current_depth + 1); + } + ec.clear(); + } + }; + + recurse(p, 0); + } + } catch (const fs::filesystem_error&) { + // Handle any remaining filesystem errors silently + } + + return out; + } + inline std::string readFile(const std::string &filename) { std::ifstream in(filename); diff --git a/lib/fuzzy.hpp b/lib/fuzzy.hpp new file mode 100644 index 0000000..b40d5ce --- /dev/null +++ b/lib/fuzzy.hpp @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include +#include // AVX/AVX2 intrinsics + +namespace fuzzy +{ + struct FuzzyMatch + { + std::string text; + int score; + std::vector matchPositions; + + bool operator<(const FuzzyMatch &other) const + { + return score > other.score; // Higher score = better match + } + }; + + class FuzzyFinder + { + public: + // Find method for string candidates + std::vector find(const std::vector &candidates, + const std::string &query, + int limit = 10) + { + return findInternal(candidates, query, limit, + [](const std::string &candidate) -> std::string + { + return candidate; + }); + } + + // Find method for filesystem path candidates + std::vector find(const std::vector &candidates, + const std::string &query, + int limit = 10) + { + return findInternal(candidates, query, limit, + [](const std::filesystem::path &candidate) -> std::string + { + // For now, just use filename for matching + // In the future, could implement path-specific scoring + return candidate.filename().string(); + }); + } + + private: + // Generic internal find method using a converter function + template + std::vector findInternal(const std::vector &candidates, + const std::string &query, + int limit, + Converter converter) + { + if (query.empty()) + { + return {}; + } + + std::vector matches; + matches.reserve(candidates.size()); + + for (const auto &candidate : candidates) + { + std::string searchText = converter(candidate); + auto match = calculateMatch(searchText, query); + if (match.score > 0) + { + // Store the original candidate as text for display + if constexpr (std::is_same_v) + { + match.text = candidate.string(); // Full path for display + } + else + { + match.text = searchText; // Use converted text + } + matches.push_back(std::move(match)); + } + } + + // Sort by score (highest first) + std::sort(matches.begin(), matches.end()); + + // Limit results + if (limit > 0 && matches.size() > static_cast(limit)) + { + matches.resize(limit); + } + + return matches; + } + + // Check if AVX2 is available at runtime + bool hasAVX2() const + { + return __builtin_cpu_supports("avx2"); + } + + // AVX2-accelerated character search - finds next occurrence of query_char in text + size_t findNextCharAVX2(const std::string &text, char query_char, size_t start_pos) const + { + const size_t text_len = text.length(); + if (start_pos >= text_len) + return std::string::npos; + + const char *text_ptr = text.c_str() + start_pos; + const size_t remaining = text_len - start_pos; + + // Process 32 characters at a time with AVX2 + const size_t avx_chunks = remaining / 32; + const __m256i query_vec = _mm256_set1_epi8(query_char); + + for (size_t chunk = 0; chunk < avx_chunks; ++chunk) + { + const __m256i text_vec = _mm256_loadu_si256(reinterpret_cast(text_ptr + chunk * 32)); + const __m256i cmp_result = _mm256_cmpeq_epi8(query_vec, text_vec); + const uint32_t match_mask = _mm256_movemask_epi8(cmp_result); + + if (match_mask != 0) + { + // Found a match - find the first set bit + const int first_match = __builtin_ctz(match_mask); + return start_pos + chunk * 32 + first_match; + } + } + + // Handle remaining characters with scalar code + const size_t remaining_start = start_pos + avx_chunks * 32; + for (size_t i = remaining_start; i < text_len; ++i) + { + if (text[i] == query_char) + { + return i; + } + } + + return std::string::npos; + } + + FuzzyMatch calculateMatch(const std::string &text, const std::string &query) + { + // Choose algorithm based on AVX2 availability and string length + if (hasAVX2() && text.length() > 64 && query.length() > 2) + { + return calculateMatchAVX2(text, query); + } + else + { + return calculateMatchScalar(text, query); + } + } + + // AVX2-accelerated fuzzy matching + FuzzyMatch calculateMatchAVX2(const std::string &text, const std::string &query) + { + FuzzyMatch match; + match.text = text; + match.score = 0; + + if (query.empty() || text.empty()) + { + return match; + } + + std::string lowerText = toLower(text); + std::string lowerQuery = toLower(query); + + size_t textIdx = 0; + size_t queryIdx = 0; + int consecutiveBonus = 0; + bool inConsecutiveMatch = false; + + // Use AVX2 to accelerate character searching + while (queryIdx < lowerQuery.length()) + { + char queryChar = lowerQuery[queryIdx]; + + // Use AVX2 to find next occurrence of query character + size_t foundPos = findNextCharAVX2(lowerText, queryChar, textIdx); + + if (foundPos == std::string::npos) + { + break; // Character not found + } + + textIdx = foundPos; + match.matchPositions.push_back(textIdx); + + // Calculate score (same logic as before) + int charScore = 10; + + if (inConsecutiveMatch && foundPos == match.matchPositions[match.matchPositions.size() - 2] + 1) + { + consecutiveBonus += 5; + charScore += consecutiveBonus; + } + else + { + consecutiveBonus = 0; + inConsecutiveMatch = true; + } + + if (textIdx == 0 || !std::isalnum(lowerText[textIdx - 1])) + { + charScore += 15; + } + + if (text[textIdx] == query[queryIdx]) + { + charScore += 5; + } + + match.score += charScore; + queryIdx++; + textIdx++; // Move past current match + } + + // Apply penalties and validation + if (queryIdx < lowerQuery.length()) + { + match.score -= (lowerQuery.length() - queryIdx) * 3; + } + + if (queryIdx < lowerQuery.length()) + { + match.score = 0; + match.matchPositions.clear(); + } + + return match; + } + + // Original scalar implementation as fallback + FuzzyMatch calculateMatchScalar(const std::string &text, const std::string &query) + { + FuzzyMatch match; + match.text = text; + match.score = 0; + + if (query.empty() || text.empty()) + { + return match; + } + + std::string lowerText = toLower(text); + std::string lowerQuery = toLower(query); + + // Simple fuzzy matching algorithm + size_t textIdx = 0; + size_t queryIdx = 0; + int consecutiveBonus = 0; + bool inConsecutiveMatch = false; + + while (textIdx < lowerText.length() && queryIdx < lowerQuery.length()) + { + if (lowerText[textIdx] == lowerQuery[queryIdx]) + { + match.matchPositions.push_back(textIdx); + + // Base score for character match + int charScore = 10; + + // Bonus for consecutive characters + if (inConsecutiveMatch) + { + consecutiveBonus += 5; + charScore += consecutiveBonus; + } + else + { + consecutiveBonus = 0; + inConsecutiveMatch = true; + } + + // Bonus for word boundaries (start of word) + if (textIdx == 0 || !std::isalnum(lowerText[textIdx - 1])) + { + charScore += 15; + } + + // Bonus for exact case match + if (text[textIdx] == query[queryIdx]) + { + charScore += 5; + } + + match.score += charScore; + queryIdx++; + } + else + { + inConsecutiveMatch = false; + consecutiveBonus = 0; + } + textIdx++; + } + + // Penalty for unmatched characters in query + if (queryIdx < lowerQuery.length()) + { + match.score -= (lowerQuery.length() - queryIdx) * 3; + } + + // If we didn't match all query characters, score is 0 + if (queryIdx < lowerQuery.length()) + { + match.score = 0; + match.matchPositions.clear(); + } + + return match; + } + + std::string toLower(const std::string &str) + { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return result; + } + }; +} \ No newline at end of file diff --git a/lib/plugins/PluginInterface.hpp b/lib/plugins/PluginInterface.hpp new file mode 100644 index 0000000..19d07c1 --- /dev/null +++ b/lib/plugins/PluginInterface.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "../ui/ListItem.hpp" +#include +#include +#include + +namespace plugins +{ + class SearchPlugin + { + public: + virtual ~SearchPlugin() = default; + + // Search for items based on query + virtual std::vector search(const QString &query) = 0; + + // Get all items (used for initial display or empty query) + virtual std::vector getAllItems() = 0; + + // Plugin identification + virtual QString pluginName() const = 0; + virtual QString pluginDescription() const = 0; + + // Priority for ordering results (higher = higher priority) + virtual int priority() const { return 0; } + + // Whether this plugin should be active by default + virtual bool isEnabled() const { return true; } + }; + + using SearchPluginPtr = std::unique_ptr; +} \ No newline at end of file diff --git a/lib/plugins/PluginManager.hpp b/lib/plugins/PluginManager.hpp new file mode 100644 index 0000000..c204de5 --- /dev/null +++ b/lib/plugins/PluginManager.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "PluginInterface.hpp" +#include "../ui/ListItem.hpp" +#include +#include +#include + +namespace plugins +{ + class PluginManager + { + public: + static PluginManager& instance() + { + static PluginManager instance; + return instance; + } + + void registerPlugin(SearchPluginPtr plugin) + { + if (plugin && plugin->isEnabled()) + { + m_plugins.push_back(std::move(plugin)); + + // Sort plugins by priority (higher priority first) + std::sort(m_plugins.begin(), m_plugins.end(), + [](const SearchPluginPtr& a, const SearchPluginPtr& b) { + return a->priority() > b->priority(); + }); + } + } + + std::vector search(const QString& query) + { + std::vector allResults; + + for (const auto& plugin : m_plugins) + { + if (query.isEmpty()) + { + auto items = plugin->getAllItems(); + allResults.insert(allResults.end(), items.begin(), items.end()); + } + else + { + auto results = plugin->search(query); + allResults.insert(allResults.end(), results.begin(), results.end()); + } + } + + return allResults; + } + + std::vector getAllItems() + { + std::vector allItems; + + for (const auto& plugin : m_plugins) + { + auto items = plugin->getAllItems(); + allItems.insert(allItems.end(), items.begin(), items.end()); + } + + return allItems; + } + + const std::vector& getPlugins() const + { + return m_plugins; + } + + private: + PluginManager() = default; + std::vector m_plugins; + }; +} \ No newline at end of file diff --git a/lib/ui/AppListModel.cpp b/lib/ui/AppListModel.cpp index c57e0a8..ab8947a 100644 --- a/lib/ui/AppListModel.cpp +++ b/lib/ui/AppListModel.cpp @@ -1,5 +1,5 @@ #include "AppListModel.hpp" -#include "DesktopAppListItem.hpp" +#include "../plugins/PluginManager.hpp" #include #undef signals @@ -58,8 +58,9 @@ void AppListModel::loadItems() beginResetModel(); m_items.clear(); - // Add desktop applications - addDesktopApps(); + // Get items from all registered plugins + auto& pluginManager = plugins::PluginManager::instance(); + m_items = pluginManager.getAllItems(); updateFilteredItems(); endResetModel(); @@ -91,20 +92,19 @@ void AppListModel::setSearchText(const QString &searchText) emit searchTextChanged(); beginResetModel(); + + // Use plugin manager for search + auto& pluginManager = plugins::PluginManager::instance(); + m_items = pluginManager.search(searchText); updateFilteredItems(); + endResetModel(); } void AppListModel::addDesktopApps() { - dmenu::DEVec apps = dmenu::get_dmenu_app_data(); - if (!apps) - return; - - for (const auto &entry : *apps) { - auto item = std::make_shared(entry); - m_items.push_back(item); - } + // This method is deprecated - plugins now handle data loading + // Kept for backward compatibility but does nothing } void AppListModel::addItems(const std::vector &items) @@ -118,11 +118,8 @@ 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) { - const ListItemPtr &item = m_items[i]; - - if (item->matches(m_searchText)) { - m_filteredIndexes.push_back(static_cast(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 06dfdba..c9f1c5c 100644 --- a/lib/ui/AppListModel.hpp +++ b/lib/ui/AppListModel.hpp @@ -28,7 +28,7 @@ public: Q_INVOKABLE void executeItem(int index); // Add items from different sources - void addDesktopApps(); + 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 new file mode 100644 index 0000000..6f2b9da --- /dev/null +++ b/lib/ui/GenericListItem.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "ListItem.hpp" +#include +#include + +// Generic implementation that plugin developers can use without writing boilerplate +class GenericListItem : public ListItem +{ +public: + GenericListItem(const QString& name, + const QString& description, + const QUrl& iconUrl, + const QString& itemType, + std::function executeFunc = nullptr) + : m_name(name) + , m_description(description) + , m_iconUrl(iconUrl) + , m_itemType(itemType) + , m_executeFunc(executeFunc) + { + } + + QString name() const override { return m_name; } + QString description() const override { return m_description; } + QUrl iconUrl() const override { return m_iconUrl; } + QString itemType() const override { return m_itemType; } + + void execute() override + { + if (m_executeFunc) { + m_executeFunc(); + } + } + + // Allow updating properties after construction + void setName(const QString& name) { m_name = name; } + void setDescription(const QString& description) { m_description = description; } + void setIconUrl(const QUrl& iconUrl) { m_iconUrl = iconUrl; } + void setExecuteFunction(std::function func) { m_executeFunc = func; } + +private: + QString m_name; + QString m_description; + QUrl m_iconUrl; + QString m_itemType; + std::function m_executeFunc; +}; + +// Convenience factory functions for common use cases +namespace ListItems { + + // Create an application-style item + inline ListItemPtr createApplication(const QString& name, + const QString& exec, + const QUrl& icon = QUrl()) + { + 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); + } + ); + } + + // Create a file-style item + inline ListItemPtr createFile(const QString& filename, + const QString& path, + const QUrl& icon = QUrl()) + { + return std::make_shared( + filename, path, icon, "file", + [path]() { + QProcess::startDetached("xdg-open", QStringList() << path); + } + ); + } + + // Create a generic item with custom action + inline ListItemPtr createItem(const QString& name, + const QString& description, + const QString& itemType, + std::function action = nullptr, + const QUrl& icon = QUrl()) + { + return std::make_shared(name, description, icon, itemType, action); + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index dfa46c9..c2a7101 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,11 @@ #include "files.hpp" #define signals public +#include "fuzzy.hpp" + #include "ui/AppListModel.hpp" +#include "plugins/PluginManager.hpp" +#include "plugins/DesktopAppPlugin.hpp" #include #include #include @@ -18,67 +22,67 @@ #include #include -std::vector split(std::string s, char delimiter) -{ - std::vector items; - std::string line; - std::stringstream ss(s); - - while (std::getline(ss, line, delimiter)) - { - items.push_back(line); - } - - return items; -} - int main(int argc, char *argv[]) { - // dmenu::DEVec apps = dmenu::get_dmenu_app_data(); - // for (auto &app : *apps.get()) - // { - // std::cout << app.iconPath() << std::endl; - // // std::cout << std::format("---\nName: {}\nID: {}\nIcon: {}\nExec: {}\nDisp: {}\n---\n", app.id, app.name, app.iconPath(), app.exec, app.display ? "yes" : "no"); - // } - - QGuiApplication app(argc, argv); - QCoreApplication::setApplicationName("waycast"); - - // Enable system theme support - app.setDesktopSettingsAware(true); - - // Ensure we follow system color scheme - app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true); - - QQmlApplicationEngine engine; - - // 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); }); - - engine.loadFromModule("WayCast", "Main"); - - // Get the root objects and configure layer shell - auto rootObjects = engine.rootObjects(); - if (!rootObjects.isEmpty()) + 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) { - 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(); - } - } + std::cout << " " << match.text << " (score: " << match.score << ")\n"; } - return app.exec(); + 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"); + + // // Initialize plugin system + // auto& pluginManager = plugins::PluginManager::instance(); + // pluginManager.registerPlugin(std::make_unique()); + + // // Enable system theme support + // app.setDesktopSettingsAware(true); + + // // Ensure we follow system color scheme + // app.setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles, true); + + // QQmlApplicationEngine engine; + + // // 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); }); + + // 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); + + // // 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 new file mode 100644 index 0000000..ed2f16a --- /dev/null +++ b/src/plugins/DesktopAppPlugin.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "../../lib/plugins/PluginInterface.hpp" +#include "../../lib/dmenu.hpp" +#include "DesktopAppPlugin/DesktopAppListItem.hpp" +#include + +namespace plugins +{ + class DesktopAppPlugin : public SearchPlugin + { + public: + DesktopAppPlugin() + { + loadDesktopEntries(); + } + + std::vector search(const QString &query) override + { + std::vector results; + + for (const auto &item : m_allItems) + { + if (item->matches(query)) + { + results.push_back(item); + } + } + + return results; + } + + std::vector getAllItems() override + { + return m_allItems; + } + + QString pluginName() const override + { + return "Desktop Applications"; + } + + QString pluginDescription() const override + { + return "Searches installed desktop applications"; + } + + int priority() const override + { + return 100; // High priority for applications + } + + private: + void loadDesktopEntries() + { + auto desktopEntries = dmenu::get_dmenu_app_data(); + + for (const auto &entry : *desktopEntries) + { + auto listItem = std::make_shared(entry); + m_allItems.push_back(listItem); + } + } + + std::vector m_allItems; + }; +} \ No newline at end of file diff --git a/lib/ui/DesktopAppListItem.cpp b/src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp similarity index 100% rename from lib/ui/DesktopAppListItem.cpp rename to src/plugins/DesktopAppPlugin/DesktopAppListItem.cpp diff --git a/lib/ui/DesktopAppListItem.hpp b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp similarity index 86% rename from lib/ui/DesktopAppListItem.hpp rename to src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp index d84813f..bc66056 100644 --- a/lib/ui/DesktopAppListItem.hpp +++ b/src/plugins/DesktopAppPlugin/DesktopAppListItem.hpp @@ -1,9 +1,9 @@ #pragma once -#include "ListItem.hpp" +#include "../../../lib/ui/ListItem.hpp" #undef signals -#include "../dmenu.hpp" +#include "../../../lib/dmenu.hpp" #define signals public class DesktopAppListItem : public ListItem diff --git a/src/plugins/ExamplePlugin.hpp b/src/plugins/ExamplePlugin.hpp new file mode 100644 index 0000000..438b062 --- /dev/null +++ b/src/plugins/ExamplePlugin.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "../../lib/plugins/PluginInterface.hpp" +#include "../../lib/ui/GenericListItem.hpp" + +namespace plugins +{ + // Example of how simple it is to create a new plugin + class ExamplePlugin : public SearchPlugin + { + public: + std::vector search(const QString& query) override + { + std::vector results; + + // Example: Create some sample items that match any query + if (query.contains("test", Qt::CaseInsensitive)) { + // Using the convenient factory function + results.push_back(ListItems::createItem( + "Test Item 1", + "This is a test item", + "example", + []() { /* custom action */ } + )); + + // Or create directly with GenericListItem + results.push_back(std::make_shared( + "Test Item 2", + "Another test item", + QUrl(), // no icon + "example", + []() { + // Custom execute action + qDebug() << "Test item executed!"; + } + )); + } + + return results; + } + + std::vector getAllItems() override + { + // Return some default items + return { + ListItems::createItem("Example Item", "Always visible", "example") + }; + } + + QString pluginName() const override { return "Example Plugin"; } + QString pluginDescription() const override { return "Demonstrates easy plugin creation"; } + int priority() const override { return 10; } // Low priority + }; +} \ No newline at end of file diff --git a/src/plugins/FileSystemPlugin.hpp b/src/plugins/FileSystemPlugin.hpp new file mode 100644 index 0000000..5bfdd0d --- /dev/null +++ b/src/plugins/FileSystemPlugin.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "../../lib/plugins/PluginInterface.hpp" +#include "../../lib/files.hpp" +#include "../../lib/ui/GenericListItem.hpp" +#include +#include +#include +#include + +namespace plugins +{ + + class FileSystemPlugin : public SearchPlugin + { + public: + FileSystemPlugin(const QString& searchPath = QDir::homePath()) + : m_searchPath(searchPath.toStdString()) + { + } + + std::vector search(const QString& query) override + { + std::vector results; + + if (query.length() < 2) // Avoid expensive searches for short queries + return results; + + try { + for (const auto& entry : std::filesystem::recursive_directory_iterator(m_searchPath)) { + if (entry.is_regular_file()) { + auto filename = QString::fromStdString(entry.path().filename().string()); + if (filename.contains(query, Qt::CaseInsensitive)) { + auto item = ListItems::createFile( + filename, + QString::fromStdString(entry.path().string()) + ); + results.push_back(item); + + if (results.size() >= 50) // Limit results to avoid performance issues + break; + } + } + } + } catch (const std::filesystem::filesystem_error&) { + // Handle permission errors silently + } + + return results; + } + + std::vector getAllItems() override + { + // For file system, don't return all files (too many) + // Instead return an empty list - files only show up on search + return {}; + } + + QString pluginName() const override + { + return "File System"; + } + + QString pluginDescription() const override + { + return "Searches files in the home directory"; + } + + int priority() const override + { + return 50; // Lower priority than applications + } + + private: + std::string m_searchPath; + }; +} \ No newline at end of file