diff --git a/home-manager/modules/hypr/hypr.nix b/home-manager/modules/hypr/hypr.nix index 79db120..721717f 100644 --- a/home-manager/modules/hypr/hypr.nix +++ b/home-manager/modules/hypr/hypr.nix @@ -14,6 +14,7 @@ ./hyprland/keybinds.nix # done ./hyprlock.nix # done ./hypridle.nix # done + ./qs/qs.nix ] ++ lib.optional (builtins.pathExists ./hyprland/my.nix) ./hyprland/my.nix; home.packages = with pkgs; [ (pkgs.writeScriptBin "wallpaper" ('' @@ -52,7 +53,7 @@ # кто-то возраждает swww # надо узнать, кому он нужен exec-once = [ - # "hyprlock" + "hyprlock --immediate-render --no-fade-in &" "wal -R &" "swww img ${config.home.homeDirectory}/Pictures/wallpaper.png &" # "(swww-daemon &) && sleep 2 && swww img ${config.home.homeDirectory}/Pictures/wallpaper.png" diff --git a/home-manager/modules/hypr/qs/qs.nix b/home-manager/modules/hypr/qs/qs.nix new file mode 100644 index 0000000..5294004 --- /dev/null +++ b/home-manager/modules/hypr/qs/qs.nix @@ -0,0 +1,552 @@ +{ config, pkgs, lib, ... }: + +let + shellQml = pkgs.writeText "shell.qml" '' + import Quickshell + import QtQuick + import QtQuick.Layouts + + import Quickshell.Wayland + import Quickshell.Hyprland + + import Quickshell.Services.Mpris + import Quickshell.Services.Pipewire + import Quickshell.Services.SystemTray + import Quickshell.Services.UPower + import Quickshell.Bluetooth + + import "widgets" as W + + ShellRoot { + id: root + + property int barHeight: 34 + property int paddingX: 10 + property int spacing: 10 + + property real backgroundOpacity: 0.45 + property int radius: 10 + property int borderWidth: 1 + + SystemClock { id: clock; precision: SystemClock.Minutes } + + Variants { + model: Quickshell.screens + + PanelWindow { + id: win + + required property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + } + + implicitHeight: root.barHeight + exclusiveZone: implicitHeight + + aboveWindows: true + focusable: false + color: "transparent" + + Rectangle { + anchors.fill: parent + radius: root.radius + border.width: root.borderWidth + border.color: Qt.rgba(1, 1, 1, 0.15) + color: Qt.rgba(0, 0, 0, root.backgroundOpacity) + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: root.paddingX + anchors.rightMargin: root.paddingX + spacing: root.spacing + + RowLayout { + spacing: root.spacing + Layout.alignment: Qt.AlignLeft + W.DashboardButton { } + W.Workspaces { } + W.Separator { } + W.WindowTitle { screen: modelData } + } + + Item { Layout.fillWidth: true } + + RowLayout { + spacing: root.spacing + Layout.alignment: Qt.AlignHCenter + W.Media { } + } + + Item { Layout.fillWidth: true } + + RowLayout { + spacing: root.spacing + Layout.alignment: Qt.AlignRight + W.Volume { } + W.Network { } + W.BluetoothIndicator { } + W.Battery { } + W.Separator { } + W.Tray { parentWindow: win } + W.KeyboardLayout { } + W.Clock { clock: clock } + W.Notifications { } + } + } + } + } + } + ''; + + barButtonQml = pkgs.writeText "BarButton.qml" '' + import QtQuick + import QtQuick.Layouts + + Item { + id: root + + property string label: "" + property string iconSource: "" + property bool emphasized: false + + signal clicked() + signal rightClicked() + + implicitHeight: 24 + implicitWidth: content.implicitWidth + 14 + + Rectangle { + anchors.fill: parent + radius: 8 + color: emphasized ? Qt.rgba(1, 1, 1, 0.18) : Qt.rgba(1, 1, 1, 0.10) + border.width: 0 + } + + RowLayout { + id: content + anchors.centerIn: parent + spacing: 6 + + Image { + visible: root.iconSource !== "" + source: root.iconSource + width: 16 + height: 16 + fillMode: Image.PreserveAspectFit + smooth: true + } + + Text { + text: root.label + font.weight: 300 + color: "white" + elide: Text.ElideRight + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function(mouse) { + if (mouse.button === Qt.LeftButton) root.clicked(); + if (mouse.button === Qt.RightButton) root.rightClicked(); + } + } + } + ''; + + separatorQml = pkgs.writeText "Separator.qml" '' + import QtQuick + import QtQuick.Layouts + + Rectangle { + Layout.preferredWidth: 1 + Layout.preferredHeight: 16 + radius: 1 + color: Qt.rgba(1, 1, 1, 0.18) + } + ''; + + dashboardQml = pkgs.writeText "DashboardButton.qml" '' + import QtQuick + import Quickshell.Io + import "." + + BarButton { + label: "≡" + + Process { id: proc } + + function run(cmd) { + proc.running = false + proc.command = ["sh", "-lc", cmd] + proc.running = true + } + + onClicked: run("command -v fuzzel >/dev/null && fuzzel || true") + onRightClicked: run("command -v wlogout >/dev/null && wlogout || true") + } + ''; + + workspacesQml = pkgs.writeText "Workspaces.qml" '' + import QtQuick + import QtQuick.Layouts + import Quickshell.Hyprland + import Quickshell.Io + import "." + + RowLayout { + spacing: 6 + + Process { id: hypr } + + function dispatchWorkspace(id) { + hypr.running = false + hypr.command = ["sh", "-lc", "hyprctl dispatch workspace " + id] + hypr.running = true + } + + Repeater { + model: Hyprland.workspaces + delegate: BarButton { + label: (modelData.name && modelData.name !== "") ? modelData.name : ("" + modelData.id) + emphasized: modelData.focused + onClicked: { + if (modelData.activate) modelData.activate() + else dispatchWorkspace(modelData.id) + } + } + } + } + ''; + + windowTitleQml = pkgs.writeText "WindowTitle.qml" '' + import QtQuick + import Quickshell.Wayland + + Item { + id: root + property var screen + + implicitHeight: 24 + implicitWidth: 320 + + Text { + anchors.verticalCenter: parent.verticalCenter + width: parent.implicitWidth + color: "white" + font.weight: 300 + elide: Text.ElideRight + + text: { + const t = ToplevelManager.activeToplevel; // singleton property :contentReference[oaicite:1]{index=1} + if (!t) return ""; + + // опционально: фильтр по монитору + if (root.screen && t.screens && t.screens.indexOf && t.screens.indexOf(root.screen) === -1) + return ""; + + return t.title || ""; + } + } + } + + ''; + + mediaQml = pkgs.writeText "Media.qml" '' + import QtQuick + import Quickshell.Services.Mpris + + Item { + implicitHeight: 24 + implicitWidth: 360 + + function pickPlayer() { + const list = Mpris.players.values || []; + if (list.length === 0) return null; + for (let i = 0; i < list.length; i++) { + if (list[i].playbackState === MprisPlaybackState.Playing) return list[i]; + } + return list[0]; + } + + Text { + anchors.verticalCenter: parent.verticalCenter + width: parent.implicitWidth + color: "white" + font.weight: 300 + elide: Text.ElideRight + text: { + const p = pickPlayer(); + if (!p) return ""; + const title = p.trackTitle || ""; + const artist = p.trackArtist || ""; + if (title === "" && artist === "") return ""; + return "\"" + (title || "Unknown Title") + "\" by " + (artist || "Unknown Artist"); + } + } + } + ''; + + volumeQml = pkgs.writeText "Volume.qml" '' + import QtQuick + import Quickshell.Services.Pipewire + import "." + + BarButton { + id: root + + PwObjectTracker { objects: [ Pipewire.defaultAudioSink ] } + + function clamp(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); } + + label: { + const s = Pipewire.defaultAudioSink; + if (!s || !s.audio) return "Vol: --"; + return "Vol: " + Math.round(s.audio.volume * 100) + "%"; + } + + WheelHandler { + onWheel: function(event) { + const s = Pipewire.defaultAudioSink; + if (!s || !s.audio) return; + const step = 0.05; + const dir = event.angleDelta.y > 0 ? -1 : 1; + s.audio.volume = root.clamp(s.audio.volume + (dir * step), 0.0, 1.0); + event.accepted = true; + } + } + } + ''; + + networkQml = pkgs.writeText "Network.qml" '' + import QtQuick + import Quickshell.Io + import "." + + BarButton { + id: root + property string ssid: "" + property int signal: -1 + + Process { + id: proc + stdout: StdioCollector { + waitForEnd: true + onStreamFinished: { + const text = this.text || ""; + const lines = text.split("\n"); + for (let i = 0; i < lines.length; i++) { + const parts = lines[i].split(":"); + if (parts.length >= 3 && parts[0] === "yes") { + root.ssid = parts[1] || ""; + root.signal = parseInt(parts[2]) || -1; + return; + } + } + root.ssid = ""; + root.signal = -1; + } + } + } + + Timer { + interval: 5000 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + proc.exec(["sh", "-lc", "nmcli -t -f active,ssid,signal dev wifi 2>/dev/null || true"]); + } + } + + function trunc(s, n) { return (s.length > n) ? (s.slice(0, n) + "…") : s; } + label: (ssid === "") ? "WiFi: --" : ("WiFi: " + trunc(ssid, 13)) + } + ''; + + btQml = pkgs.writeText "BluetoothIndicator.qml" '' + import QtQuick + import Quickshell.Bluetooth + import "." + + BarButton { + label: { + const n = (Bluetooth.devices && Bluetooth.devices.values) ? Bluetooth.devices.values.length : 0; + return n > 0 ? ("BT: " + n) : "BT: --"; + } + } + ''; + + batteryQml = pkgs.writeText "Battery.qml" '' + import QtQuick + import Quickshell.Services.UPower + import "." + + BarButton { + label: { + const d = UPower.displayDevice; + if (!d || !d.ready) return "Bat: --"; + return "Bat: " + Math.round(d.percentage) + "%"; + } + } + ''; + + trayQml = pkgs.writeText "Tray.qml" '' + import QtQuick + import QtQuick.Layouts + import Quickshell.Services.SystemTray + + RowLayout { + id: root + property var parentWindow + spacing: 6 + + Repeater { + model: SystemTray.items + delegate: Item { + implicitWidth: 18 + implicitHeight: 18 + + Image { + anchors.fill: parent + source: modelData.icon + fillMode: Image.PreserveAspectFit + smooth: true + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: function(mouse) { + if (mouse.button === Qt.LeftButton) { + if (!modelData.onlyMenu) modelData.activate(); + else if (root.parentWindow) modelData.display(root.parentWindow, mouse.x, mouse.y); + } else if (mouse.button === Qt.RightButton) { + if (root.parentWindow && modelData.hasMenu) modelData.display(root.parentWindow, mouse.x, mouse.y); + else modelData.secondaryActivate(); + } else if (mouse.button === Qt.MiddleButton) { + modelData.secondaryActivate(); + } + } + } + } + } + } + ''; + + kbQml = pkgs.writeText "KeyboardLayout.qml" '' + import QtQuick + import Quickshell.Io + import "." + + BarButton { + id: root + property string layout: "--" + + Process { + id: proc + stdout: StdioCollector { + waitForEnd: true + onStreamFinished: { + try { + const obj = JSON.parse(this.text || "{}"); + const kbs = obj.keyboards || []; + let active = null; + + for (let i = 0; i < kbs.length; i++) { + if (kbs[i].main === true) { active = kbs[i]; break; } + } + if (!active && kbs.length > 0) active = kbs[0]; + + const km = active ? (active.active_keymap || active.keymap || "") : ""; + root.layout = km !== "" ? km : "--"; + } catch (e) { + root.layout = "--"; + } + } + } + } + + Timer { + interval: 3000 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + proc.exec(["sh", "-lc", "hyprctl devices -j 2>/dev/null || echo '{}'"]); + } + } + + label: "KB: " + layout + } + ''; + + clockQml = pkgs.writeText "Clock.qml" '' + import QtQuick + import "." + + BarButton { + property var clock + label: clock ? Qt.formatDateTime(clock.date, "ddd dd HH:mm") : "--" + } + ''; + + notifQml = pkgs.writeText "Notifications.qml" '' + import QtQuick + import Quickshell.Io + import "." + + BarButton { + label: "Notif" + Process { id: proc } + + function run(cmd) { + proc.running = false + proc.command = ["sh", "-lc", cmd] + proc.running = true + } + + onClicked: run( + "command -v swaync-client >/dev/null && swaync-client -t || " + + "command -v dunstctl >/dev/null && dunstctl history-pop || true" + ) + } + ''; + + quickshellDefault = pkgs.runCommand "quickshell-default-config" { } '' + mkdir -p "$out/widgets" + + install -m644 ${shellQml} "$out/shell.qml" + + install -m644 ${barButtonQml} "$out/widgets/BarButton.qml" + install -m644 ${separatorQml} "$out/widgets/Separator.qml" + install -m644 ${dashboardQml} "$out/widgets/DashboardButton.qml" + install -m644 ${workspacesQml} "$out/widgets/Workspaces.qml" + install -m644 ${windowTitleQml} "$out/widgets/WindowTitle.qml" + install -m644 ${mediaQml} "$out/widgets/Media.qml" + install -m644 ${volumeQml} "$out/widgets/Volume.qml" + install -m644 ${networkQml} "$out/widgets/Network.qml" + install -m644 ${btQml} "$out/widgets/BluetoothIndicator.qml" + install -m644 ${batteryQml} "$out/widgets/Battery.qml" + install -m644 ${trayQml} "$out/widgets/Tray.qml" + install -m644 ${kbQml} "$out/widgets/KeyboardLayout.qml" + install -m644 ${clockQml} "$out/widgets/Clock.qml" + install -m644 ${notifQml} "$out/widgets/Notifications.qml" + ''; +in { + home.packages = with pkgs; [ quickshell networkmanager ]; + + # Важно: default должен быть обычной writable директорией в $HOME. + home.file.".config/quickshell/default" = { + source = quickshellDefault; + recursive = true; + force = true; + }; +} diff --git a/home-manager/modules/zsh/zsh.nix b/home-manager/modules/zsh/zsh.nix index 317519f..1e191e3 100644 --- a/home-manager/modules/zsh/zsh.nix +++ b/home-manager/modules/zsh/zsh.nix @@ -112,6 +112,9 @@ ta = ''f() { todo add "$*"; todo; unset -f f; }; f''; tr = ''f() { todo rm "$@"; todo; unset -f f; }; f''; td = ''f() { todo done "$@"; todo; unset -f f; }; f''; + + s = '' + (){ local val=''${1:-true} vault=''${2:-default}; local file="$HOME/stats/''${vault}.tsv"; mkdir -p "''${file:h}"; printf "%s\t%s\n" "$(date "+%Y-%m-%d %H:%M:%S")" "$val" >> "$file"; }''; }; initContent = '' @@ -152,6 +155,18 @@ [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh eval "$(zoxide init zsh --cmd cd)" + # Автоматически выполнять ls после любого перехода в каталог (только в интерактивной оболочке) + autoload -Uz add-zsh-hook + + _ls_after_cd() { + ls + } + + if [[ -o interactive ]]; then + add-zsh-hook chpwd _ls_after_cd + fi + + ''; }; diff --git a/hosts/loki-nixos/configuration.nix b/hosts/loki-nixos/configuration.nix index ba86645..d530228 100644 --- a/hosts/loki-nixos/configuration.nix +++ b/hosts/loki-nixos/configuration.nix @@ -12,6 +12,61 @@ }]; boot.resumeDevice = "/dev/nvme0n1p2"; + # -- tmp -- + + # No display manager / X11 required + services.xserver.enable = false; + services.displayManager.gdm.enable = false; + + # Hyprland (NixOS module sets up the usual system bits like portals/polkit/etc.) + # programs.hyprland = { + # enable = true; + # xwayland.enable = true; + + # # Recommended on NixOS for systemd-managed sessions (optional, but nice): + # withUWSM = true; + # }; + + # A terminal for the default Hyprland config (you can pick another) + # environment.systemPackages = with pkgs; [ + # kitty + # ]; + + # Auto-login on tty1 + services.getty.autologinUser = "krosh"; + # Optional: only do the autologin once per boot + services.getty.autologinOnce = true; + + # Start Hyprland immediately after the tty1 autologin. + # Choose ONE of the blocks below depending on your shell. + + ## If you log in with zsh: + programs.zsh.enable = true; + programs.zsh.loginShellInit = '' + if [ "$(tty)" = "/dev/tty1" ] && [ -z "$WAYLAND_DISPLAY" ] && [ -z "$DISPLAY" ] && [ -z "$SSH_CONNECTION" ]; then + # If using UWSM: + #if command -v uwsm >/dev/null 2>&1 && uwsm check may-start; then + # exec uwsm start hyprland.desktop + #fi + + # Fallback (no UWSM): + exec Hyprland 2>/dev/null 1>&2 + fi + ''; + + ## If you log in with bash instead, use this (and remove the zsh block): + # programs.bash.loginShellInit = '' + # if [ "$(tty)" = "/dev/tty1" ] && [ -z "$WAYLAND_DISPLAY" ] && [ -z "$DISPLAY" ] && [ -z "$SSH_CONNECTION" ]; then + # if command -v uwsm >/dev/null 2>&1 && uwsm check may-start; then + # exec uwsm start hyprland.desktop + # fi + # exec Hyprland + # fi + # ''; + + # systemd.services.getty."tty1".exec = + # [ "/bin/login" "-f" "krosh" "--" "${pkgs.hyprland}/bin/Hyprland" ]; + # --- Temp sections for testing purposes --- # services.pipewire = { # enable = true; diff --git a/nixos/modules/power.nix b/nixos/modules/power.nix index ff3d81e..1f7f349 100644 --- a/nixos/modules/power.nix +++ b/nixos/modules/power.nix @@ -19,7 +19,7 @@ CPU_ENERGY_PERF_POLICY_ON_SAV = "power"; PLATFORM_PROFILE_ON_AC = "performance"; - PLATFORM_PROFILE_ON_BAT = "low-power"; + PLATFORM_PROFILE_ON_BAT = "power"; PLATFORM_PROFILE_ON_SAV = "low-power"; CPU_BOOST_ON_AC = 1; diff --git a/nixos/modules/wm.nix b/nixos/modules/wm.nix index b32c8ef..7bff27e 100644 --- a/nixos/modules/wm.nix +++ b/nixos/modules/wm.nix @@ -1,14 +1,14 @@ { config, pkgs, lib, ... }: { - services.xserver.enable = true; + services.xserver.enable = lib.mkDefault true; services.xserver.xkb = { layout = "us,ru"; variant = ""; options = "grp:win_space_toggle"; }; - services.displayManager.gdm.enable = true; + services.displayManager.gdm.enable = lib.mkDefault true; services.desktopManager.gnome.enable = lib.mkDefault false; programs.uwsm.enable = true; programs.hyprland = {