vsk-contextual-menu
tauri-plugin-vsk-contextual-menu#
El menú del clic derecho de VasakOS. Un solo menú para todo el sistema: las aplicaciones describen qué opciones ofrecen y el plugin decide cómo se ven, cómo responden al teclado y cómo se cierran. El escritorio, el gestor de archivos, la galería y lo que venga se ven igual.
- Crate:
tauri-plugin-vsk-contextual-menu - Paquete JS:
@vasakgroup/plugin-vsk-contextual-menu - Plataforma: Linux (Wayland y X11) con Tauri 2
Instalación#
| |
| |
| |
init() deja la ventana del menú en la ruta por omisión, #/vsk-context-menu.
Sólo hace falta cambiarla si la aplicación no puede registrar esa ruta:
| |
La ruta es la parte que va después de index.html, con su almohadilla.
Permisos#
vsk-contextual-menu:default habilita los cinco comandos (open_menu_window,
close_menu_window, take_pending_menu, resolve_menu, show_menu_window).
No hay más permisos que los allow- y deny- por comando.
La ventana del menú lleva la etiqueta vsk_context_menu y tiene que estar
incluida en la capability: si no, se queda sin permisos y el menú se dibuja
sin los colores del sistema y sin traducciones. La misma lista de permisos
cubre los dos lados, porque los dos invocan al plugin:
| |
core:event:default es lo que permite escuchar config-changed, el aviso con
el que el menú se entera de que cambió el esquema de colores.
El modo dentro del DOM no invoca ningún comando del plugin, pero sí lee la
paleta del sistema: necesita los permisos del complemento config-manager
(read_config y get_scheme_by_id). Sin ellos el menú se sigue dibujando, con
los colores del sistema o con los de fábrica.
API de JavaScript#
showContextMenu(items, donde, opciones?): Promise<MenuChoice | null>#
Abre el menú y devuelve lo que se eligió, o null si se cerró sin elegir nada.
items es el árbol de renglones y donde es el evento del clic derecho (le
corta el menú que dibuja el motor del navegador) o un { x, y } propio. Si ya
había un menú abierto en esa ventana, lo cierra antes de abrir el nuevo.
opciones:
| Campo | Qué hace |
|---|---|
minWidth | Ancho mínimo, en píxeles. Por omisión, 200. |
maxWidth | Ancho máximo, antes de cortar el texto con puntos suspensivos. Por omisión, 420. |
window | Dibujar el menú en una ventana propia en vez de dentro de la página. |
closeContextMenu(): void#
Cierra el menú abierto, si hay alguno. Quien lo abrió recibe null, igual que
si se hubiera descartado con Escape.
disableNativeContextMenu(): void#
Apaga el menú del clic derecho que dibuja el motor del navegador en toda la ventana. Evita «Recargar» e «Inspeccionar elemento» sobre una aplicación que no es una página web; los menús propios siguen abriéndose igual.
setupContextMenu(opciones?): void#
Todo lo anterior junto, para el arranque de la aplicación. Acepta
iconResolver (cómo convertir un nombre de icono del sistema en una imagen) y
disableNative (por omisión, apaga el menú de WebKit). Además pide la paleta
del sistema por adelantado, para que el primer clic derecho no espere.
setIconResolver(resolutor): void#
Enseña al menú a convertir nombres de iconos en imágenes. El resolutor recibe
el nombre y devuelve una imagen, como getIconSource de
vicons
; pasar null lo quita. El plugin
no depende de vicons a propósito: así se puede usar en cualquier aplicación.
openContextMenuWindow(items, punto, opciones?): Promise<MenuChoice | null>#
Abre el menú en una ventana propia y espera lo que se elija. Es lo que llama
showContextMenu con window: true; casi nunca hace falta llamarla a mano.
Suma a mano la posición de la ventana para pasar el clic a coordenadas de
pantalla, que son las que necesita Rust.
mountContextMenuWindow(): Promise<void>#
Dibuja el menú en la ventana que el plugin abrió. Es lo único que tiene que hacer la ruta que la aplicación registró para el menú.
useContextMenu()#
El envoltorio para Vue. Devuelve { show, close } y cierra el menú cuando el
componente se desmonta, para que un menú abierto no quede huérfano.
useContextMenuWindow(): void#
Para el componente que la aplicación pone en la ruta del menú; llama a
mountContextMenuWindow al montar.
El árbol de renglones#
MenuEntry es la misma unión de tipos que entiende el lado de Rust:
| Tipo | Campos |
|---|---|
item | id, label, icon?, accelerator?, disabled?, danger?. El type es opcional. |
checkbox | id, label, checked?, disabled?. Al elegirla, MenuChoice trae el estado nuevo. |
submenu | label, icon?, items. Sólo en modo dentro del DOM. |
separator | Ninguno. Separa grupos de opciones. |
label | label. Título de grupo, que no se puede elegir. |
Tres cosas que conviene tener presentes:
labelllega ya traducido: el menú no traduce nada por su cuenta.acceleratoresCtrl+Ctal como se lee; el menú lo muestra, no lo escucha. El atajo lo sigue atendiendo la aplicación.dangerpinta la opción con el color de error, para lo que destruye algo.
Comandos Rust#
open_menu_window(request)— Abre el menú en una ventana propia, pegada al clic.requesttraeitems,x,y(coordenadas físicas de la pantalla) yminWidth/maxWidthopcionales.take_pending_menu()— Los ítems que tiene que dibujar la ventana del menú. No los saca del estado: el menú sigue abierto hasta que alguien lo resuelva.show_menu_window()— Muestra la ventana, ya con los ítems dibujados adentro. Sólo hace algo si la llama la propia ventana del menú.resolve_menu(id, checked)— Se eligió una opción.checkedsólo viene cuando fue una casilla.close_menu_window()— Se cerró el menú sin elegir nada.
Los dos últimos cierran la ventana y le avisan a quien pidió el menú. Si la
ventana no se pudo abrir, open_menu_window contesta igual: sin eso, quien
pidió el menú esperaría para siempre.
Eventos#
| Evento | Cuándo se emite | Carga |
|---|---|---|
vsk-context-menu-result | Se eligió una opción, se cerró el menú sin elegir, o la ventana no se pudo abrir | { id: string | null, checked: boolean | null } |
Va sólo a la ventana que pidió el menú, con null en id cuando se descartó.
showContextMenu y openContextMenuWindow ya lo escuchan por su cuenta: la
aplicación recibe la respuesta en el valor de retorno, no en un escucha propio.
Ejemplo#
| |
En modo ventana hacen falta las dos mitades: la aplicación abre el menú con
window: true y registra una ruta que lo dibuja.
| |
| |
Detalles de implementación#
- Los dos modos dibujan con el mismo componente: el menú se ve y se maneja igual adentro de la página que en su propia ventana.
- Dentro del DOM el único límite es la ventana de la aplicación. En modo ventana el límite es la pantalla: sirve para el panel —una franja de treinta píxeles— y para los clics cerca del borde de abajo, donde el menú quedaría recortado.
- La ventana del menú es transparente, sin decoración, no redimensionable, siempre encima y fuera de la barra de tareas. Nace del tamaño exacto de sus renglones (32 px por opción, 9 px por separador, 26 px por título, más el aire y el borde) y se corre hacia adentro cuando el clic cae cerca del borde de la pantalla. Se crea cada vez y se cierra al elegir o al perder el foco: mantenerla viva costaría un webview en memoria por algo que se abre unos segundos. Deja 8 px de borde transparente para la sombra, medidos igual en Rust y en el JavaScript que dibuja.
- Los submenús no entran en modo ventana —la ventana mide lo que mide el menú y el submenú quedaría afuera—, así que ese modo los rechaza. En modo dentro del DOM son parte del mismo dibujo.
- Los colores y el redondeo salen del complemento config-manager: el esquema
elegido en el sistema y el radio de las esquinas. Se leen una sola vez y se
guardan; el evento
config-changedlos vuelve a pedir. Si config-manager no está o no contesta, el menú cae a las variables del sistema (--use-*) y de ahí a los colores de fábrica: no se queda sin dibujar por no saber de qué color pintarse. De esa lectura se ocupa el propio menú; no forma parte de lo que el paquete exporta. - El
typede una opción común es opcional: antes de cruzar a Rust, el plugin completatype: "item"en cada renglón —también adentro de los submenús—, porque del lado de Rust el árbol es un enum etiquetado por ese campo y sin él la deserialización falla. - En modo ventana el menú no se cierra cuando la página pierde el foco o
cambia de tamaño: al mapearse la ventana se disparan
bluryresizeque no significan que alguien se haya ido a otra cosa, y el menú se habría cerrado a los cien milisegundos de abrirse. El foco lo mira la ventana de verdad, y sólo cierra si lo perdió después de haberlo tenido. - El teclado es el de cualquier menú del escritorio: flechas para moverse, Inicio y Fin para los extremos, Enter y espacio para elegir, Escape para volver del submenú al padre y para cerrar el menú, y escribir salta a la opción que empieza así. Los submenús se abren al pasar por encima.
- El estilo va como CSS plano y no como clases de Tailwind: el plugin se publica compilado y el escaneo de la aplicación no ve las clases que vienen adentro de un paquete, así que llegarían sin estilo.
- Lo que el plugin no puede hacer es cambiarle el menú a las aplicaciones ajenas: en Wayland cada cliente dibuja el suyo en su propia superficie y no hay protocolo para reemplazarlo. Para esas, la vía es el tema de GTK y de Qt.