wry, many thing
This commit is contained in:
parent
6e62eccfba
commit
848ed62c5d
47 changed files with 1598 additions and 1201 deletions
|
|
@ -1,74 +0,0 @@
|
|||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
lib,
|
||||
getFlakePkg,
|
||||
...
|
||||
}:
|
||||
let
|
||||
jay = pkgs.jay.overrideAttrs (prev: {
|
||||
version = "unstable-slay-${toString inputs.jay-src.lastModified}";
|
||||
src = inputs.jay-src;
|
||||
cargoDeps = pkgs.rustPlatform.importCargoLock {
|
||||
lockFile = "${inputs.jay-src}/Cargo.lock";
|
||||
};
|
||||
patches = [
|
||||
./patches/0001-add-configurable-gap-between-tiled-windows.patch
|
||||
./patches/0002-add-position-and-size-animations-for-tiled-windows.patch
|
||||
./patches/0003-add-window-border-frames-when-gaps-are-enabled.patch
|
||||
./patches/0004-add-sequential-animation-mode-for-tiled-windows.patch
|
||||
./patches/0005-add-cursor-follows-focus-setting.patch
|
||||
./patches/0006-add-toggle-focus-between-floating-and-tiled-layers.patch
|
||||
./patches/0007-add-open-close-animations-for-tiled-windows.patch
|
||||
./patches/0008-add-directional-focus-navigation-for-floating-window.patch
|
||||
];
|
||||
});
|
||||
|
||||
jay-session = pkgs.writeShellScript "jay-session" ''
|
||||
systemctl --user import-environment
|
||||
dbus-update-activation-environment --all
|
||||
systemctl --user start jay-session-bridge.service &
|
||||
exec ${lib.getExe jay} run
|
||||
'';
|
||||
in
|
||||
{
|
||||
environment.systemPackages = [ jay ];
|
||||
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
settings.default_session.command = "${lib.getExe (getFlakePkg inputs.tuigreet)} --sessions /etc/greetd/wayland-sessions --remember-session";
|
||||
};
|
||||
|
||||
environment.etc."greetd/wayland-sessions/jay.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
Name=Jay
|
||||
Comment=A Wayland compositor written in Rust
|
||||
Exec=${jay-session}
|
||||
Type=Application
|
||||
DesktopNames=jay
|
||||
'';
|
||||
|
||||
# bridge service to activate graphical-session.target for direct-launched jay.
|
||||
# waits for jay IPC readiness before pulling in the target.
|
||||
systemd.user.services.jay-session-bridge = {
|
||||
unitConfig = {
|
||||
Description = "Activate graphical-session.target for direct-launched jay";
|
||||
BindsTo = [ "graphical-session.target" ];
|
||||
Before = [ "graphical-session.target" ];
|
||||
Wants = [ "graphical-session-pre.target" ];
|
||||
After = [ "graphical-session-pre.target" ];
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
# Jay's IPC goes through the Wayland socket (unlike niri's separate IPC socket),
|
||||
# so we can't use a jay subcommand without WAYLAND_DISPLAY already set.
|
||||
# Wait for Jay to create its wayland socket in XDG_RUNTIME_DIR instead.
|
||||
ExecStart = pkgs.writeShellScript "jay-ready" ''
|
||||
until find "$XDG_RUNTIME_DIR" -maxdepth 1 -name "wayland-*" ! -name "*.lock" -type s | grep -q .; do
|
||||
sleep 0.1
|
||||
done
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -182,6 +182,11 @@ Singleton {
|
|||
id: clickableEntry
|
||||
required property DesktopEntry modelData
|
||||
onClicked: {
|
||||
// console.log(modelData.command);
|
||||
// Quickshell.execDetached({
|
||||
// command: ["systemd-run", "--slice-inherit", "--user", "-t", "-G"].concat(modelData.command),
|
||||
// workingDirectory: modelData.workingDirectory
|
||||
// });
|
||||
modelData.execute();
|
||||
launcherData.open = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ Singleton {
|
|||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
width: parent.width / 3 * 2
|
||||
height: parent.height / 6 * 5
|
||||
Repeater {
|
||||
model: topLevel.buttons.length
|
||||
Button {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,56 @@
|
|||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
NotificationServer {
|
||||
id: notifications
|
||||
id: server
|
||||
actionsSupported: true
|
||||
bodyHyperlinksSupported: true
|
||||
// bodyImagesSupported: true
|
||||
bodyMarkupSupported: true
|
||||
imageSupported: true
|
||||
imageSupported: true
|
||||
onNotification: noti => {
|
||||
|
||||
noti.tracked = true
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
active: server.trackedNotifications.values.length > 0
|
||||
|
||||
WlrLayershell {
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
}
|
||||
implicitWidth: 344
|
||||
implicitHeight: notiColumn.implicitHeight + 24
|
||||
color: "transparent"
|
||||
layer: WlrLayer.Overlay
|
||||
namespace: "shell:noti"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
Column {
|
||||
id: notiColumn
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
topMargin: 12
|
||||
rightMargin: 12
|
||||
}
|
||||
spacing: 8
|
||||
width: 320
|
||||
|
||||
Repeater {
|
||||
model: server.trackedNotifications
|
||||
NotiCard {
|
||||
required property var modelData
|
||||
notification: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
142
graphical/desktop/quickshell/noti/NotiCard.qml
Normal file
142
graphical/desktop/quickshell/noti/NotiCard.qml
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var notification
|
||||
|
||||
width: 320
|
||||
implicitHeight: card.implicitHeight * heightScale
|
||||
|
||||
property string borderColor: {
|
||||
switch (notification.urgency) {
|
||||
case NotificationUrgency.Critical: return "#C46056"
|
||||
case NotificationUrgency.Low: return "#8CC4C9"
|
||||
default: return "#FFAB5B"
|
||||
}
|
||||
}
|
||||
|
||||
// --- slide-in/out ---
|
||||
property real slideProgress: 0.0
|
||||
opacity: slideProgress
|
||||
transform: Translate { x: (1.0 - root.slideProgress) * 32 }
|
||||
|
||||
Behavior on slideProgress {
|
||||
NumberAnimation { duration: 300; easing.type: Easing.InOutExpo }
|
||||
}
|
||||
|
||||
// --- height collapse (after slide-out, before model removal) ---
|
||||
property real heightScale: 1.0
|
||||
|
||||
Behavior on heightScale {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.InOutExpo }
|
||||
}
|
||||
|
||||
property bool dismissing: false
|
||||
|
||||
function beginDismiss() {
|
||||
if (root.dismissing) return
|
||||
root.dismissing = true
|
||||
root.slideProgress = 0.0
|
||||
collapseTimer.start()
|
||||
}
|
||||
|
||||
// wait for slide-out, then collapse height
|
||||
Timer {
|
||||
id: collapseTimer
|
||||
interval: 310
|
||||
onTriggered: {
|
||||
root.heightScale = 0.0
|
||||
removeTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
// wait for height collapse, then remove from model
|
||||
Timer {
|
||||
id: removeTimer
|
||||
interval: 210
|
||||
onTriggered: root.notification.dismiss()
|
||||
}
|
||||
|
||||
Component.onCompleted: root.slideProgress = 1.0
|
||||
|
||||
// auto-dismiss
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
interval: notification.expireTimeout > 0 ? notification.expireTimeout : 5000
|
||||
running: !(notification.urgency === NotificationUrgency.Critical && notification.expireTimeout <= 0)
|
||||
repeat: false
|
||||
onTriggered: root.beginDismiss()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: parent.width
|
||||
implicitHeight: content.implicitHeight + 32
|
||||
color: "#1B2021"
|
||||
border.color: root.borderColor
|
||||
border.width: 2
|
||||
// sharp on the screen-edge diagonal (top-right), rounded on the inner diagonal
|
||||
topRightRadius: 0
|
||||
bottomLeftRadius: 0
|
||||
topLeftRadius: 10
|
||||
bottomRightRadius: 10
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.beginDismiss()
|
||||
}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 16
|
||||
}
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: notification.summary
|
||||
color: "#CECBCA"
|
||||
font { family: "MS W98 UI"; pointSize: 12; bold: true }
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: notification.body
|
||||
visible: notification.body !== ""
|
||||
color: "#CECBCA"
|
||||
font { family: "MS W98 UI"; pointSize: 12 }
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 6
|
||||
visible: notification.actions.length > 0
|
||||
topPadding: 4
|
||||
|
||||
Repeater {
|
||||
model: notification.actions
|
||||
Button {
|
||||
required property var modelData
|
||||
text: modelData.text
|
||||
font { family: "MS W98 UI"; pointSize: 11 }
|
||||
palette.buttonText: "#FFAB5B"
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? "#FFAB5B" : "#272A2A"
|
||||
border.color: "#FFAB5B"
|
||||
border.width: 2
|
||||
radius: 6
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
onClicked: modelData.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
163
graphical/desktop/quickshell/osd/Osd.qml
Normal file
163
graphical/desktop/quickshell/osd/Osd.qml
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property real value: 0.0
|
||||
property string mode: "volume"
|
||||
property bool muted: false
|
||||
property bool shown: false
|
||||
|
||||
property real showProgress: root.shown ? 1.0 : 0.0
|
||||
Behavior on showProgress {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.InOutExpo }
|
||||
}
|
||||
|
||||
function show(newMode, newValue, newMuted) {
|
||||
mode = newMode
|
||||
value = Math.max(0.0, Math.min(1.0, newValue))
|
||||
muted = newMuted
|
||||
shown = true
|
||||
hideTimer.restart()
|
||||
}
|
||||
|
||||
function parseVolume(line) {
|
||||
let parts = line.trim().split(/\s+/)
|
||||
let vol = parseFloat(parts[1])
|
||||
if (isNaN(vol)) return
|
||||
root.show("volume", vol, line.includes("[MUTED]"))
|
||||
}
|
||||
|
||||
function parseBrightness(line) {
|
||||
let parts = line.trim().split(/\s+/)
|
||||
let cur = parseInt(parts[0])
|
||||
let max = parseInt(parts[1])
|
||||
if (isNaN(cur) || isNaN(max) || max === 0) return
|
||||
root.show("brightness", cur / max, false)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: root.shown = false
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "osd"
|
||||
function volumeUp(): void { volumeUpCmd.running = true }
|
||||
function volumeDown(): void { volumeDownCmd.running = true }
|
||||
function toggleMute(): void { toggleMuteCmd.running = true }
|
||||
function brightnessUp(): void { brightnessUpCmd.running = true }
|
||||
function brightnessDown(): void { brightnessDownCmd.running = true }
|
||||
}
|
||||
|
||||
Process {
|
||||
id: volumeUpCmd
|
||||
command: ["sh", "-c", "wpctl set-volume -l 1.0 @DEFAULT_SINK@ 5%+ && wpctl get-volume @DEFAULT_SINK@"]
|
||||
stdout: SplitParser { onRead: line => root.parseVolume(line) }
|
||||
}
|
||||
Process {
|
||||
id: volumeDownCmd
|
||||
command: ["sh", "-c", "wpctl set-volume @DEFAULT_SINK@ 5%- && wpctl get-volume @DEFAULT_SINK@"]
|
||||
stdout: SplitParser { onRead: line => root.parseVolume(line) }
|
||||
}
|
||||
Process {
|
||||
id: toggleMuteCmd
|
||||
command: ["sh", "-c", "wpctl set-mute @DEFAULT_SINK@ toggle && wpctl get-volume @DEFAULT_SINK@"]
|
||||
stdout: SplitParser { onRead: line => root.parseVolume(line) }
|
||||
}
|
||||
Process {
|
||||
id: brightnessUpCmd
|
||||
command: ["sh", "-c", "brightnessctl s 5%+ > /dev/null; printf '%s %s' \"$(brightnessctl g)\" \"$(brightnessctl m)\""]
|
||||
stdout: SplitParser { onRead: line => root.parseBrightness(line) }
|
||||
}
|
||||
Process {
|
||||
id: brightnessDownCmd
|
||||
command: ["sh", "-c", "brightnessctl s 5%- > /dev/null; printf '%s %s' \"$(brightnessctl g)\" \"$(brightnessctl m)\""]
|
||||
stdout: SplitParser { onRead: line => root.parseBrightness(line) }
|
||||
}
|
||||
|
||||
WlrLayershell {
|
||||
anchors { bottom: true; left: true; right: true }
|
||||
implicitHeight: 90
|
||||
color: "transparent"
|
||||
visible: root.showProgress > 0
|
||||
layer: WlrLayer.Overlay
|
||||
namespace: "shell:osd"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
Item {
|
||||
id: osdItem
|
||||
anchors.centerIn: parent
|
||||
width: 260
|
||||
height: osdCard.implicitHeight
|
||||
|
||||
opacity: root.showProgress
|
||||
transform: Translate { y: (1.0 - root.showProgress) * 10 }
|
||||
|
||||
Rectangle {
|
||||
id: osdCard
|
||||
width: parent.width
|
||||
implicitHeight: osdContent.implicitHeight + 24
|
||||
color: "#1B2021"
|
||||
border.color: root.muted ? "#C46056" : "#FFAB5B"
|
||||
border.width: 2
|
||||
topLeftRadius: 0
|
||||
bottomRightRadius: 0
|
||||
topRightRadius: 10
|
||||
bottomLeftRadius: 10
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
|
||||
Column {
|
||||
id: osdContent
|
||||
anchors { fill: parent; margins: 12 }
|
||||
spacing: 8
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: modeLabel.height
|
||||
|
||||
Text {
|
||||
id: modeLabel
|
||||
anchors.left: parent.left
|
||||
text: root.muted ? "Muted" : (root.mode === "brightness" ? "Brightness" : "Volume")
|
||||
color: "#CECBCA"
|
||||
font { family: "MS W98 UI"; pointSize: 12 }
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
text: Math.round(root.value * 100) + "%"
|
||||
color: root.muted ? "#C46056" : "#FFAB5B"
|
||||
font { family: "MS W98 UI"; pointSize: 12 }
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 6
|
||||
color: "#272A2A"
|
||||
radius: 3
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * root.value
|
||||
height: parent.height
|
||||
color: root.muted ? "#C46056" : "#FFAB5B"
|
||||
radius: 3
|
||||
Behavior on width { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } }
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
property var c: {
|
||||
"bg": "#1b2021",
|
||||
"fg": "#cecbca",
|
||||
|
||||
"black": "#272a2a",
|
||||
"black_b": "#202e2f",
|
||||
|
||||
"red": "#c43325",
|
||||
"red_b": "#c46056",
|
||||
|
||||
"green": "#8cc992",
|
||||
"green_b": "#c2dab0",
|
||||
|
||||
"yellow": "#ffb852",
|
||||
"yellow_b": "#ffab5b",
|
||||
|
||||
"blue": "#5299ff",
|
||||
"blue_b": "#92beff",
|
||||
|
||||
"magenta": "#645ac9",
|
||||
"magenta_b": "#928cc9",
|
||||
|
||||
"cyan": "#5abfc9",
|
||||
"cyan_b": "#8cc4c9",
|
||||
|
||||
"white": "#b0c2da",
|
||||
"white_b": "#caccce",
|
||||
|
||||
}
|
||||
}
|
||||
1
graphical/desktop/quickshell/rice/Colours.qml
Symbolic link
1
graphical/desktop/quickshell/rice/Colours.qml
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/nix/store/bc2v8k5620k5p57ggrxy6i5w6pay9kis-Colours.qml
|
||||
|
|
@ -4,8 +4,10 @@ import "launcher" as Launcher
|
|||
import "logout" as Logout
|
||||
// singletons
|
||||
import "title"
|
||||
import "tags"
|
||||
import "tags"
|
||||
import "rice"
|
||||
import "noti" as Noti
|
||||
import "osd" as Osd
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
|
@ -244,6 +246,9 @@ ShellRoot {
|
|||
// }//invisible rect
|
||||
// }
|
||||
|
||||
Noti.Noti {}
|
||||
Osd.Osd {}
|
||||
|
||||
// pops up on current monitor
|
||||
Launcher.Launcher {
|
||||
width: 190
|
||||
|
|
@ -273,4 +278,5 @@ ShellRoot {
|
|||
command: "systemctl reboot"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,25 @@
|
|||
}:
|
||||
let
|
||||
inherit (lib) getExe;
|
||||
shotta =
|
||||
let
|
||||
grim = lib.getExe pkgs.grim;
|
||||
slurp = lib.getExe pkgs.slurp;
|
||||
wl-copy = lib.getExe' pkgs.wl-clipboard-rs "wl-copy";
|
||||
wl-paste = lib.getExe' pkgs.wl-clipboard-rs "wl-paste";
|
||||
in
|
||||
pkgs.writeScriptBin "shotta" ''
|
||||
#! /usr/bin/env nu
|
||||
let savePath = $"~/screenshots/(date now | format date "%Y-%m-%d-%H-%M-%S").png" | path expand
|
||||
${grim} -g $"(${slurp})" -c - | ${wl-copy}
|
||||
${wl-paste} o> $savePath
|
||||
'';
|
||||
|
||||
ui = config.rice.roles config.rice.palette.hex;
|
||||
in
|
||||
{
|
||||
user.packages = getPkgs {
|
||||
# quickshell stuff
|
||||
environment.systemPackages = getPkgs {
|
||||
inherit shotta;
|
||||
inherit (pkgs.kdePackages) qtbase qtdeclarative;
|
||||
inherit (pkgs) wl-clipboard quickshell;
|
||||
};
|
||||
|
|
@ -23,7 +38,6 @@ in
|
|||
];
|
||||
services.stasis = {
|
||||
enable = false;
|
||||
extraPathPackages = [ (config.programs.niri.package) ];
|
||||
extraConfig = ''
|
||||
default:
|
||||
dpms_off:
|
||||
|
|
@ -43,40 +57,55 @@ in
|
|||
excludedApps = [ "Bitwarden" ];
|
||||
};
|
||||
|
||||
quick.services = {
|
||||
noti.cmd = "${getExe pkgs.swaynotificationcenter}";
|
||||
shell = {
|
||||
cmd = "${getExe pkgs.quickshell}";
|
||||
restart = true;
|
||||
};
|
||||
# pwManager.cmd = "${getExe config.apps.passwordManager}";
|
||||
# music = "${getExe config.apps.streamPlayer}";
|
||||
};
|
||||
|
||||
# user.systemd.services.music = {
|
||||
# environment.PATH = lib.mkForce "/run/current-system/sw/bin:/run/current-system/sw/sbin:/etc/profiles/per-user/${mainUser}/bin:/etc/profiles/per-user/${mainUser}/sbin";
|
||||
# unitConfig = {
|
||||
# Description = "airdrome";
|
||||
# Requires = [
|
||||
# "graphical-session.target"
|
||||
# ];
|
||||
# After = [
|
||||
# "graphical-session.target"
|
||||
# "niri.target"
|
||||
# ];
|
||||
# PartOf = [ "graphical-session.target" ];
|
||||
# };
|
||||
# serviceConfig = {
|
||||
# ExecStart = "${getExe config.apps.streamPlayer}";
|
||||
# Type = "forking";
|
||||
# };
|
||||
# wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
# };
|
||||
|
||||
environment.files."/home/${mainUser}/.config/quickshell" = {
|
||||
source = "/home/${mainUser}/.nix/graphical/desktop/quickshell";
|
||||
uid = 1000;
|
||||
gid = 100;
|
||||
};
|
||||
|
||||
# generate Colours.qml from palette into the source tree via arbys
|
||||
environment.files."/home/${mainUser}/.nix/graphical/desktop/quickshell/rice/Colours.qml" =
|
||||
let
|
||||
pal = config.rice.palette.hex;
|
||||
in
|
||||
{
|
||||
source = pkgs.writeText "Colours.qml" ''
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
property var c: {
|
||||
"bg": "${ui.bg}",
|
||||
"fg": "${ui.fg}",
|
||||
|
||||
"black": "${pal.normal.black}",
|
||||
"black_b": "${pal.bright.black}",
|
||||
|
||||
"red": "${pal.normal.red}",
|
||||
"red_b": "${pal.bright.red}",
|
||||
|
||||
"green": "${pal.normal.green}",
|
||||
"green_b": "${pal.bright.green}",
|
||||
|
||||
"yellow": "${pal.normal.yellow}",
|
||||
"yellow_b": "${pal.bright.yellow}",
|
||||
|
||||
"blue": "${pal.normal.blue}",
|
||||
"blue_b": "${pal.bright.blue}",
|
||||
|
||||
"magenta": "${pal.normal.magenta}",
|
||||
"magenta_b": "${pal.bright.magenta}",
|
||||
|
||||
"cyan": "${pal.normal.cyan}",
|
||||
"cyan_b": "${pal.bright.cyan}",
|
||||
|
||||
"white": "${pal.normal.white}",
|
||||
"white_b": "${pal.bright.white}",
|
||||
|
||||
}
|
||||
}
|
||||
'';
|
||||
uid = 1000;
|
||||
gid = 100;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,59 @@
|
|||
}:
|
||||
let
|
||||
inherit (config) rice;
|
||||
inherit (inputs.niri.lib.kdl) leaf node flag serialize;
|
||||
niri = getFlakePkg' inputs.niri "niri-unstable";
|
||||
xwayland-satellite = getFlakePkg' inputs.niri "xwayland-satellite-unstable";
|
||||
|
||||
ui = rice.roles rice.palette.shortHex;
|
||||
|
||||
# serialize binds attrset to KDL
|
||||
serializeBind = key: bind:
|
||||
if bind ? action then
|
||||
leaf key { action = bind.action; }
|
||||
else if bind ? spawn then
|
||||
leaf key bind.spawn
|
||||
else
|
||||
flag key;
|
||||
|
||||
bindsKdl = serialize.nodes [
|
||||
(node "binds" [] (
|
||||
lib.mapAttrsToList serializeBind config.niri.binds
|
||||
))
|
||||
];
|
||||
|
||||
niriConfig =
|
||||
let
|
||||
template = builtins.readFile ./niri.kdl;
|
||||
baseConfig =
|
||||
with rice;
|
||||
lib.replaceStrings
|
||||
[
|
||||
"%CURSOR%"
|
||||
"%ROUNDING%"
|
||||
"%GAPS%"
|
||||
"%BORDER%"
|
||||
"%ACTIVE%"
|
||||
"%INACTIVE%"
|
||||
"%SATELLITE%"
|
||||
]
|
||||
[
|
||||
cursor.name
|
||||
(toString borders.rounding)
|
||||
(toString borders.gaps)
|
||||
(toString borders.thickness)
|
||||
ui.highlight
|
||||
ui.muted
|
||||
(lib.getExe xwayland-satellite)
|
||||
]
|
||||
template;
|
||||
in
|
||||
baseConfig + "\n" + bindsKdl + "\n" + config.niri.extraConfig;
|
||||
|
||||
niriConfigFile = pkgs.writeText "niri-config.kdl" niriConfig;
|
||||
|
||||
niri-session-direct = pkgs.writeShellScript "niri-session-direct" ''
|
||||
export NIRI_CONFIG="${niriConfigFile}"
|
||||
systemctl --user import-environment
|
||||
dbus-update-activation-environment --all
|
||||
systemctl --user start niri-session-bridge.service &
|
||||
|
|
@ -24,43 +74,25 @@ in
|
|||
inputs.niri-s76.nixosModules.default
|
||||
];
|
||||
|
||||
user.desktops.niri = {
|
||||
enable = true;
|
||||
config =
|
||||
let
|
||||
template = builtins.readFile ./niri.kdl;
|
||||
baseConfig =
|
||||
with rice;
|
||||
lib.replaceStrings
|
||||
[
|
||||
"%CURSOR%"
|
||||
"%ROUNDING%"
|
||||
"%GAPS%"
|
||||
"%BORDER%"
|
||||
"%ACTIVE%"
|
||||
"%INACTIVE%"
|
||||
"%SATELLITE%"
|
||||
]
|
||||
[
|
||||
cursor.name
|
||||
(toString borders.rounding)
|
||||
(toString borders.gaps)
|
||||
(toString borders.thickness)
|
||||
palette.shortHex.bright.yellow
|
||||
palette.shortHex.normal.white
|
||||
(lib.getExe xwayland-satellite)
|
||||
]
|
||||
template;
|
||||
in
|
||||
baseConfig;
|
||||
options.niri = {
|
||||
binds = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = { };
|
||||
description = "Niri keybindings";
|
||||
};
|
||||
extraConfig = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "Extra KDL config appended to niri config";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
config.environment.systemPackages = [
|
||||
niri
|
||||
xwayland-satellite
|
||||
];
|
||||
|
||||
environment.etc."greetd/wayland-sessions/niri.desktop".text = ''
|
||||
config.environment.etc."greetd/wayland-sessions/niri.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
Name=Niri
|
||||
Comment=A scrollable-tiling Wayland compositor
|
||||
|
|
@ -69,12 +101,12 @@ in
|
|||
DesktopNames=niri
|
||||
'';
|
||||
|
||||
programs.niri = {
|
||||
config.programs.niri = {
|
||||
enable = true;
|
||||
package = niri;
|
||||
};
|
||||
|
||||
services.niri-tag = {
|
||||
config.services.niri-tag = {
|
||||
enable = true;
|
||||
prepopulate = 10;
|
||||
strict = true;
|
||||
|
|
@ -84,16 +116,16 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
services.niri-s76-bridge.enable = true;
|
||||
config.services.niri-s76-bridge.enable = true;
|
||||
|
||||
# niri runs directly from greetd (not as a user service),
|
||||
# so that it stays inside the logind session scope for proper polkit/dbus access.
|
||||
systemd.user.services.niri.wantedBy = lib.mkForce [ ];
|
||||
systemd.user.services.niri.enable = lib.mkForce false;
|
||||
config.systemd.user.services.niri.wantedBy = lib.mkForce [ ];
|
||||
config.systemd.user.services.niri.enable = lib.mkForce false;
|
||||
|
||||
# bridge service to activate graphical-session.target for the direct-launched niri.
|
||||
# waits for niri IPC readiness before pulling in the target.
|
||||
systemd.user.services.niri-session-bridge = {
|
||||
config.systemd.user.services.niri-session-bridge = {
|
||||
unitConfig = {
|
||||
Description = "Activate graphical-session.target for direct-launched niri";
|
||||
BindsTo = [ "graphical-session.target" ];
|
||||
|
|
|
|||
64
graphical/desktop/wry.nix
Normal file
64
graphical/desktop/wry.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
lib,
|
||||
getFlakePkg,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wry = getFlakePkg inputs.wry;
|
||||
wry-session = pkgs.writeShellScript "wry-session" ''
|
||||
systemctl --user import-environment
|
||||
dbus-update-activation-environment --all
|
||||
systemctl --user start wry-session-bridge.service &
|
||||
exec ${lib.getExe wry} run
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.programs.wry = {
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = wry;
|
||||
};
|
||||
};
|
||||
config = {
|
||||
environment.systemPackages = [ wry ];
|
||||
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
settings.default_session.command = "${lib.getExe (getFlakePkg inputs.tuigreet)} --sessions /etc/greetd/wayland-sessions --remember-session";
|
||||
};
|
||||
|
||||
environment.etc."greetd/wayland-sessions/wry.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
Name=wry
|
||||
Exec=${wry-session}
|
||||
Type=Application
|
||||
DesktopNames=wry
|
||||
'';
|
||||
|
||||
# bridge service to activate graphical-session.target for direct-launched wry.
|
||||
# waits for wry IPC readiness before pulling in the target.
|
||||
systemd.user.services.wry-session-bridge = {
|
||||
unitConfig = {
|
||||
Description = "Activate graphical-session.target for direct-launched wry";
|
||||
BindsTo = [ "graphical-session.target" ];
|
||||
Before = [ "graphical-session.target" ];
|
||||
Wants = [ "graphical-session-pre.target" ];
|
||||
After = [ "graphical-session-pre.target" ];
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
# wry's IPC goes through the Wayland socket,
|
||||
# so we can't use a wry subcommand without WAYLAND_DISPLAY already set.
|
||||
# Wait for wry to create its wayland socket in XDG_RUNTIME_DIR instead.
|
||||
ExecStart = pkgs.writeShellScript "wry-ready" ''
|
||||
until find "$XDG_RUNTIME_DIR" -maxdepth 1 -name "wayland-*" ! -name "*.lock" -type s | grep -q .; do
|
||||
sleep 0.1
|
||||
done
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue