commit 1b0af7b3fffae3d8f4c98ca919a2ca34206e4041 Author: wuyang <5700876+banisherwy@user.noreply.gitee.com> Date: Mon Jul 27 13:39:29 2026 +0800 Initial Ubuntu 24.04 Niri installer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f238a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache/ +.build/ +*.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..540752d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 wuyang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..60b9187 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Niri Beyond Glass + +面向 **Ubuntu 24.04 LTS** 的可移植 Niri 桌面配置。一条命令安装 Niri +26.04、Xwayland 支持和一套克制的半透明桌面组件,同时保留原有 GNOME 与 +GDM,可在登录界面随时切换会话。 + +## 效果与范围 + +- Niri 滚动平铺、窗口圆角、阴影、流畅动画 +- 支持 Niri 26.04 的背景模糊、`xray` 和轻微噪点效果 +- Waybar 顶栏,时间位于最右侧区域,图标风格统一 +- Fuzzel 启动器、Mako 通知、Swaylock 锁屏和自动息屏 +- 中文快捷键说明 +- 浏览器、视频播放器、Steam 保持完全不透明 +- 自动备份已有配置,可卸载恢复 + +本项目**不包含**动态壁纸、DMS、登录界面或 GRUB 主题,也不重新分发 +Wallpaper Engine 创意工坊图片。默认使用 Ubuntu 自带静态壁纸;安装时可指定 +自己的图片。 + +## 一键安装 + +```bash +curl -fsSL https://git.k1412.top/wuyang/niri-beyond-glass/raw/branch/main/install.sh | bash +``` + +首次安装需要编译 Niri,耗时取决于 CPU 和网络。脚本会调用 `sudo` 安装依赖, +但必须以普通用户运行。完成后注销,在 GDM 左下角的会话菜单选择 **Niri**。 + +使用自己的静态壁纸: + +```bash +curl -fsSL https://git.k1412.top/wuyang/niri-beyond-glass/raw/branch/main/install.sh \ + -o /tmp/install-niri.sh +bash /tmp/install-niri.sh --wallpaper "$HOME/Pictures/wallpaper.jpg" +``` + +只更新配置,不编译和安装软件: + +```bash +bash /tmp/install-niri.sh --config-only +``` + +## 常用快捷键 + +| 快捷键 | 功能 | +|---|---| +| `Super + Enter` | 终端 | +| `Super + D` | 应用启动器 | +| `Super + B` | 默认浏览器 | +| `Super + E` | 文件管理器 | +| `Super + O` | 总览 | +| `Super + Q` | 关闭窗口 | +| `Super + V` | 切换浮动 | +| `Super + L` | 锁屏 | +| `Super + Shift + /` | 中文快捷键总览 | +| `Super + PageUp/PageDown` | 切换工作区 | +| `Super + Shift + E` | 退出 Niri | + +`Super` 就是键盘上的 Windows 徽标键或 Mac 键盘上的 Command 键。 + +## 安全设计 + +- 仅支持 Ubuntu 24.04,检测到其他版本会停止 +- 不替换显示管理器,不修改 GDM,不移除 GNOME +- 安装前备份 `niri`、`waybar`、`fuzzel`、`mako`、`swaylock` +- Niri 和 Xwayland Satellite 固定到明确版本 +- Rust 工具链隔离在 `~/.local/share/niri-beyond-glass` +- 配置安装后使用 `niri validate` 校验 + +备份位于: + +```text +~/.local/state/niri-beyond-glass/backups/ +``` + +## 卸载 + +```bash +curl -fsSL https://git.k1412.top/wuyang/niri-beyond-glass/raw/branch/main/uninstall.sh | bash +``` + +卸载器恢复最近一次配置备份,并保留已安装的系统软件和 Niri 二进制,避免破坏 +其他会话。若要删除系统依赖,请按实际用途手动处理,不建议执行盲目的 +`apt autoremove`。 + +## 已知限制 + +- Niri 26.04 比 Ubuntu 24.04 更新,因此脚本采用源码编译,而不是跨版本安装 + PPA 二进制包。 +- Ubuntu 24.04 自带的 `libdisplay-info` 版本不足时,脚本会在 + `/usr/local` 编译安装 0.3.0。 +- 这是一套 Niri 配置,不复刻 DMS 的控制中心、登录页和动态壁纸。 +- NVIDIA 用户应先确保 Ubuntu 官方驱动在 GNOME Wayland 下工作正常。 + +## 上游项目 + +- [Niri](https://github.com/niri-wm/niri) +- [Xwayland Satellite](https://github.com/Supreeeme/xwayland-satellite) +- [Waybar](https://github.com/Alexays/Waybar) +- [Fuzzel](https://codeberg.org/dnkl/fuzzel) + +## License + +[MIT](LICENSE)。Niri、Xwayland Satellite 及各依赖遵循其各自许可证。 diff --git a/config/fuzzel/fuzzel.ini b/config/fuzzel/fuzzel.ini new file mode 100644 index 0000000..d36f87a --- /dev/null +++ b/config/fuzzel/fuzzel.ini @@ -0,0 +1,29 @@ +[main] +font=Noto Sans CJK SC:size=13 +terminal=alacritty +prompt="搜索 " +icon-theme=Yaru +icons-enabled=yes +width=46 +lines=10 +horizontal-pad=20 +vertical-pad=14 +inner-pad=8 +image-size-ratio=0.45 +layer=overlay + +[colors] +background=181a1de8 +text=f0eee8ff +match=d4c5a5ff +selection=3b3e44ff +selection-text=ffffffff +selection-match=e7d8b8ff +border=ffffff20 + +[border] +width=1 +radius=10 + +[dmenu] +exit-immediately-if-empty=yes diff --git a/config/mako/config b/config/mako/config new file mode 100644 index 0000000..48c4ff6 --- /dev/null +++ b/config/mako/config @@ -0,0 +1,16 @@ +font=Noto Sans CJK SC 12 +background-color=#181a1de8 +text-color=#f0eee8 +border-color=#ffffff20 +progress-color=over #d4c5a5 +border-size=1 +border-radius=8 +padding=14 +margin=12 +width=360 +height=120 +max-visible=5 +default-timeout=5000 +icons=1 +layer=overlay +anchor=top-right diff --git a/config/niri/config.kdl b/config/niri/config.kdl new file mode 100644 index 0000000..3bf38ed --- /dev/null +++ b/config/niri/config.kdl @@ -0,0 +1,223 @@ +// Niri Beyond Glass +// Portable profile for Ubuntu 24.04 and Niri 26.04. + +input { + keyboard { + numlock + } + + touchpad { + tap + natural-scroll + dwt + } + + mouse { + accel-profile "flat" + } +} + +environment { + "XDG_CURRENT_DESKTOP" "niri" + "XDG_SESSION_DESKTOP" "niri" + "QT_QPA_PLATFORM" "wayland;xcb" + "NIXOS_OZONE_WL" "1" + "_JAVA_AWT_WM_NONREPARENTING" "1" +} + +spawn-at-startup "xwayland-satellite" +spawn-at-startup "sh" "-lc" "$HOME/.local/bin/niri-beyond-wallpaper" +spawn-at-startup "waybar" +spawn-at-startup "mako" +spawn-at-startup "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1" +spawn-at-startup "swayidle" "-w" "timeout" "600" "swaylock -f" "timeout" "660" "niri msg action power-off-monitors" "resume" "niri msg action power-on-monitors" "before-sleep" "swaylock -f" + +hotkey-overlay { + skip-at-startup +} + +prefer-no-csd +screenshot-path "~/Pictures/Screenshots/niri-%Y-%m-%d-%H-%M-%S.png" + +cursor { + xcursor-theme "Yaru" + xcursor-size 24 +} + +layer-rule { + match namespace="swaybg" + place-within-backdrop true +} + +layer-rule { + match namespace="waybar" + geometry-corner-radius 10 + background-effect { + blur true + xray true + noise 0.018 + saturation 0.94 + } +} + +animations { + slowdown 0.85 +} + +layout { + gaps 8 + center-focused-column "never" + background-color "#15171a" + + preset-column-widths { + proportion 0.33333 + proportion 0.5 + proportion 0.66667 + } + + default-column-width { + proportion 0.5 + } + + focus-ring { + width 2 + active-color "#d4c5a5" + inactive-color "#4d5056" + urgent-color "#dc7a78" + } + + border { + off + } + + shadow { + on + softness 30 + spread -5 + offset x=0 y=8 + color "#00000070" + } + + tab-indicator { + active-color "#d4c5a5" + inactive-color "#4d5056" + urgent-color "#dc7a78" + } + + insert-hint { + color "#d4c5a580" + } +} + +window-rule { + match app-id=".*" + geometry-corner-radius 12 + clip-to-geometry true + tiled-state true + draw-border-with-background false +} + +window-rule { + match app-id=r#"(?i)^alacritty$"# + opacity 0.88 + default-column-width { + proportion 0.5 + } + background-effect { + blur true + xray true + noise 0.025 + saturation 0.95 + } +} + +window-rule { + match app-id=r#"(?i)^(firefox|org\.mozilla\.firefox|google-chrome|chromium|brave-browser|zen-browser)$"# + opacity 1.0 + default-column-width { + proportion 0.62 + } +} + +window-rule { + match app-id=r#"(?i)^(mpv|vlc|io\.github\.celluloid_player\.Celluloid|org\.gnome\.Totem|steam)$"# + opacity 1.0 + default-column-width { + proportion 0.70 + } +} + +window-rule { + match app-id=r#"^org\.gnome\.(Calculator|Settings|Loupe)$"# + open-floating true + default-column-width { + proportion 0.36 + } +} + +binds { + Mod+Return hotkey-overlay-title="打开终端" { spawn "alacritty"; } + Mod+D hotkey-overlay-title="应用启动器" { spawn "fuzzel"; } + Mod+B hotkey-overlay-title="打开默认浏览器" { spawn "sh" "-lc" "xdg-open https://www.google.com"; } + Mod+E hotkey-overlay-title="文件管理器" { spawn "nautilus"; } + Mod+L hotkey-overlay-title="锁定屏幕" { spawn "swaylock" "-f"; } + Mod+Shift+Slash hotkey-overlay-title="显示快捷键说明" { show-hotkey-overlay; } + + Mod+Q hotkey-overlay-title="关闭窗口" { close-window; } + Mod+O repeat=false hotkey-overlay-title="总览" { toggle-overview; } + Mod+F hotkey-overlay-title="最大化当前列" { maximize-column; } + Mod+Shift+F hotkey-overlay-title="窗口全屏" { fullscreen-window; } + Mod+V hotkey-overlay-title="切换浮动窗口" { toggle-window-floating; } + Mod+W hotkey-overlay-title="切换标签页模式" { toggle-column-tabbed-display; } + + Mod+Left hotkey-overlay-title="焦点向左" { focus-column-left; } + Mod+Down hotkey-overlay-title="焦点向下" { focus-window-down; } + Mod+Up hotkey-overlay-title="焦点向上" { focus-window-up; } + Mod+Right hotkey-overlay-title="焦点向右" { focus-column-right; } + Mod+H { focus-column-left; } + Mod+J { focus-window-down; } + Mod+K { focus-window-up; } + Mod+Semicolon { focus-column-right; } + + Mod+Shift+Left hotkey-overlay-title="窗口向左移动" { move-column-left; } + Mod+Shift+Down hotkey-overlay-title="窗口向下移动" { move-window-down; } + Mod+Shift+Up hotkey-overlay-title="窗口向上移动" { move-window-up; } + Mod+Shift+Right hotkey-overlay-title="窗口向右移动" { move-column-right; } + + Mod+Page_Down hotkey-overlay-title="下一个工作区" { focus-workspace-down; } + Mod+Page_Up hotkey-overlay-title="上一个工作区" { focus-workspace-up; } + Mod+Shift+Page_Down hotkey-overlay-title="窗口移到下一个工作区" { move-column-to-workspace-down; } + Mod+Shift+Page_Up hotkey-overlay-title="窗口移到上一个工作区" { move-column-to-workspace-up; } + + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+Shift+1 { move-column-to-workspace 1; } + Mod+Shift+2 { move-column-to-workspace 2; } + Mod+Shift+3 { move-column-to-workspace 3; } + Mod+Shift+4 { move-column-to-workspace 4; } + Mod+Shift+5 { move-column-to-workspace 5; } + + Mod+R hotkey-overlay-title="切换列宽" { switch-preset-column-width; } + Mod+Shift+R hotkey-overlay-title="切换窗口高度" { switch-preset-window-height; } + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + + Print hotkey-overlay-title="截图" { screenshot; } + Ctrl+Print hotkey-overlay-title="截取屏幕" { screenshot-screen; } + Alt+Print hotkey-overlay-title="截取窗口" { screenshot-window; } + + XF86AudioRaiseVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05+"; } + XF86AudioLowerVolume allow-when-locked=true { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05-"; } + XF86AudioMute allow-when-locked=true { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; } + XF86AudioPlay allow-when-locked=true { spawn "playerctl" "play-pause"; } + XF86AudioNext allow-when-locked=true { spawn "playerctl" "next"; } + XF86AudioPrev allow-when-locked=true { spawn "playerctl" "previous"; } + XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "+5%"; } + XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "5%-"; } + + Mod+Shift+E hotkey-overlay-title="退出 Niri" { quit; } + Mod+Shift+P hotkey-overlay-title="关闭显示器" { power-off-monitors; } +} diff --git a/config/swaylock/config b/config/swaylock/config new file mode 100644 index 0000000..9aca551 --- /dev/null +++ b/config/swaylock/config @@ -0,0 +1,21 @@ +daemonize +show-failed-attempts +ignore-empty-password +font=Noto Sans CJK SC +font-size=24 +indicator-radius=82 +indicator-thickness=8 +inside-color=181a1dcc +inside-clear-color=d4c5a5cc +inside-ver-color=5d83aacc +inside-wrong-color=dc7a78cc +ring-color=ffffff30 +ring-clear-color=d4c5a5ff +ring-ver-color=7aa2c8ff +ring-wrong-color=dc7a78ff +key-hl-color=d4c5a5ff +text-color=f0eee8ff +text-clear-color=181a1dff +text-ver-color=f0eee8ff +text-wrong-color=f0eee8ff +separator-color=00000000 diff --git a/config/waybar/config.jsonc b/config/waybar/config.jsonc new file mode 100644 index 0000000..6851535 --- /dev/null +++ b/config/waybar/config.jsonc @@ -0,0 +1,56 @@ +{ + "layer": "top", + "position": "top", + "height": 36, + "spacing": 6, + "margin-top": 6, + "margin-left": 8, + "margin-right": 8, + "modules-left": ["custom/launcher"], + "modules-center": [], + "modules-right": ["tray", "network", "pulseaudio", "battery", "clock", "custom/lock", "custom/power"], + "custom/launcher": { + "format": " \uf141 ", + "tooltip": false, + "on-click": "fuzzel" + }, + "tray": { + "icon-size": 17, + "spacing": 8 + }, + "network": { + "format-wifi": "\uf1eb {signalStrength}%", + "format-ethernet": "\uf1e6", + "format-disconnected": "\uf071", + "tooltip-format": "{ifname}: {ipaddr}/{cidr}" + }, + "pulseaudio": { + "format": "{icon} {volume}%", + "format-muted": "\uf026", + "format-icons": ["\uf026", "\uf027", "\uf028"], + "on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "tooltip": false + }, + "battery": { + "states": {"warning": 30, "critical": 15}, + "format": "{icon} {capacity}%", + "format-charging": "\uf0e7 {capacity}%", + "format-icons": ["\uf244", "\uf243", "\uf242", "\uf241", "\uf240"], + "tooltip": false + }, + "clock": { + "interval": 1, + "format": "{:%m月%d日 %H:%M}", + "tooltip-format": "{calendar}" + }, + "custom/lock": { + "format": "\uf023", + "tooltip": false, + "on-click": "swaylock -f" + }, + "custom/power": { + "format": "\uf011", + "tooltip": false, + "on-click": "wlogout" + } +} diff --git a/config/waybar/style.css b/config/waybar/style.css new file mode 100644 index 0000000..a742fde --- /dev/null +++ b/config/waybar/style.css @@ -0,0 +1,59 @@ +@define-color panel rgba(24, 26, 29, 0.76); +@define-color text #f0eee8; +@define-color muted #a8abb2; +@define-color accent #d4c5a5; +@define-color red #dc7a78; + +* { + border: none; + border-radius: 0; + min-height: 0; + font-family: "Noto Sans CJK SC", "Noto Sans", "FontAwesome"; + font-size: 13px; + font-weight: 500; +} + +window#waybar { + background: @panel; + color: @text; + border: 1px solid rgba(255, 255, 255, 0.10); + border-radius: 10px; +} + +#custom-launcher, +#tray, +#network, +#pulseaudio, +#battery, +#clock, +#custom-lock, +#custom-power { + padding: 0 10px; + color: @text; +} + +#custom-launcher { + color: @accent; + font-size: 16px; +} + +#network.disconnected, +#battery.critical { + color: @red; +} + +#custom-lock, +#custom-power { + padding: 0 12px; +} + +#custom-power { + color: @red; +} + +tooltip { + background: #202226; + color: @text; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..0127c65 --- /dev/null +++ b/install.sh @@ -0,0 +1,323 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +readonly PROJECT="niri-beyond-glass" +readonly NIRI_TAG="v26.04" +readonly XWS_TAG="v0.8.2" +readonly RUST_TOOLCHAIN="1.85.1" +readonly ARCHIVE_URL="https://git.k1412.top/wuyang/niri-beyond-glass/archive/main.tar.gz" + +wallpaper="" +config_only=0 +dry_run=0 +rebuild=0 +source_dir="${NBG_SOURCE_DIR:-}" +tmp_dir="" + +usage() { + cat <<'EOF' +用法:install.sh [选项] + + --wallpaper PATH 使用指定的静态壁纸 + --config-only 只更新配置,不安装/编译软件 + --rebuild 强制重新编译 Niri 和 Xwayland Satellite + --dry-run 显示将执行的主要步骤,不修改系统 + -h, --help 显示帮助 +EOF +} + +log() { + printf '\033[1;36m[Niri]\033[0m %s\n' "$*" +} + +die() { + printf '\033[1;31m错误:\033[0m %s\n' "$*" >&2 + exit 1 +} + +run() { + if (( dry_run )); then + printf '+' + printf ' %q' "$@" + printf '\n' + else + "$@" + fi +} + +cleanup() { + [[ -n "$tmp_dir" && -d "$tmp_dir" ]] && rm -rf "$tmp_dir" +} +trap cleanup EXIT + +while (($#)); do + case "$1" in + --wallpaper) + (($# >= 2)) || die "--wallpaper 缺少路径" + wallpaper="$2" + shift 2 + ;; + --config-only) + config_only=1 + shift + ;; + --rebuild) + rebuild=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "未知参数:$1" + ;; + esac +done + +[[ ${EUID:-$(id -u)} -ne 0 ]] || die "请用普通用户运行,脚本会在需要时调用 sudo。" +[[ -z "$wallpaper" || -f "$wallpaper" ]] || die "壁纸不存在:$wallpaper" + +if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release +else + die "无法识别操作系统。" +fi + +if [[ "${ID:-}" != "ubuntu" || "${VERSION_ID:-}" != "24.04" ]]; then + die "只支持 Ubuntu 24.04;当前是 ${PRETTY_NAME:-未知系统}。" +fi + +case "$(uname -m)" in + x86_64|aarch64) ;; + *) die "目前只支持 x86_64 和 aarch64。" ;; +esac + +fetch_source() { + if [[ -n "$source_dir" ]]; then + [[ -f "$source_dir/config/niri/config.kdl" ]] || die "NBG_SOURCE_DIR 不是有效源码目录。" + return + fi + + tmp_dir="$(mktemp -d)" + log "下载配置仓库" + curl -fL --retry 3 "$ARCHIVE_URL" -o "$tmp_dir/source.tar.gz" + mkdir -p "$tmp_dir/source" + tar -xzf "$tmp_dir/source.tar.gz" -C "$tmp_dir/source" --strip-components=1 + source_dir="$tmp_dir/source" +} + +install_packages() { + log "安装 Ubuntu 24.04 依赖" + run sudo apt-get update + run sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y \ + build-essential clang git curl ca-certificates pkg-config \ + meson ninja-build cmake \ + libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev \ + libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev \ + libseat-dev libpipewire-0.3-dev libpango1.0-dev \ + libdisplay-info-dev libcairo2-dev libglib2.0-dev \ + libxcb1-dev libxcb-composite0-dev libxcb-randr0-dev \ + libxcb-res0-dev libxcb-xfixes0-dev libxcb-cursor-dev \ + xwayland xdg-desktop-portal-gnome xdg-desktop-portal-gtk \ + alacritty fuzzel waybar swaybg swaylock swayidle mako-notifier \ + nautilus policykit-1-gnome pipewire-pulse wireplumber \ + brightnessctl playerctl grim slurp wl-clipboard wlogout \ + fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji fonts-font-awesome +} + +install_rust() { + export RUSTUP_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/$PROJECT/rustup" + export CARGO_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/$PROJECT/cargo" + export PATH="$CARGO_HOME/bin:$PATH" + + if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then + log "安装隔离的 Rust $RUST_TOOLCHAIN 工具链" + if (( dry_run )); then + printf '+ curl https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain %q\n' "$RUST_TOOLCHAIN" + return + fi + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | + sh -s -- -y --no-modify-path --profile minimal --default-toolchain "$RUST_TOOLCHAIN" + else + run "$CARGO_HOME/bin/rustup" toolchain install "$RUST_TOOLCHAIN" --profile minimal + run "$CARGO_HOME/bin/rustup" default "$RUST_TOOLCHAIN" + fi +} + +version_ge() { + [[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" == "$2" ]] +} + +install_libdisplay_info() { + local installed="" + installed="$(pkg-config --modversion libdisplay-info 2>/dev/null || true)" + if [[ -n "$installed" ]] && version_ge "$installed" "0.3.0"; then + return + fi + + log "编译 libdisplay-info 0.3.0(Ubuntu 24.04 自带版本较旧)" + local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build" + local src="$build_root/libdisplay-info" + run mkdir -p "$build_root" + if [[ ! -d "$src/.git" ]]; then + run git clone --depth 1 --branch 0.3.0 \ + https://gitlab.freedesktop.org/emersion/libdisplay-info.git "$src" + fi + run meson setup "$src/build" "$src" --prefix=/usr/local --buildtype=release --wipe + run ninja -C "$src/build" + run sudo ninja -C "$src/build" install + run sudo ldconfig +} + +install_niri() { + local current="" + current="$(niri --version 2>/dev/null || true)" + if (( ! rebuild )) && [[ "$current" == *"26.4"* || "$current" == *"26.04"* ]]; then + log "已安装兼容的 $current,跳过编译" + return + fi + + log "编译 Niri $NIRI_TAG" + local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build" + local src="$build_root/niri" + run mkdir -p "$build_root" + if [[ ! -d "$src/.git" ]]; then + run git clone --depth 1 --branch "$NIRI_TAG" https://github.com/niri-wm/niri.git "$src" + else + run git -C "$src" fetch --depth 1 origin "refs/tags/$NIRI_TAG:refs/tags/$NIRI_TAG" + run git -C "$src" checkout -f "$NIRI_TAG" + fi + run cargo build --manifest-path "$src/Cargo.toml" --release --locked + + run sudo install -Dm755 "$src/target/release/niri" /usr/local/bin/niri + run sudo install -Dm755 "$src/resources/niri-session" /usr/local/bin/niri-session + run sudo install -Dm644 "$src/resources/niri.service" /usr/local/lib/systemd/user/niri.service + run sudo install -Dm644 "$src/resources/niri-shutdown.target" /usr/local/lib/systemd/user/niri-shutdown.target + run sudo install -Dm644 "$src/resources/niri.desktop" /usr/local/share/wayland-sessions/niri.desktop + run sudo install -Dm644 "$src/resources/niri-portals.conf" /usr/local/share/xdg-desktop-portal/niri-portals.conf +} + +install_xwayland_satellite() { + if (( ! rebuild )) && command -v xwayland-satellite >/dev/null 2>&1; then + return + fi + + log "编译 Xwayland Satellite $XWS_TAG" + local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build" + local src="$build_root/xwayland-satellite" + run mkdir -p "$build_root" + if [[ ! -d "$src/.git" ]]; then + run git clone --depth 1 --branch "$XWS_TAG" \ + https://github.com/Supreeeme/xwayland-satellite.git "$src" + else + run git -C "$src" fetch --depth 1 origin "refs/tags/$XWS_TAG:refs/tags/$XWS_TAG" + run git -C "$src" checkout -f "$XWS_TAG" + fi + run cargo build --manifest-path "$src/Cargo.toml" --release --locked + run sudo install -Dm755 "$src/target/release/xwayland-satellite" /usr/local/bin/xwayland-satellite +} + +backup_and_install_config() { + local config_home="${XDG_CONFIG_HOME:-$HOME/.config}" + local state_home="${XDG_STATE_HOME:-$HOME/.local/state}" + local stamp + stamp="$(date +%Y%m%d-%H%M%S)" + local backup="$state_home/$PROJECT/backups/$stamp/config" + local names=(niri waybar fuzzel mako swaylock niri-beyond-glass) + + log "备份并安装用户配置" + run mkdir -p "$backup" + local name + for name in "${names[@]}"; do + if [[ -e "$config_home/$name" ]]; then + run cp -a "$config_home/$name" "$backup/$name" + fi + done + + run mkdir -p \ + "$config_home/niri" \ + "$config_home/waybar" \ + "$config_home/fuzzel" \ + "$config_home/mako" \ + "$config_home/swaylock" \ + "$config_home/niri-beyond-glass" \ + "$HOME/.local/bin" \ + "$HOME/Pictures/Screenshots" + + run install -m644 "$source_dir/config/niri/config.kdl" "$config_home/niri/config.kdl" + run install -m644 "$source_dir/config/waybar/config.jsonc" "$config_home/waybar/config.jsonc" + run install -m644 "$source_dir/config/waybar/style.css" "$config_home/waybar/style.css" + run install -m644 "$source_dir/config/fuzzel/fuzzel.ini" "$config_home/fuzzel/fuzzel.ini" + run install -m644 "$source_dir/config/mako/config" "$config_home/mako/config" + run install -m644 "$source_dir/config/swaylock/config" "$config_home/swaylock/config" + run install -m755 "$source_dir/scripts/niri-beyond-wallpaper" "$HOME/.local/bin/niri-beyond-wallpaper" + + select_wallpaper "$config_home/niri-beyond-glass/wallpaper" +} + +select_wallpaper() { + local state_file="$1" + local selected="$wallpaper" + + if [[ -z "$selected" ]]; then + local candidates=( + /usr/share/backgrounds/warty-final-ubuntu.png + /usr/share/backgrounds/ubuntu-default-greyscale-wallpaper.png + ) + local candidate + for candidate in "${candidates[@]}"; do + if [[ -f "$candidate" ]]; then + selected="$candidate" + break + fi + done + fi + + if [[ -z "$selected" ]]; then + selected="$(find /usr/share/backgrounds -maxdepth 2 -type f \ + \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) \ + -print -quit 2>/dev/null || true)" + fi + + if (( dry_run )); then + printf '+ write wallpaper path %q to %q\n' "$selected" "$state_file" + else + printf '%s\n' "$selected" > "$state_file" + fi +} + +validate_config() { + if (( dry_run )); then + printf '+ niri validate -c %q\n' "${XDG_CONFIG_HOME:-$HOME/.config}/niri/config.kdl" + return + fi + command -v niri >/dev/null 2>&1 || die "找不到 niri,无法验证配置。" + niri validate -c "${XDG_CONFIG_HOME:-$HOME/.config}/niri/config.kdl" +} + +main() { + fetch_source + + if (( ! config_only )); then + install_packages + install_rust + install_libdisplay_info + install_niri + install_xwayland_satellite + fi + + backup_and_install_config + validate_config + + log "安装完成" + printf '\n请注销,在 GDM 会话菜单中选择“Niri”后登录。\n' + printf 'GNOME 和 GDM 均未被修改;快捷键说明:Super+Shift+/。\n' +} + +main diff --git a/scripts/niri-beyond-wallpaper b/scripts/niri-beyond-wallpaper new file mode 100755 index 0000000..faf9c4a --- /dev/null +++ b/scripts/niri-beyond-wallpaper @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +state="${XDG_CONFIG_HOME:-$HOME/.config}/niri-beyond-glass/wallpaper" +wallpaper="" + +if [[ -s "$state" ]]; then + IFS= read -r wallpaper < "$state" +fi + +if [[ -n "$wallpaper" && -f "$wallpaper" ]]; then + exec swaybg -m fill -i "$wallpaper" +fi + +exec swaybg -c '#15171a' diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..bda260c --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +config_home="${XDG_CONFIG_HOME:-$HOME/.config}" +state_home="${XDG_STATE_HOME:-$HOME/.local/state}" +backup_root="$state_home/niri-beyond-glass/backups" +latest="" + +if [[ -d "$backup_root" ]]; then + latest="$(find "$backup_root" -mindepth 1 -maxdepth 1 -type d -printf '%p\n' | sort | tail -n 1)" +fi + +rm -rf \ + "$config_home/niri" \ + "$config_home/waybar" \ + "$config_home/fuzzel" \ + "$config_home/mako" \ + "$config_home/swaylock" \ + "$config_home/niri-beyond-glass" +rm -f "$HOME/.local/bin/niri-beyond-wallpaper" + +if [[ -n "$latest" && -d "$latest/config" ]]; then + cp -a "$latest/config/." "$config_home/" + printf '已恢复备份:%s\n' "$latest" +else + printf '配置已移除;没有找到可恢复的备份。\n' +fi + +printf 'Niri 和系统软件包被保留。GNOME/GDM 未修改。\n'