commit 33ab4308d1e02de1ea64db51effca0de745d37ec Author: Javier Feliz Date: Wed Aug 27 23:01:30 2025 -0400 Great things coming up 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