From 33ab4308d1e02de1ea64db51effca0de745d37ec Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Wed, 27 Aug 2025 23:01:30 -0400 Subject: [PATCH] Great things coming up --- .envrc | 16 ++++ .gitignore | 4 + CLAUDE.md | 74 ++++++++++++++++++ CMakeLists.txt | 67 ++++++++++++++++ Makefile | 11 +++ README.md | 3 + build.zig | 57 ++++++++++++++ lib/dmenu.hpp | 106 +++++++++++++++++++++++++ lib/files.hpp | 53 +++++++++++++ shell.nix | 35 +++++++++ src/main.cpp | 72 +++++++++++++++++ src/qrc_resources.cpp | 178 ++++++++++++++++++++++++++++++++++++++++++ ui/Main.qml | 123 +++++++++++++++++++++++++++++ ui/resources.qrc | 5 ++ 14 files changed, 804 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 build.zig create mode 100644 lib/dmenu.hpp create mode 100644 lib/files.hpp create mode 100644 shell.nix create mode 100644 src/main.cpp create mode 100644 src/qrc_resources.cpp create mode 100644 ui/Main.qml create mode 100644 ui/resources.qrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..93bfe1c --- /dev/null +++ b/.envrc @@ -0,0 +1,16 @@ +# Use the Nix shell +use nix + +# Optional: Layout for different build types +layout() { + case $1 in + cpp) + # Add any C++ specific environment setup here + export CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Debug} + export CMAKE_EXPORT_COMPILE_COMMANDS=ON + ;; + esac +} + +# Activate the cpp layout +layout cpp \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70a234b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.vscode +.zig-cache +zig-out \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..af492af --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +WayCast is a Raycast-like application launcher for Linux, built with C++20 and Qt6. It scans XDG desktop entries to provide a searchable interface for launching applications on Wayland/X11. + +## Build System + +The project supports three build systems: + +### Primary: CMake with Ninja (Recommended) +```bash +# Configure and build +make configure # or: cmake -S . -B build -G Ninja +make bld # or: cmake --build build +make run # or: ./build/waycast + +# Combined: make br (build + run) +# Full rebuild: make all (configure + build + run) +``` + +### Alternative: Zig Build +```bash +zig build run +``` + +### Development Environment: Nix Shell +```bash +nix-shell # Sets up Qt6, CMake, Ninja, Clang, and required dependencies +``` + +## Architecture + +### Core Components + +- **main.cpp**: Entry point, currently configured for CLI testing of desktop entry parsing +- **dmenu.hpp/namespace**: Desktop entry parsing using GIO/GLib to read XDG application data +- **files.hpp/namespace**: File system utilities for scanning directories and reading files +- **ui/Main.qml**: Qt Quick interface (currently minimal, Qt GUI code commented out in main) + +### Key Classes + +- `dmenu::DesktopEntry`: Parses .desktop files using GDesktopAppInfo, extracts app metadata (name, icon, executable, display flags) +- `files::findFilesWithExtension()`: Recursively scans directories for files with specific extensions +- `DEVec`: Type alias for `std::unique_ptr>` + +### Current State + +The application is in active development: +- Main Qt GUI loop is commented out in main.cpp:56-64 +- Currently runs as CLI tool that prints discovered application IDs +- Desktop entry scanning logic is functional +- Qt QML interface exists but is not connected + +## Dependencies + +- **Qt6**: Core, Gui, Qml, Quick, QuickControls2 +- **GIO/GLib**: For XDG desktop entry parsing +- **C++20**: Uses std::format, filesystem, and modern C++ features +- **CMake 3.21+**: Build system +- **Ninja**: Preferred generator + +## Development Notes + +- Qt resources are bundled via CMakeLists.txt (qt_add_qml_module) +- Uses C++20 modules compilation flags for GCC/Clang +- Nix shell provides complete development environment with Qt6 Wayland support +- Built-in RPATH configuration for Linux runtime library discovery + +## Project Conventions + +- We're not using header files. Prioritize .hpp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5826ebe --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.21) +project(waycast LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Use Ninja if available (optional) +if(NOT CMAKE_GENERATOR) + set(CMAKE_GENERATOR "Ninja" CACHE INTERNAL "" FORCE) +endif() + +find_package(PkgConfig REQUIRED) +find_package(Qt6 REQUIRED COMPONENTS + Core + Gui + Qml + Quick + QuickControls2 +) +find_package(LayerShellQt REQUIRED) + +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +qt_standard_project_setup() + +qt_add_executable(waycast) + +include_directories("${CMAKE_SOURCE_DIR}/lib") +include_directories("${CMAKE_SOURCE_DIR}/lib") +file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/*.cpp") +target_sources(waycast PRIVATE ${SRC_FILES} src/main.cpp) +target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib) +# target_include_directories(waycast PRIVATE ${CMAKE_SOURCE_DIR}/lib ${CMAKE_CURRENT_BINARY_DIR}) + +# Bundle QML into the app (no runtime QML path headaches) +qt_add_qml_module(waycast + URI WayCast + VERSION 1.0 + QML_FILES + ui/Main.qml +) + +target_link_libraries(waycast PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Qml + Qt6::Quick + PkgConfig::GIO + LayerShellQt::Interface +) + +# On Linux, ensure plugins/libs are found at runtime if needed +# (usually fine when running inside nix shell) +if(UNIX AND NOT APPLE) + set_target_properties(waycast PROPERTIES + INSTALL_RPATH "$ORIGIN" + BUILD_WITH_INSTALL_RPATH TRUE + ) +endif() + +# Add -fmodules-ts where needed +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(waycast PRIVATE -fmodules-ts) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(waycast PRIVATE -fmodules-ts) +endif() \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c97162e --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +configure: + rm -rf build + cmake -S . -B build -G Ninja +bld: + cmake --build build +run: + ./build/waycast + +br: bld run + +all: configure bld run \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..acebca6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# WayCast + +Raycast for linux. We deserve this. \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..2e52a70 --- /dev/null +++ b/build.zig @@ -0,0 +1,57 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + // const target = b.standardTargetOptions(.{}); + // const optimize = b.standardOptimizeOption(.{}); + + // Step 1: Generate Qt resources automatically + const rcc_cmd = b.addSystemCommand(&.{ "rcc", "ui/resources.qrc", "-o", "qrc_resources.cpp" }); + + // Create the executable + const mod = b.createModule(.{ + .target = b.graph.host, + }); + + mod.addCSourceFiles(.{ + .files = &.{ + "main.cpp", + "qrc_resources.cpp", + }, + .flags = &.{ + "-std=c++20", + "-Wall", + "-Wextra", + }, + }); + + const exe = b.addExecutable(.{ + .name = "waycast", + .root_module = mod, + }); + + exe.step.dependOn(&rcc_cmd.step); + + // Link C++ standard library + exe.linkLibCpp(); + // Link Qt6 + exe.linkSystemLibrary("Qt6Core"); + exe.linkSystemLibrary("Qt6Quick"); + exe.linkSystemLibrary("Qt6Gui"); + exe.linkSystemLibrary("Qt6Qml"); + + // Install the executable + b.installArtifact(exe); + + // Create a run step + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + // Allow passing arguments to the program + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // Create the run step + const run_step = b.step("run", "Run the application"); + run_step.dependOn(&run_cmd.step); +} diff --git a/lib/dmenu.hpp b/lib/dmenu.hpp new file mode 100644 index 0000000..f3d9f93 --- /dev/null +++ b/lib/dmenu.hpp @@ -0,0 +1,106 @@ +#pragma once +#include "files.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace dmenu +{ + class DesktopEntry + { + public: + std::string id; + std::string name; + std::string icon_path; + std::string exec; + bool display = false; + DesktopEntry(std::string path) + { + GDesktopAppInfo *info = g_desktop_app_info_new_from_filename(path.c_str()); + if (!info) { + std::cerr << "Failed to create desktop app info for: " << path << std::endl; + return; + } + + GAppInfo *app = G_APP_INFO(info); + if (!app) { + std::cerr << "Failed to get app info for: " << path << std::endl; + g_object_unref(info); + return; + } + + const char *app_id = g_app_info_get_id(app); + if (app_id) id = app_id; + + const char *app_name = g_app_info_get_name(app); + if (app_name) name = app_name; + + GIcon *icon = g_app_info_get_icon(app); + if (icon) { + char* icon_str = g_icon_to_string(icon); + if (icon_str) { + icon_path = icon_str; + g_free(icon_str); + } + } + + const char *ex = g_app_info_get_executable(app); + if (ex) + exec = ex; + + display = g_desktop_app_info_get_boolean(info, "NoDisplay") ? false : true; + g_object_unref(info); + } + + std::string iconPath() + { + return icon_path; + } + }; + + 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; + } + + using DEVec = std::unique_ptr>; + DEVec get_dmenu_app_data() + { + DEVec out = std::make_unique>(); + const char* env_dirs = std::getenv("XDG_DATA_DIRS"); + if (!env_dirs) { + std::cerr << "XDG_DATA_DIRS environment variable not set" << std::endl; + return out; + } + std::string dataDirs = env_dirs; + std::vector paths = split(dataDirs, ':'); + for (std::string &p : paths) + p.append("/applications"); + + for (std::string &p : paths) + { + std::vector desktopFiles = files::findFilesWithExtension(p, ".desktop"); + + for (const auto &dfile : desktopFiles) + { + out->emplace_back(dfile.string()); + } + } + + return out; + } + +} \ No newline at end of file diff --git a/lib/files.hpp b/lib/files.hpp new file mode 100644 index 0000000..721e104 --- /dev/null +++ b/lib/files.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace files +{ + namespace fs = std::filesystem; + + std::vector findFilesWithExtension(const std::string path, const std::string ext) + { + std::vector out; + std::unordered_set seen; // canonicalized paths to dedupe + std::error_code ec; + fs::path p(path); + + if (!fs::exists(p, ec) || !fs::is_directory(p, ec)) + return out; + + for (const auto &entry : fs::directory_iterator(p, ec)) + { + if (ec) + { + ec.clear(); + continue; + } + + const auto &filePath = entry.path(); + if (filePath.extension() == ext && fs::is_regular_file(filePath, ec)) + { + out.push_back(filePath); + } + } + + return out; + } + + std::string readFile(const std::string &filename) + { + std::ifstream in(filename); + if (!in) + throw std::runtime_error("Could not open file"); + + std::ostringstream ss; + ss << in.rdbuf(); // dump entire buffer into string + return ss.str(); + } +} \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..a4c83a5 --- /dev/null +++ b/shell.nix @@ -0,0 +1,35 @@ +{ + pkgs ? import { }, +}: +let + qt = pkgs.qt6Packages; +in +pkgs.mkShell { + buildInputs = [ + pkgs.cmake + pkgs.ninja + pkgs.clang_18 + pkgs.lldb + pkgs.ccacheWrapper + pkgs.glib + pkgs.glib.dev + qt.full + qt.qtbase + qt.qtdeclarative # <- provides QML/Quick + Quick Controls 2 (QtQuick.Controls) + qt.qtwayland # <- provides the Wayland platform plugin + pkgs.kdePackages.layer-shell-qt # <- provides layer shell support + pkgs.kdePackages.layer-shell-qt.dev # <- provides headers + pkgs.pkg-config + # qt.qttools + # qt.qtshadertools + ]; + + shellHook = '' + export QT_PLUGIN_PATH='${qt.full}' + export QML2_IMPORT_PATH='${qt.full}:${pkgs.kdePackages.layer-shell-qt}/lib/qt-6/qml' + export QT_QPA_PLATFORM=wayland + echo "--------------------------------------------" + echo "QT_PLUGIN_PATH: $QT_PLUGIN_PATH" + echo "QML2_IMPORT_PATH: $QML2_IMPORT_PATH" + ''; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..26c1b9a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,72 @@ +#include "dmenu.hpp" +#include "files.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 << 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); + + QQmlApplicationEngine engine; + + // 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/qrc_resources.cpp b/src/qrc_resources.cpp new file mode 100644 index 0000000..0fc3d93 --- /dev/null +++ b/src/qrc_resources.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** Resource object code +** +** Created by: The Resource Compiler for Qt version 5.15.17 +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +static const unsigned char qt_resource_data[] = { + // /home/javi/projects/waycast/ui/main.qml + 0x0,0x0,0x5,0x8e, + 0x69, + 0x6d,0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63,0x6b,0x20,0x32,0x2e, + 0x31,0x35,0xa,0x69,0x6d,0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63, + 0x6b,0x2e,0x57,0x69,0x6e,0x64,0x6f,0x77,0x20,0x32,0x2e,0x31,0x35,0xa,0x69,0x6d, + 0x70,0x6f,0x72,0x74,0x20,0x51,0x74,0x51,0x75,0x69,0x63,0x6b,0x2e,0x43,0x6f,0x6e, + 0x74,0x72,0x6f,0x6c,0x73,0x20,0x32,0x2e,0x31,0x35,0xa,0xa,0x41,0x70,0x70,0x6c, + 0x69,0x63,0x61,0x74,0x69,0x6f,0x6e,0x57,0x69,0x6e,0x64,0x6f,0x77,0x20,0x7b,0xa, + 0x20,0x20,0x20,0x20,0x69,0x64,0x3a,0x20,0x77,0x69,0x6e,0x64,0x6f,0x77,0xa,0x20, + 0x20,0x20,0x20,0x77,0x69,0x64,0x74,0x68,0x3a,0x20,0x36,0x34,0x30,0xa,0x20,0x20, + 0x20,0x20,0x68,0x65,0x69,0x67,0x68,0x74,0x3a,0x20,0x34,0x38,0x30,0xa,0x20,0x20, + 0x20,0x20,0x76,0x69,0x73,0x69,0x62,0x6c,0x65,0x3a,0x20,0x74,0x72,0x75,0x65,0xa, + 0x20,0x20,0x20,0x20,0x74,0x69,0x74,0x6c,0x65,0x3a,0x20,0x71,0x73,0x54,0x72,0x28, + 0x22,0x51,0x74,0x20,0x51,0x75,0x69,0x63,0x6b,0x20,0x48,0x65,0x6c,0x6c,0x6f,0x20, + 0x57,0x6f,0x72,0x6c,0x64,0x22,0x29,0xa,0x20,0x20,0x20,0x20,0xa,0x20,0x20,0x20, + 0x20,0x52,0x65,0x63,0x74,0x61,0x6e,0x67,0x6c,0x65,0x20,0x7b,0xa,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x61,0x6e,0x63,0x68,0x6f,0x72,0x73,0x2e,0x66,0x69,0x6c, + 0x6c,0x3a,0x20,0x70,0x61,0x72,0x65,0x6e,0x74,0xa,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x67,0x72,0x61,0x64,0x69,0x65,0x6e,0x74,0x3a,0x20,0x47,0x72,0x61,0x64, + 0x69,0x65,0x6e,0x74,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x47,0x72,0x61,0x64,0x69,0x65,0x6e,0x74,0x53,0x74,0x6f,0x70,0x20, + 0x7b,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3a,0x20,0x30,0x2e,0x30,0x3b, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3a,0x20,0x22,0x23,0x34,0x41,0x39,0x30,0x45,0x32, + 0x22,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x47,0x72,0x61,0x64,0x69,0x65,0x6e,0x74,0x53,0x74,0x6f,0x70,0x20,0x7b,0x20,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3a,0x20,0x31,0x2e,0x30,0x3b,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x3a,0x20,0x22,0x23,0x33,0x35,0x37,0x41,0x42,0x44,0x22,0x20,0x7d, + 0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x43,0x6f,0x6c,0x75, + 0x6d,0x6e,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x61,0x6e,0x63,0x68,0x6f,0x72,0x73,0x2e,0x63,0x65,0x6e,0x74,0x65,0x72,0x49, + 0x6e,0x3a,0x20,0x70,0x61,0x72,0x65,0x6e,0x74,0xa,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x73,0x70,0x61,0x63,0x69,0x6e,0x67,0x3a,0x20,0x32, + 0x30,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x54,0x65,0x78,0x74,0x20, + 0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x69,0x64,0x3a,0x20,0x68,0x65,0x6c,0x6c,0x6f,0x54,0x65,0x78,0x74,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x74,0x65,0x78,0x74,0x3a,0x20,0x22,0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x51,0x74, + 0x20,0x51,0x75,0x69,0x63,0x6b,0x21,0x22,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6f,0x6e,0x74,0x2e,0x70,0x69, + 0x78,0x65,0x6c,0x53,0x69,0x7a,0x65,0x3a,0x20,0x33,0x32,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6f,0x6e,0x74, + 0x2e,0x62,0x6f,0x6c,0x64,0x3a,0x20,0x74,0x72,0x75,0x65,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x3a,0x20,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0xa,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x61,0x6e,0x63,0x68,0x6f, + 0x72,0x73,0x2e,0x68,0x6f,0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61,0x6c,0x43,0x65,0x6e, + 0x74,0x65,0x72,0x3a,0x20,0x70,0x61,0x72,0x65,0x6e,0x74,0x2e,0x68,0x6f,0x72,0x69, + 0x7a,0x6f,0x6e,0x74,0x61,0x6c,0x43,0x65,0x6e,0x74,0x65,0x72,0xa,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x42,0x75,0x74,0x74,0x6f,0x6e,0x20,0x7b,0xa,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x74,0x65,0x78, + 0x74,0x3a,0x20,0x22,0x43,0x6c,0x69,0x63,0x6b,0x20,0x6d,0x65,0x21,0x22,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x61, + 0x6e,0x63,0x68,0x6f,0x72,0x73,0x2e,0x68,0x6f,0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61, + 0x6c,0x43,0x65,0x6e,0x74,0x65,0x72,0x3a,0x20,0x70,0x61,0x72,0x65,0x6e,0x74,0x2e, + 0x68,0x6f,0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61,0x6c,0x43,0x65,0x6e,0x74,0x65,0x72, + 0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x6f,0x6e,0x43,0x6c,0x69,0x63,0x6b,0x65,0x64,0x3a,0x20,0x7b,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x68,0x65,0x6c,0x6c,0x6f,0x54,0x65,0x78,0x74,0x2e,0x74,0x65,0x78, + 0x74,0x20,0x3d,0x20,0x22,0x42,0x75,0x74,0x74,0x6f,0x6e,0x20,0x63,0x6c,0x69,0x63, + 0x6b,0x65,0x64,0x21,0x22,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x65,0x6c,0x6c,0x6f,0x54, + 0x65,0x78,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x22,0x23,0x46,0x46, + 0x44,0x37,0x30,0x30,0x22,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x42, + 0x75,0x74,0x74,0x6f,0x6e,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x74,0x65,0x78,0x74,0x3a,0x20,0x22,0x52, + 0x65,0x73,0x65,0x74,0x22,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x61,0x6e,0x63,0x68,0x6f,0x72,0x73,0x2e,0x68,0x6f, + 0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61,0x6c,0x43,0x65,0x6e,0x74,0x65,0x72,0x3a,0x20, + 0x70,0x61,0x72,0x65,0x6e,0x74,0x2e,0x68,0x6f,0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61, + 0x6c,0x43,0x65,0x6e,0x74,0x65,0x72,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x6f,0x6e,0x43,0x6c,0x69,0x63,0x6b, + 0x65,0x64,0x3a,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x68,0x65,0x6c,0x6c,0x6f,0x54, + 0x65,0x78,0x74,0x2e,0x74,0x65,0x78,0x74,0x20,0x3d,0x20,0x22,0x48,0x65,0x6c,0x6c, + 0x6f,0x2c,0x20,0x51,0x74,0x20,0x51,0x75,0x69,0x63,0x6b,0x21,0x22,0xa,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x68,0x65,0x6c,0x6c,0x6f,0x54,0x65,0x78,0x74,0x2e,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x7d,0xa,0x7d, + +}; + +static const unsigned char qt_resource_name[] = { + // ui + 0x0,0x2, + 0x0,0x0,0x7,0xb9, + 0x0,0x75, + 0x0,0x69, + // main.qml + 0x0,0x8, + 0x8,0x1,0x5a,0x5c, + 0x0,0x6d, + 0x0,0x61,0x0,0x69,0x0,0x6e,0x0,0x2e,0x0,0x71,0x0,0x6d,0x0,0x6c, + +}; + +static const unsigned char qt_resource_struct[] = { + // : + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, +0x0,0x0,0x0,0x49,0x77,0x38,0x70,0x0, + // :/ui + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2, +0x0,0x0,0x0,0x49,0x77,0x38,0x70,0x0, + // :/ui/main.qml + 0x0,0x0,0x0,0xa,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0, +0x0,0x0,0x0,0x49,0x77,0x38,0x70,0x0, + +}; + +#ifdef QT_NAMESPACE +# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name +# define QT_RCC_MANGLE_NAMESPACE0(x) x +# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b +# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b) +# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \ + QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE)) +#else +# define QT_RCC_PREPEND_NAMESPACE(name) name +# define QT_RCC_MANGLE_NAMESPACE(name) name +#endif + +#ifdef QT_NAMESPACE +namespace QT_NAMESPACE { +#endif + +bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); +bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); + +#ifdef QT_NAMESPACE +} +#endif + +int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); +int QT_RCC_MANGLE_NAMESPACE(qInitResources)() +{ + int version = 3; + QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); +int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() +{ + int version = 3; + QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) + (version, qt_resource_struct, qt_resource_name, qt_resource_data); + return 1; +} + +namespace { + struct initializer { + initializer() { QT_RCC_MANGLE_NAMESPACE(qInitResources)(); } + ~initializer() { QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); } + } dummy; +} diff --git a/ui/Main.qml b/ui/Main.qml new file mode 100644 index 0000000..5010fee --- /dev/null +++ b/ui/Main.qml @@ -0,0 +1,123 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Window +import QtQuick.Controls.Material +import QtQuick.Controls.Universal + +ApplicationWindow { + id: win + visible: false + width: 600 + height: 400 + flags: Qt.FramelessWindowHint + property int timeoutInterval: 5000 + + Shortcut { + sequence: "Escape" + onActivated: Qt.quit() + } + + Component.onCompleted: { + forceActiveFocus() + } + + Rectangle { + anchors.fill: parent + radius: 8 + border.width: 1 + border.color: palette.mid + color: palette.window + + Column { + anchors.fill: parent + anchors.margins: 10 + spacing: 5 + + TextField { + id: searchField + width: parent.width + placeholderText: "Type to search applications..." + selectByMouse: true + focus: true + + Keys.onDownPressed: listView.incrementCurrentIndex() + Keys.onUpPressed: listView.decrementCurrentIndex() + Keys.onReturnPressed: { + if (listView.currentItem) { + console.log("Selected:", listModel.get(listView.currentIndex).name) + } + } + } + + ScrollView { + width: parent.width + height: parent.height - searchField.height - parent.spacing + clip: true + + ListView { + id: listView + model: listModel + currentIndex: 0 + highlightFollowsCurrentItem: true + + highlight: Rectangle { + color: palette.highlight + radius: 4 + } + + delegate: ItemDelegate { + width: listView.width + height: 40 + + Rectangle { + anchors.fill: parent + color: parent.hovered ? palette.alternateBase : "transparent" + radius: 4 + } + + Row { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 10 + spacing: 10 + + Rectangle { + width: 24 + height: 24 + color: palette.button + radius: 4 + + Text { + anchors.centerIn: parent + text: "📱" + font.pixelSize: 16 + } + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: model.name + color: palette.text + font.pixelSize: 14 + } + } + + onClicked: { + listView.currentIndex = index + console.log("Clicked:", model.name) + } + } + } + } + } + } + + ListModel { + id: listModel + ListElement { name: "Firefox"; exec: "firefox" } + ListElement { name: "Terminal"; exec: "gnome-terminal" } + ListElement { name: "File Manager"; exec: "nautilus" } + ListElement { name: "Text Editor"; exec: "gedit" } + ListElement { name: "Calculator"; exec: "gnome-calculator" } + } +} diff --git a/ui/resources.qrc b/ui/resources.qrc new file mode 100644 index 0000000..c6bf00c --- /dev/null +++ b/ui/resources.qrc @@ -0,0 +1,5 @@ + + + main.qml + + \ No newline at end of file