feat(desktop): configure Yazi portal and floating borders

This commit is contained in:
fy59 2026-10-08 20:57:24 +02:00
parent c06c55db13
commit dd72ba36b9
11 changed files with 254 additions and 9 deletions

View file

@ -74,7 +74,7 @@ ArchASP
└── Dotfiles_ArchAP utilisateur · $HOME · Stow · systemd --user
```
Les 31 packages Stow couvrent le desktop, le shell, le développement, les
Les 32 packages Stow couvrent le desktop, le shell, le développement, les
services utilisateur et l'apparence. `.assets/` contient les inventaires de
reconstruction ; `docs/` contient la documentation et n'est jamais un package
Stow. Voir le [contrat d'architecture](docs/architecture.md).
@ -90,7 +90,7 @@ git clone --recurse-submodules \
cd "$HOME/.dotfiles"
packages=(atuin bat bin btop cliphist eza fastfetch fzf gtk-3.0 gtklock \
hyprwhspr icons kitty mako nvim opencode qt5ct qt6ct \
hyprwhspr icons kitty mako nvim opencode portal qt5ct qt6ct \
quickshell starship sway swaylock systemd themes tmux uwsm wallpapers wofi yazi \
ytmusic-tui zsh)
@ -110,6 +110,7 @@ inventaires et validations sont décrits dans le
| [Architecture](docs/architecture.md) | Frontières ArchASP, Stow et données locales |
| [Installation](docs/installation.md) | Clone neuf, sous-modules, Stow et validations |
| [Desktop](docs/desktop.md) | Chaîne greetd/UWSM/Sway et composants Wayland |
| [Sélecteur de fichiers](portal/README.md) | Portail FileChooser, Yazi, Kitty et raccourcis |
| [Shell](docs/shell.md) | Zsh, outils interactifs, plugins et scripts |
| [systemd](docs/systemd.md) | Services système et unités utilisateur |
| [Packages](docs/packages.md) | Inventaires Pacman, AUR, services et outils externes |

View file

@ -24,6 +24,34 @@ ombres validées, flou global désactivé, flou réservé à la
couche QuickShell. Le script d'opacité inactive écoute l'IPC Sway au lancement
de la session.
## Décoration des fenêtres flottantes
Les fenêtres flottantes classiques utilisent une bordure serveur `pixel 2`,
sans barre de titre Sway. `sway/.config/sway/style` fixe les valeurs par défaut
pour les nouvelles fenêtres et les couleurs `client.*` : Lavender `#b4befe`
avec le focus, Surface2 `#6c7086` sans focus. Ces couleurs sont communes aux
fenêtres flottantes et en mosaïque ; SwayFX 0.6 ne fournit pas ici une palette
distincte selon le mode. La règle `[floating]` de `sway/.config/sway/rules`
couvre les fenêtres créées flottantes, y compris les dialogues. La règle
`labfy-yazi-filechooser` conserve son centrage et force cette même bordure
pour le sélecteur Yazi. L'abonné IPC existant
`scripts/inactive-windows-transparency.py` écoute aussi `window::floating`.
Il cible l'ID du conteneur devenu flottant, attend 50 ms que son éventuel mode
`csd` se stabilise, puis applique `border pixel 2` si nécessaire. La bordure
antérieure est rétablie lorsqu'il revient en mosaïque. Cela couvre aussi bien
le raccourci `$mod+Ctrl+space` qu'une commande IPC directe `floating enable`,
sans nouveau processus et sans modifier les surfaces layer-shell.
`corner_radius 4` dans `sway/.config/sway/swayfx` est un réglage global SwayFX :
les fenêtres en mosaïque avaient déjà ce rayon, qui n'est pas modifié par cette
politique. Les valeurs par défaut des fenêtres en mosaïque restent inchangées.
SwayFX 0.6 ne propose pas de règle native réévaluée à chaque transition ;
`for_window` ne traite que l'apparition de la fenêtre. L'abonné IPC mémorise
uniquement les bordures remplacées jusqu'au retour en mosaïque ou à la fermeture
du conteneur. Les surfaces layer-shell de QuickShell, ses notifications et les
menus système ne sont pas des fenêtres classiques ciblées par `[floating]` ;
leurs règles existantes restent en vigueur.
## Services et outils
`labfy-quickshell-updates.timer` déclenche la vérification Arch/AUR sans

View file

@ -63,14 +63,14 @@ installer. Comparer leur contenu à la machine cible et lire
[`packages.md`](packages.md) ainsi que
[`external-tools.md`](../.assets/external-tools.md) avant toute installation.
## 5. Définir les 31 packages Stow
## 5. Définir les 32 packages Stow
Les commandes suivantes sont destinées à Bash :
```bash
packages=(
atuin bat bin btop cliphist eza fastfetch fzf gtk-3.0 gtklock
hyprwhspr icons kitty mako nvim opencode qt5ct qt6ct
hyprwhspr icons kitty mako nvim opencode portal qt5ct qt6ct
quickshell starship sway swaylock systemd themes tmux uwsm wallpapers wofi yazi
ytmusic-tui zsh
)
@ -79,6 +79,9 @@ printf '%s\n' "${packages[@]}"
```
`docs/` et `.assets/` ne doivent pas être ajoutés à ce tableau. Le package
`portal` demande l'installation du backend décrite dans
[`portal/README.md`](../portal/README.md).
Le package
`quickshell` déploie `~/.config/quickshell/labfy-sway/` ; une Nerd Font
(`ttf-jetbrains-mono-nerd` sur cette machine) fournit les icônes.
SwayFX 0.6 est un paquet foreign/local à reconstruire depuis sa source

View file

@ -50,7 +50,7 @@ exigent une décision spécifique ; aucun helper AUR n'est supposé magique.
## 6. Déployer Stow
Suivre le [guide d'installation](installation.md) : définir explicitement les
31 packages, exécuter le dry-run, résoudre les conflits, puis seulement lancer
32 packages, exécuter le dry-run, résoudre les conflits, puis seulement lancer
le déploiement réel. `docs/` et `.assets/` ne sont pas déployés.
## 7. Restaurer les services

View file

@ -0,0 +1,8 @@
[filechooser]
cmd=yazi-wrapper.sh
default_dir=$HOME/Downloads
# WHY: le fichier provisoire permet à Yazi de choisir un nouveau nom de sauvegarde.
# CONTRACT: le backend supprime ce fichier si la sélection est annulée.
create_help_file=1
open_mode = suggested
save_mode = last

View file

@ -0,0 +1,66 @@
#!/bin/sh
# Adaptation du wrapper Yazi de hunkyburrito/xdg-desktop-portal-termfilechooser 1.4.3.
# CONTRACT: arguments multiple, directory, save, path, out, debug dans cet ordre.
set -eu
if [ "${1:-}" != '--session' ]; then
# WHY: kitty reçoit des arguments distincts ; aucun chemin n'est réinterprété par un shell.
# INVARIANT: l'app_id est réservé à ce sélecteur, jamais aux terminaux ordinaires.
# CONTRACT: dimension initiale 1000x650, bornée à la sortie SwayFX active.
width=1000
height=650
if command -v swaymsg >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
geometry=$(swaymsg -t get_outputs -r 2>/dev/null | jq -r '[.[] | select(.focused == true and .active == true)] | first | if . then "\(.rect.width) \(.rect.height)" else empty end' 2>/dev/null) || geometry=''
if [ -n "$geometry" ]; then
output_width=${geometry%% *}
output_height=${geometry#* }
case "$output_width:$output_height" in
*[!0-9:]* ) ;;
* )
[ "$output_width" -le 1040 ] && width=$(( output_width > 80 ? output_width - 40 : 40 ))
[ "$output_height" -le 690 ] && height=$(( output_height > 80 ? output_height - 40 : 40 ))
;;
esac
fi
fi
exec kitty --class labfy-yazi-filechooser --title 'Yazi · Sélecteur de fichiers' \
--override remember_window_size=no \
--override "initial_window_width=${width}" \
--override "initial_window_height=${height}" \
-- "$0" --session "$@"
fi
shift
multiple=${1:?}
directory=${2:?}
save=${3:?}
path=${4:?}
out=${5:?}
debug=${6:-0}
case "$multiple:$directory:$save:$debug" in
*[!01:]* ) exit 2 ;;
esac
# CONTRACT: Yazi utilise sa configuration XDG normale ; chooser-file contient
# les chemins sélectionnés, tandis que cwd-file sert au choix du dossier courant.
if [ "$directory" = 1 ]; then
yazi --chooser-file="$out" --cwd-file="$out.1" "$path" || :
if [ ! -s "$out" ] && [ -s "$out.1" ]; then
cat "$out.1" > "$out"
fi
rm -f -- "$out.1"
else
yazi --chooser-file="$out" "$path" || :
fi
if [ "$save" = 1 ] && [ -s "$out" ]; then
# WHY: le backend peut proposer un fichier existant. La confirmation garde
# l'écrasement potentiel explicite avant de publier le chemin à l'appelant.
printf '\nDestination sélectionnée : %s\nConfirmer ? L’application peut écraser son contenu. [o/N] ' "$(head -n 1 -- "$out")"
IFS= read -r reply </dev/tty || reply=''
case "$reply" in
o|O|oui|Oui|OUI) ;;
*) : > "$out" ;;
esac
fi

View file

@ -0,0 +1,8 @@
[preferred]
# WHY: la configuration utilisateur est choisie avant sway-portals.conf ;
# CONTRACT: conserver le routage Sway pour toutes les autres interfaces.
default=gtk
org.freedesktop.impl.portal.FileChooser=termfilechooser
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr
org.freedesktop.impl.portal.Secret=gnome-keyring

84
portal/README.md Normal file
View file

@ -0,0 +1,84 @@
# Yazi comme FileChooser du portail
Package GNU Stow : `stow -d ~/.dotfiles -t ~ portal`. Les fichiers actifs dans
`~/.config/xdg-desktop-portal/` et
`~/.config/xdg-desktop-portal-termfilechooser/` sont des liens Stow. La règle
SwayFX est dans le package `sway`.
## Dépendances
Arch Linux : `xdg-desktop-portal`, `xdg-desktop-portal-gtk`,
`xdg-desktop-portal-wlr`, `kitty`, `yazi`, `jq`, `swayfx`, `libinih`,
`libsystemd`, `meson`, `ninja` et `scdoc` pour une compilation complète. Le
backend retenu est
[`hunkyburrito/xdg-desktop-portal-termfilechooser`](https://github.com/hunkyburrito/xdg-desktop-portal-termfilechooser),
version 1.4.3, commit `bcb2387949e4eb38a35390ebf1693f92869e619d` dans
l'installation testée. Le paquet AUR
`xdg-desktop-portal-termfilechooser-hunkyburrito-git` existe aussi.
Cette machine utilise une installation Meson utilisateur sous `~/.local`, sans
changer les backends système. Les fichiers nécessaires sont
`~/.local/libexec/xdg-desktop-portal-termfilechooser`,
`~/.local/share/dbus-1/services/org.freedesktop.impl.portal.desktop.termfilechooser.service`
et `~/.local/share/xdg-desktop-portal/portals/termfilechooser.portal`.
La procédure de reproduction, conservée dans ce dépôt indépendamment du cache
de construction, est la suivante (sans l'exécuter pendant une simple publication) :
```sh
git clone https://github.com/hunkyburrito/xdg-desktop-portal-termfilechooser.git
cd xdg-desktop-portal-termfilechooser
git checkout --detach bcb2387949e4eb38a35390ebf1693f92869e619d
meson setup build --prefix="$HOME/.local"
meson compile -C build
meson install -C build
```
Contrôler ensuite les trois chemins ci-dessus et la configuration Stow de ce
dossier. Le dépôt source et le répertoire `build` sont temporaires et ne font
pas partie des dotfiles. Cette procédure installe uniquement dans le préfixe
utilisateur ; elle ne remplace aucun paquet système.
Après installation ou mise à jour du backend, recharger seulement
`xdg-desktop-portal.service` après avoir vérifié qu'aucune session de partage
d'écran ou requête portal n'est en cours.
## Routage et fenêtre
`FileChooser` utilise `termfilechooser`, `ScreenCast` et `Screenshot` utilisent
`wlr`, `Secret` utilise `gnome-keyring`, et les autres interfaces disponibles
gardent `gtk`. `XDG_CURRENT_DESKTOP=sway:wlroots:swayfx` sélectionne la
configuration utilisateur `portals.conf` avant les fichiers système propres à
Sway. Le wrapper lance Kitty avec `--class labfy-yazi-filechooser` ; SwayFX
flotte et centre uniquement cet `app_id`. Kitty calcule au lancement une taille
de 1000 × 650, bornée à la sortie SwayFX active avec une marge de 40 pixels.
La fenêtre reste redimensionnable et sur le workspace courant. Le thème Kitty
et le thème Yazi Catppuccin Mocha sont ceux de la configuration habituelle.
Firefox 157 utilise le portail FileChooser en mode automatique quand il est
disponible. Aucun `GTK_USE_PORTAL` global ni préférence de profil n'est
nécessaire dans cette installation. Pour diagnostiquer un profil différent,
vérifier `widget.use-xdg-desktop-portal.file-picker` dans `about:config` : `1`
force le portail, `0` le désactive, `2` suit le mode automatique.
## Utilisation
Dans le sélecteur, `o` ou `Entrée` valide le ou les fichiers survolés ou
sélectionnés. `Espace` sélectionne chaque fichier en mode multiple. `q` annule
un choix de fichier ; `Q` annule aussi et ne transmet pas le dossier courant.
Pour un dossier, entrer dedans puis appuyer sur `q` ; `Q` annule. `M` ouvre le
menu des supports, `g` puis `Espace` permet de saisir un chemin. Ouvrir un
fichier via Yazi sans mode chooser n'est pas la même opération : ici, le
wrapper utilise `--chooser-file` pour remettre les chemins à l'application.
Pour « Enregistrer sous », le backend crée un fichier provisoire annoté. Le
déplacer avec `x`, naviguer vers la destination, le déposer avec `p`, puis le
renommer avec `r` si nécessaire. Valider le fichier avec `o` ou `Entrée`, puis
répondre `o` au prompt de confirmation du wrapper. Le prompt avertit que
l'application peut écraser une destination existante ; répondre autrement
annule. `Q` dans Yazi annule également et le backend supprime le provisoire
resté à son emplacement d'origine. Si le provisoire a été déplacé avant une
annulation, supprimer manuellement ce fichier déplacé. Les essais doivent
utiliser un dossier temporaire et éviter tout écrasement de données réelles.
Les applications GTK/Qt qui utilisent leurs dialogues natifs plutôt que le
portail conservent leur comportement. Ce réglage ne change aucune association
MIME et ne remplace pas les portails d'autres interfaces.

View file

@ -23,6 +23,9 @@ QuickShell, lancé par `quickshell-labfy-sway.service`.
`autostart` lance une fois le listener IPC
`scripts/inactive-windows-transparency.py`, `limusic-app` et `autotiling`.
Ce listener réutilise les événements `window::floating` pour appliquer la
bordure serveur de 2 px lors des bascules IPC, puis restaure la bordure
antérieure au retour en mosaïque. Aucun second abonné permanent n'est lancé.
Cliphist, swayidle, wlsunset et QuickShell sont gérés par systemd user ;
aucune commande `qs` n'est lancée par Sway. Le fichier `swayfx` fixe les
animations à 0, les coins à 4, les ombres validées et le flou uniquement sur

View file

@ -9,6 +9,11 @@
for_window [app_id="blueman-manager"] floating enable, resize set 40ppt 40ppt, move position center
for_window [app_id="Proton Pass"] floating enable, resize set 40ppt 40ppt, move position right top
# WHY: le sélecteur portal doit flotter et rester centré.
# CONTRACT: seul l'app_id dédié au wrapper Yazi reçoit ce placement ;
# la bordure serveur garantit la même politique que les autres flottantes.
for_window [app_id="labfy-yazi-filechooser"] floating enable, border pixel 2, move position center
#=================================================
# Lardon
#=================================================
@ -17,3 +22,9 @@ for_window [app_id="lardon-freecad-log"] floating enable
for_window [app_id="lardon-freecad-log"] resize set width 850 px height 360 px
for_window [app_id="lardon-freecad-log"] move position center
for_window [app_id="lardon-freecad-log"] border pixel 2
# WHY: les fenêtres CSD peuvent ignorer default_floating_border.
# CONTRACT: toute fenêtre créée flottante utilise une bordure serveur de 2 px,
# sans barre de titre ; client.* fournit Lavender au focus et Mocha hors focus.
# Les transitions ultérieures sont traitées par l'abonné IPC d'opacité existant.
for_window [floating] border pixel 2

View file

@ -2,14 +2,15 @@
# This script requires i3ipc-python package (install it from a system package manager
# or pip).
# It makes inactive windows transparent. Use `transparency_val` variable to control
# transparency strength in range of 0…1 or use the command line argument -o.
# It makes inactive windows transparent and corrects borders on floating events.
# Use the command line argument -o to control inactive opacity in range 0…1.
import argparse
import json
import os
import signal
import sys
import time
from functools import partial
from pathlib import Path
@ -17,6 +18,10 @@ import i3ipc
STATE = Path(os.environ.get('XDG_STATE_HOME', Path.home() / '.local/state')) / 'labfy-appearance/effective.json'
# INVARIANT: seules les bordures de mosaïque remplacées en flottant sont
# mémorisées, puis oubliées au retour en mosaïque ou à la fermeture.
previous_tiled_borders = {}
def inactive_opacity():
# CONTRACT: lecture uniquement sur événement IPC ou SIGUSR1 de la
@ -38,10 +43,38 @@ def apply_all(ipc):
def on_window(args, ipc, event):
global focused_set
# To get the workspace for a container, we need to have received its
# parents, so fetch the whole tree
# WHY: Sway émet window::floating avant de finir le passage à csd de
# certaines fenêtres. Attendre uniquement cette transition évite qu'une
# bordure corrigée aussitôt soit écrasée dans la même opération.
if event.change == 'floating' and event.container.floating in ('user_on', 'auto_on'):
time.sleep(0.05)
# L'arbre courant remplace l'instantané trop précoce de l'événement.
tree = ipc.get_tree()
# WHY: for_window ne se rejoue pas sur une transition IPC vers floating.
# CONTRACT: la bordure serveur ne s'applique qu'au conteneur devenu
# flottant ; sa décoration précédente revient lors du retour en mosaïque.
# INVARIANT: une bordure déjà correcte ne déclenche aucune commande, donc
# aucune réaction répétée à un éventuel événement causé par border.
if event.change == 'floating':
window = tree.find_by_id(event.container.id)
if window is not None and window.floating in ('user_on', 'auto_on'):
if window.border != 'pixel' or window.current_border_width != 2:
# L'instantané de l'événement garde la bordure d'entrée,
# même si l'arbre courant montre déjà csd en flottant.
previous_tiled_borders[window.id] = (
event.container.border, event.container.current_border_width)
window.command('border pixel 2')
elif event.container.id in previous_tiled_borders:
border, width = previous_tiled_borders.pop(event.container.id)
if border in ('normal', 'pixel') and isinstance(width, int) and 0 <= width <= 1000:
event.container.command(f'border {border} {width}')
elif border in ('none', 'csd'):
event.container.command(f'border {border}')
elif event.change == 'close':
previous_tiled_borders.pop(event.container.id, None)
args.opacity = inactive_opacity()
focused = tree.find_focused()
if focused is None: