mirror of
https://github.com/ByteAtATime/raycast-linux.git
synced 2025-08-31 19:27:24 +00:00
feat: add hud API
This commit adds the HUD API, allowing extensions to display temporary, non-interactive messages on the screen. The implementation adds a `show_hud` Tauri command that manages a dedicated, borderless Svelte window. This window is dynamically resized to fit its content and is automatically hidden after two seconds.
This commit is contained in:
parent
1ae8a571cb
commit
28d1605bba
8 changed files with 125 additions and 2 deletions
|
@ -78,6 +78,15 @@ const HideToastPayloadSchema = z.object({
|
||||||
id: z.number()
|
id: z.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ShowHudPayloadSchema = z.object({
|
||||||
|
title: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const ShowHudMessageSchema = z.object({
|
||||||
|
type: z.literal('SHOW_HUD'),
|
||||||
|
payload: ShowHudPayloadSchema
|
||||||
|
});
|
||||||
|
|
||||||
export const CommandSchema = z.discriminatedUnion('type', [
|
export const CommandSchema = z.discriminatedUnion('type', [
|
||||||
z.object({ type: z.literal('CREATE_INSTANCE'), payload: CreateInstancePayloadSchema }),
|
z.object({ type: z.literal('CREATE_INSTANCE'), payload: CreateInstancePayloadSchema }),
|
||||||
z.object({ type: z.literal('CREATE_TEXT_INSTANCE'), payload: CreateTextInstancePayloadSchema }),
|
z.object({ type: z.literal('CREATE_TEXT_INSTANCE'), payload: CreateTextInstancePayloadSchema }),
|
||||||
|
@ -292,6 +301,7 @@ const OauthRemoveTokensMessageSchema = z.object({
|
||||||
export const SidecarMessageWithPluginsSchema = z.union([
|
export const SidecarMessageWithPluginsSchema = z.union([
|
||||||
BatchUpdateSchema,
|
BatchUpdateSchema,
|
||||||
CommandSchema,
|
CommandSchema,
|
||||||
|
ShowHudMessageSchema,
|
||||||
LogMessageSchema,
|
LogMessageSchema,
|
||||||
PluginListSchema,
|
PluginListSchema,
|
||||||
PreferenceValuesSchema,
|
PreferenceValuesSchema,
|
||||||
|
|
8
sidecar/src/api/hud.ts
Normal file
8
sidecar/src/api/hud.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { writeOutput } from '../io';
|
||||||
|
|
||||||
|
export async function showHUD(title: string): Promise<void> {
|
||||||
|
writeOutput({
|
||||||
|
type: 'SHOW_HUD',
|
||||||
|
payload: { title }
|
||||||
|
});
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import { Detail } from './components/detail';
|
||||||
import { environment, getSelectedFinderItems, getSelectedText, open } from './environment';
|
import { environment, getSelectedFinderItems, getSelectedText, open } from './environment';
|
||||||
import { preferencesStore } from '../preferences';
|
import { preferencesStore } from '../preferences';
|
||||||
import { showToast } from './toast';
|
import { showToast } from './toast';
|
||||||
|
import { showHUD } from './hud';
|
||||||
import { BrowserExtensionAPI } from './browserExtension';
|
import { BrowserExtensionAPI } from './browserExtension';
|
||||||
import { Clipboard } from './clipboard';
|
import { Clipboard } from './clipboard';
|
||||||
import * as OAuth from './oauth';
|
import * as OAuth from './oauth';
|
||||||
|
@ -70,6 +71,7 @@ export const getRaycastApi = () => {
|
||||||
getSelectedText,
|
getSelectedText,
|
||||||
open,
|
open,
|
||||||
showToast,
|
showToast,
|
||||||
|
showHUD,
|
||||||
useNavigation,
|
useNavigation,
|
||||||
usePersistentState: <T>(
|
usePersistentState: <T>(
|
||||||
key: string,
|
key: string,
|
||||||
|
|
14
src-tauri/capabilities/hud.json
Normal file
14
src-tauri/capabilities/hud.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "hud",
|
||||||
|
"description": "Capability for the HUD window",
|
||||||
|
"windows": [
|
||||||
|
"hud"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:window:allow-set-size",
|
||||||
|
"core:window:allow-center",
|
||||||
|
"core:window:allow-set-min-size",
|
||||||
|
"core:window:allow-set-max-size"
|
||||||
|
]
|
||||||
|
}
|
|
@ -56,6 +56,42 @@ fn get_selected_text() -> String {
|
||||||
get_text()
|
get_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn show_hud(app: tauri::AppHandle, title: String) -> Result<(), String> {
|
||||||
|
let url = format!("/hud?title={}", title);
|
||||||
|
let hud_window = match app.get_webview_window("hud") {
|
||||||
|
Some(window) => window,
|
||||||
|
None => tauri::WebviewWindowBuilder::new(&app, "hud", tauri::WebviewUrl::App(url.into()))
|
||||||
|
.decorations(false)
|
||||||
|
.transparent(true)
|
||||||
|
.always_on_top(true)
|
||||||
|
.skip_taskbar(true)
|
||||||
|
.center()
|
||||||
|
.min_inner_size(300.0, 80.0)
|
||||||
|
.max_inner_size(300.0, 80.0)
|
||||||
|
.inner_size(300.0, 80.0)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_clone = hud_window.clone();
|
||||||
|
window_clone
|
||||||
|
.emit("hud-message", &title)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
window_clone
|
||||||
|
.set_ignore_cursor_events(true)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
window_clone.show().map_err(|e| e.to_string())?;
|
||||||
|
window_clone.set_focus().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
|
let _ = window_clone.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_background_refresh() {
|
fn setup_background_refresh() {
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
thread::sleep(Duration::from_secs(60));
|
thread::sleep(Duration::from_secs(60));
|
||||||
|
@ -107,8 +143,8 @@ pub fn run() {
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
let _ = window.emit("deep-link", args[1].to_string());
|
let _ = window.emit("deep-link", args[1].to_string());
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
|
window.set_focus().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +166,7 @@ pub fn run() {
|
||||||
get_installed_apps,
|
get_installed_apps,
|
||||||
launch_app,
|
launch_app,
|
||||||
get_selected_text,
|
get_selected_text,
|
||||||
|
show_hud,
|
||||||
filesystem::get_selected_finder_items,
|
filesystem::get_selected_finder_items,
|
||||||
extensions::install_extension,
|
extensions::install_extension,
|
||||||
browser_extension::browser_extension_check_connection,
|
browser_extension::browser_extension_check_connection,
|
||||||
|
|
|
@ -23,6 +23,18 @@
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 80,
|
"height": 80,
|
||||||
"center": true
|
"center": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "hud",
|
||||||
|
"url": "/hud",
|
||||||
|
"visible": false,
|
||||||
|
"decorations": false,
|
||||||
|
"transparent": true,
|
||||||
|
"alwaysOnTop": true,
|
||||||
|
"skipTaskbar": true,
|
||||||
|
"center": true,
|
||||||
|
"width": 300,
|
||||||
|
"height": 80
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
@ -55,4 +67,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -152,6 +152,11 @@ class SidecarService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typedMessage.type === 'SHOW_HUD') {
|
||||||
|
invoke('show_hud', { title: typedMessage.payload.title });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (typedMessage.type === 'open') {
|
if (typedMessage.type === 'open') {
|
||||||
const { target, application } = typedMessage.payload;
|
const { target, application } = typedMessage.payload;
|
||||||
shellOpen(target, application).catch((err) => {
|
shellOpen(target, application).catch((err) => {
|
||||||
|
|
35
src/routes/hud/+page.svelte
Normal file
35
src/routes/hud/+page.svelte
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { getCurrentWindow, LogicalSize } from '@tauri-apps/api/window';
|
||||||
|
|
||||||
|
let hudText = $derived(page.url.searchParams.get('title') ?? '');
|
||||||
|
let hudEl = $state<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const bounds = hudEl?.getBoundingClientRect();
|
||||||
|
if (bounds) {
|
||||||
|
const window = getCurrentWindow();
|
||||||
|
window.setMinSize(new LogicalSize(bounds.width, bounds.height));
|
||||||
|
window.setMaxSize(new LogicalSize(bounds.width, bounds.height));
|
||||||
|
window.setSize(new LogicalSize(bounds.width, bounds.height));
|
||||||
|
window.center();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex h-screen items-center justify-center bg-transparent">
|
||||||
|
{#if hudText}
|
||||||
|
<div
|
||||||
|
class="rounded-full bg-black/70 px-4 py-2 text-sm font-medium text-white"
|
||||||
|
bind:this={hudEl}
|
||||||
|
>
|
||||||
|
{hudText}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(body) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Add table
Add a link
Reference in a new issue