feat(desktop): configure Yazi portal and floating borders
This commit is contained in:
parent
c06c55db13
commit
dd72ba36b9
11 changed files with 254 additions and 9 deletions
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
8
portal/.config/xdg-desktop-portal-termfilechooser/config
Normal file
8
portal/.config/xdg-desktop-portal-termfilechooser/config
Normal 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
|
||||
66
portal/.config/xdg-desktop-portal-termfilechooser/yazi-wrapper.sh
Executable file
66
portal/.config/xdg-desktop-portal-termfilechooser/yazi-wrapper.sh
Executable 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
|
||||
8
portal/.config/xdg-desktop-portal/portals.conf
Normal file
8
portal/.config/xdg-desktop-portal/portals.conf
Normal 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
84
portal/README.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue