diff --git a/CLAUDE.md b/CLAUDE.md index d66f004..251fb4d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ NixOS dotfiles for Pierre MARTIN (Lundi Matin — Front-Commerce, LMB, NEIA), us - `nixos/configuration.nix` — System-level NixOS config (bootloader, networking, services, system packages). Imports `hardware-configuration.nix` et `framework.nix`. - `home.nix` — Entry point home-manager. Importe `packages.nix` et les modules de `programs/`. Mappe aussi les fichiers statiques depuis `files/`. - `packages.nix` — Tous les packages utilisateur (avec allowlist unfree). -- `programs/` — Modules home-manager par programme : `git.nix`, `zsh.nix`, `i3.nix`, `neia.nix`. +- `programs/` — Modules home-manager par programme : `git.nix`, `zsh.nix`, `i3.nix`, `hyprland.nix`, `ghostty.nix`, `neovim.nix`, `pi.nix`. - `files/` — Fichiers de config statiques copiés dans `$HOME` par home-manager (`.npmrc`, `traefik.toml`, etc.). Home-manager est intégré au module NixOS (pas standalone). Appliquer avec `sudo nixos-rebuild switch`. @@ -46,7 +46,7 @@ Après modification de fichiers `.sh`, lancer `make fmt && make lint` avant d'ap ## Key details - Keyboard layout : bépo (`fr` variant `bepo`) -- Window manager : i3 (Mod4) +- Window manager : i3 (Mod4), session par défaut. Hyprland (Wayland, config Lua dans `files/hypr/hyprland.lua`) en test, mêmes raccourcis bépo - Shell : zsh + oh-my-zsh + starship + mcfly - Terminal : sakura - Git : difftastic (diffs), meld (merges), `push.autoSetupRemote = true` diff --git a/files/hypr/hyprland.lua b/files/hypr/hyprland.lua new file mode 100644 index 0000000..6be0f05 --- /dev/null +++ b/files/hypr/hyprland.lua @@ -0,0 +1,129 @@ +-- Hyprland - https://wiki.hypr.land/Configuring/Start/ +-- Raccourcis repris de programs/i3.nix (bépo), apparence reprise d'Omarchy : +-- https://github.com/basecamp/omarchy/tree/master/default/hypr + +local mod = "SUPER" + +local function bind(keys, dispatcher, options) + if type(dispatcher) == "string" then + dispatcher = hl.dsp.exec_cmd(dispatcher) + end + hl.bind(keys, dispatcher, options or {}) +end + +-- Écrans : détection automatique (remplace autorandr) +hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" }) + +hl.env("XCURSOR_SIZE", "24") +hl.env("GDK_BACKEND", "wayland,x11,*") +hl.env("QT_QPA_PLATFORM", "wayland;xcb") +hl.env("ELECTRON_OZONE_PLATFORM_HINT", "wayland") +hl.env("TERMINAL", "ghostty") + +hl.config({ + input = { + kb_layout = "fr", + kb_variant = "bepo", + follow_mouse = 1, + repeat_rate = 40, + repeat_delay = 250, + numlock_by_default = true, + touchpad = { + clickfinger_behavior = true, + scroll_factor = 0.4, + }, + }, + + general = { + gaps_in = 5, + gaps_out = 10, + border_size = 2, + col = { + active_border = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 }, + inactive_border = "rgba(595959aa)", + }, + resize_on_border = true, + layout = "dwindle", + }, + + decoration = { + rounding = 0, + shadow = { enabled = false }, + blur = { enabled = false }, + }, + + animations = { enabled = true }, + + dwindle = { + preserve_split = true, + force_split = 2, + }, + + misc = { + disable_hyprland_logo = true, + disable_splash_rendering = true, + background_color = "rgb(000000)", + focus_on_activate = true, + }, + + -- Remplace unclutter + cursor = { inactive_timeout = 3 }, +}) + +hl.on("hyprland.start", function() + hl.exec_cmd("waybar") + hl.exec_cmd("swaybg --mode fill --image /home/pierre/Documents/Graphisme/fc-bg-light-black.png") + hl.exec_cmd("copyq") +end) + +-- Applications +bind(mod .. " + Return", "~/.local/bin/workspace-run ghostty --working-directory=") +bind(mod .. " + SHIFT + Return", "~/.local/bin/workspace-run code") +bind(mod .. " + d", "rofi -combi-modi 'window#run#ssh#emoji#calc' -modi 'calc#combi' -show combi") +bind(mod .. " + space", "~/.local/bin/stt-dictate toggle") +bind(mod .. " + SHIFT + t", "hyprlock") +bind("Print", "grim -g \"$(slurp)\" - | wl-copy") +bind("SHIFT + Print", "grim - | wl-copy") + +-- Fenêtres (mêmes touches qu'i3 en bépo) +bind(mod .. " + b", hl.dsp.window.close()) +bind(mod .. " + e", hl.dsp.window.fullscreen({ mode = "fullscreen" })) +bind(mod .. " + p", hl.dsp.layout("togglesplit")) +-- Pas de layout tabbed/stacking dans Hyprland : les groupes en sont l'équivalent +bind(mod .. " + eacute", hl.dsp.group.toggle()) +bind(mod .. " + u", hl.dsp.group.next()) +bind(mod .. " + SHIFT + space", hl.dsp.window.float({ action = "toggle" })) + +for _, arrow in ipairs({ { "Left", "l" }, { "Right", "r" }, { "Up", "u" }, { "Down", "d" } }) do + local key, direction = arrow[1], arrow[2] + bind(mod .. " + " .. key, hl.dsp.focus({ direction = direction })) + bind(mod .. " + SHIFT + " .. key, hl.dsp.window.swap({ direction = direction })) + bind(mod .. " + CTRL + " .. key, hl.dsp.workspace.move({ monitor = direction })) +end + +bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +-- Espaces de travail : keycodes 10 à 19 = rangée du haut, quelle que soit la +-- disposition (" « » ( ) @ + - / * en bépo) +for workspace = 1, 10 do + local key = "code:" .. tostring(workspace + 9) + bind(mod .. " + " .. key, hl.dsp.focus({ workspace = tostring(workspace) })) + bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = tostring(workspace) })) +end + +-- Session +bind(mod .. " + SHIFT + c", "hyprctl reload") +bind(mod .. " + SHIFT + e", "uwsm stop") + +-- Touches multimédia (actives même écran verrouillé) +local media = { locked = true, repeating = true } +bind("XF86AudioRaiseVolume", "pactl set-sink-volume @DEFAULT_SINK@ +5%", media) +bind("XF86AudioLowerVolume", "pactl set-sink-volume @DEFAULT_SINK@ -5%", media) +bind("XF86AudioMute", "pactl set-sink-mute @DEFAULT_SINK@ toggle", { locked = true }) +bind("XF86AudioPlay", "playerctl play-pause", { locked = true }) +bind("XF86AudioPause", "playerctl play-pause", { locked = true }) +bind("XF86AudioNext", "playerctl next", { locked = true }) +bind("XF86AudioPrev", "playerctl previous", { locked = true }) +bind("XF86MonBrightnessUp", "brightnessctl set +5%", media) +bind("XF86MonBrightnessDown", "brightnessctl set 5%-", media) diff --git a/files/stt-dictate.sh b/files/stt-dictate.sh index e989e8a..cd1cf3d 100755 --- a/files/stt-dictate.sh +++ b/files/stt-dictate.sh @@ -86,7 +86,11 @@ stop_and_transcribe() { # Tape le texte au curseur if [[ -n "$TEXT" ]]; then sleep 0.1 # petit delai pour focus - xdotool type --delay 10 -- "$TEXT" + if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then + wtype -d 10 -- "$TEXT" + else + xdotool type --delay 10 -- "$TEXT" + fi notify "$TEXT" else notify "Aucun texte detecte" diff --git a/files/workspace-run.sh b/files/workspace-run.sh index a7bc1f1..e8c9053 100644 --- a/files/workspace-run.sh +++ b/files/workspace-run.sh @@ -3,22 +3,35 @@ # Examples: # workspace-run.sh zeditor # workspace-run.sh sakura --working-directory +# workspace-run.sh ghostty --working-directory= (un argument finissant par = est collé au dossier) cmd=${1:?usage: workspace-run.sh [args...]} shift -workspace=$(i3-msg -t get_workspaces | jq -r '.[] | select(.focused) | .name') +if [ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]; then + workspace=$(hyprctl activeworkspace -j | jq -r '.name') +else + workspace=$(i3-msg -t get_workspaces | jq -r '.[] | select(.focused) | .name') +fi + +window_pids() { + if [ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]; then + hyprctl clients -j | jq -r --arg ws "$workspace" '.[] | select(.workspace.name == $ws) | .pid' + return + fi -terminal_cwd() { local wids wids=$(i3-msg -t get_tree | jq -r " .nodes[].nodes[].nodes[] | select(.name == \"$workspace\") | .. | objects | select(.window? != null) | .window ") - for wid in $wids; do - local pid - pid=$(xdotool getwindowpid "$wid" 2>/dev/null) || continue + xdotool getwindowpid "$wid" 2>/dev/null + done +} + +terminal_cwd() { + for pid in $(window_pids); do local shell_pid shell_pid=$(pgrep -P "$pid" -x zsh | head -1) || continue local cwd @@ -35,4 +48,9 @@ if [ -z "$dir" ]; then dir=$(grep "^${workspace}=" "$dir_file" 2>/dev/null | cut -d= -f2-) fi -exec "$cmd" "$@" "${dir:-$HOME}" +dir=${dir:-$HOME} +if [ $# -gt 0 ] && [[ "${!#}" == *= ]]; then + set -- "${@:1:$#-1}" "${!#}$dir" + exec "$cmd" "$@" +fi +exec "$cmd" "$@" "$dir" diff --git a/home.nix b/home.nix index a5d381d..39ab685 100644 --- a/home.nix +++ b/home.nix @@ -54,6 +54,7 @@ ./programs/ghostty.nix ./programs/git.nix + ./programs/hyprland.nix ./programs/i3.nix ./programs/neovim.nix ./programs/pi.nix diff --git a/nixos/configuration.nix b/nixos/configuration.nix index 191c5d4..00122f5 100644 --- a/nixos/configuration.nix +++ b/nixos/configuration.nix @@ -86,6 +86,14 @@ defaultSession = "none+i3"; }; + # Session Hyprland en test : à choisir sur l'écran de connexion. + # UWSM, comme Omarchy, lance les services de session (graphical-session.target). + programs.hyprland = { + enable = true; + withUWSM = true; + }; + security.pam.services.hyprlock = { }; + # Configure console keymap console.keyMap = "fr"; diff --git a/programs/hyprland.nix b/programs/hyprland.nix new file mode 100644 index 0000000..edcddb9 --- /dev/null +++ b/programs/hyprland.nix @@ -0,0 +1,75 @@ +{ pkgs, ... }: + +# Session Hyprland, en test à côté d'i3. Le compositeur et la session sont +# activés côté système (programs.hyprland dans nixos/configuration.nix). +{ + xdg.configFile."hypr/hyprland.lua".source = ../files/hypr/hyprland.lua; + + home.packages = with pkgs; [ + swaybg # fond d'écran (remplace feh) + grim # captures d'écran… + slurp # … d'une zone + wl-clipboard # wl-copy / wl-paste (remplace xsel) + wtype # saisie de texte (remplace xdotool), pour stt-dictate + brightnessctl # remplace xbacklight + ]; + + # Lancée par hyprland.lua et pas par systemd, pour ne pas apparaître sous i3 + programs.waybar = { + enable = true; + settings.main = { + layer = "top"; + position = "top"; + height = 26; + modules-left = [ "hyprland/workspaces" ]; + modules-center = [ "clock" ]; + modules-right = [ + "cpu" + "temperature" + "disk" + "network" + "pulseaudio" + "battery" + "tray" + ]; + clock = { + format = "{:%a %d %b %H:%M}"; + tooltip-format = "{calendar}"; + }; + cpu.format = "CPU {usage}%"; + disk.format = "/ {percentage_used}%"; + network = { + format-wifi = "{essid} {signalStrength}%"; + format-ethernet = "{ipaddr}"; + format-disconnected = "hors ligne"; + }; + pulseaudio = { + format = "vol {volume}%"; + format-muted = "muet"; + on-click = "pavucontrol"; + }; + battery.format = "bat {capacity}%"; + }; + }; + + programs.hyprlock = { + enable = true; + settings = { + general.hide_cursor = true; + background = [ + { + color = "rgb(000000)"; + } + ]; + input-field = [ + { + size = "400, 60"; + position = "0, 0"; + halign = "center"; + valign = "center"; + placeholder_text = "Mot de passe"; + } + ]; + }; + }; +}