project-gauntlet / gauntlet

Raycast-inspired open-source cross-platform application launcher with React-based plugins
Mozilla Public License 2.0
118 stars 2 forks source link
application-launcher deno linux react rust typescript

Gauntlet

Discord

Web-first cross-platform application launcher with React-based plugins.

[!NOTE] Launcher is in active development, expect bugs, missing features, incomplete ux, etc.

There will probably be breaking changes which will be documented in changelog.

https://github.com/user-attachments/assets/1aa790dc-fce9-4ac5-97d8-3b81b83acf2e

Features

OS Support
Implemented
UI
Implemented
Planned

See #13

APIs
Implemented
Planned

See #13

Getting Started

Create your own plugin

Install plugin

Plugins are installed in Settings UI. Use Git repository url of the plugin to install it.

Install application

macOS

Although it is possible to install Gauntlet by using .dmg directly, application doesn't have auto-update functionality so it is recommended to install using brew package manager.

Brew package: link

To install run:

brew install --cask gauntlet

To start, manually open application.

Windows

Although it is possible to install Gauntlet by using .msi directly, application doesn't have auto-update functionality so it is recommended to install using chocolatey package manager.

Chocolatey package: link

To install run:

choco install gauntlet

To start, manually open application.

Arch Linux

AUR package: link

To install run:

yay -S gauntlet-bin

To start systemd service run:

systemctl --user enable --now gauntlet.service

Other Linux Distributions

At the moment application is only available for Arch Linux. If you want to create a package for other distributions see Application packaging for Linux

Global Shortcut

Main window can be opened using global shortcut or CLI command:

Configuration

Plugin manifest

[gauntlet]
name = 'Plugin Name'
description = """
Plugin description
""" # required

[[preferences]] # plugin preference
name = 'testBool'
type = 'enum' # available values: 'number', 'string,' 'bool', 'enum', 'list_of_strings', 'list_of_numbers', 'list_of_enums'
default = 'item' # type of default depends on type field. Currently, list types have no default
description = "Some preference description"
enum_values = [{ label = 'Item', value = 'item'}] # defines list of available enum values, required for types "enum" and "list_of_enums"

[[entrypoint]]
id = 'ui-view' # id for entrypoint
name = 'UI view' # name of entrypoint
path = 'src/ui-view.tsx' # path to file, default export is expected to be function React Function Component
type = 'view'
description = 'Some entrypoint description' # required

[[entrypoint.preferences]] # entrypoint preference
name = 'boolPreference'
type = 'bool'
default = true
description = "bool preference description"

[[entrypoint.actions]]
id = 'someAction' # id of action, needs to align with value in <Action> "id" property
description = "demo action description"
shortcut = { key = ':', kind = 'main'} # key string only accepts lower and upper-case letters, numbers and symbols. kind can be "main" or "alternative"

[[entrypoint]]
id = 'command-a' 
name = 'Command A'
path = 'src/command-a.ts' # path to file, the whole file is a js script
type = 'command'
description = 'Some entrypoint description' # required

[[entrypoint]]
id = 'command-generator'
name = 'Command generator'
path = 'src/command-generator.ts'
type = 'command-generator'
description = 'Some entrypoint description' # required

[[entrypoint]]
id = 'inline-view'
name = 'Inline view'
path = 'src/inline-view.tsx'
type = 'inline-view'
description = 'Some entrypoint description' # required

[permissions] # For allowed values see: https://docs.deno.com/runtime/manual/basics/permissions
environment = ["ENV_VAR_NAME"] # array of strings, if specified requires supported_system to be specified as well
high_resolution_time = false # boolean
network = ["github.com"] # array of strings
ffi = ["path/to/dynamic/lib"] # array of strings, if specified requires supported_system to be specified as well
fs_read_access = ["path/to/something"] # array of strings, if specified requires supported_system to be specified as well
fs_write_access = ["path/to/something"] # array of strings, if specified requires supported_system to be specified as well
run_subprocess = ["program"] # array of strings, if specified requires supported_system to be specified as well
system = ["apiName"] # array of strings, if specified requires supported_system to be specified as well

[[supported_system]]
os = 'linux' # 'linux', 'windows' or 'macos'

Application config

Located at $XDG_CONFIG_HOME/gauntlet/config.toml for Linux. Not used at the moment.

CLI

Application

The Application has a simple command line interface

Dev Tools

@project-gauntlet/tools contains separate CLI tool for plugin development purposes. It has following commands:

Plugin template has nice npm run wrappers for them.

Theming

See THEME.md

Architecture

The Application consists of three parts: server, frontend and settings. Server is an application that exposes gRPC server. All plugins run on server. Each plugin in its own sandboxed Deno Worker. In plugin manifest it is possible to configure permissions which will allow plugin to have access to filesystem, network, environment variables, ffi or subprocess execution. Server saves plugins themselves and state of plugins into SQLite database.

Frontend is GUI application that uses iced-rs as a GUI framework. It is also exposes gRPC server that is used by server to render views

Plugins can create UI using React. Server implements custom React Reconciler (similar to React Native) which renders GUI components to frontend. Server listens on signals from frontend, so when user opens view defined by plugin, frontend sends an open-view request. Server then receives it, runs React render and React Reconciler makes requests to the frontend containing information what actually should be rendered. When a user interacts with the UI by clicking button or entering text into form, frontend sends events to server to see whether any re-renders are needed.

Settings is a GUI application runs in separate process that communicates with server via gRPC using a simple request-response approach.

Simplified gRPC communication:

Components:

Each component runs in a separate thread. Main thread is the thread that renders GUI. Each component has its own tokio runtime instance.

Plugins (or rather its compiled state: manifest, js code and assets) are distributed via Git repository in gauntlet/release branch (similar to GitHub Pages). Which means there is no one central place required for plugin distribution. And to install plugin all you need is Git repository url.

Application defines set of React components to use for plugins. Creating and validating components involves some boilerplate. Component model was created for help manage is. It is essentially a json file which defines what components exist, what properties and event handler they have. This file is then used to generate TypeScript typings for @project-gauntlet/api and Rust validation code for server and frontend.

Application packaging for Linux

This section contains a list of things that could be useful for someone who wants to package application for Linux distribution. If something is missing, please create an issue.

Application is already packaged for Arch Linux so you can use it as example, see Arch Linux

Relevant CLI commands:

.desktop sample file can be found here

systemd service sample file can be found here

Directories used

Application and Dev Tools use temporary directories:

X11 API is used to add global shortcut

Client and Setting applications have GUI and therefore use all the usual graphics-related stuff from X11. Wayland support requires LayerShell protocol zwlr_layer_shell_v1.

Building Gauntlet

You will need:

To build dev run:

git submodule update --init
npm ci
npm run build
npm run build-dev-plugin
cargo build

In dev (without "release" feature) application will use only directories inside project directory to store state or cache.

To build release run:

git submodule update --init
npm ci
npm run build
cargo build --release --features release

But the new version release needs to be done via GitHub Actions

Versioning

Application

Application uses simple incremental integers starting from 1. It doesn't follow the SemVer versioning. Given application's reliance on plugins, once it is stable, introducing breaking changes will be done carefully (if at all) and will be given a reasonable grace period to migrate. SemVer is about a hard cutoff between major versions with breaking changes, which doesn't fit this kind of application. Before application is declared stable, breaking changes could be done without a grace period.

Tools

@project-gauntlet/tools uses SemVer.

Plugins

Plugins only have the latest published "version".