From 7ae4243548f1f9a6711e1bbf56ab98332bceed2b Mon Sep 17 00:00:00 2001 From: hhhh hgfh Date: Mon, 5 Oct 2026 00:37:52 +0300 Subject: [PATCH] feat: add custom SDDM login theme --- nixos/hosts/nixos/default.nix | 1 + nixos/modules/login-theme.nix | 26 +++++ sddm/Main.qml | 205 ++++++++++++++++++++++++++++++++++ sddm/metadata.desktop | 9 ++ 4 files changed, 241 insertions(+) create mode 100644 nixos/modules/login-theme.nix create mode 100644 sddm/Main.qml create mode 100644 sddm/metadata.desktop diff --git a/nixos/hosts/nixos/default.nix b/nixos/hosts/nixos/default.nix index 018380c..d70a2f2 100644 --- a/nixos/hosts/nixos/default.nix +++ b/nixos/hosts/nixos/default.nix @@ -5,6 +5,7 @@ ./hardware-configuration.nix ../../modules/desktop.nix ../../modules/hardware.nix + ../../modules/login-theme.nix ../../modules/packages ../../modules/system.nix ../../modules/users.nix diff --git a/nixos/modules/login-theme.nix b/nixos/modules/login-theme.nix new file mode 100644 index 0000000..c7a696f --- /dev/null +++ b/nixos/modules/login-theme.nix @@ -0,0 +1,26 @@ +{ pkgs, ... }: + +let + theme = pkgs.stdenvNoCC.mkDerivation { + pname = "sddm-custom-environment"; + version = "1.0"; + src = ../../sddm; + dontBuild = true; + installPhase = '' + runHook preInstall + themeDir="$out/share/sddm/themes/custom-environment" + mkdir -p "$themeDir" + cp Main.qml metadata.desktop "$themeDir/" + cp ${../../wallpapers/night_city.jpg} "$themeDir/background.jpg" + cp ${../../wallpapers/kotost.png} "$themeDir/avatar.png" + cp ${pkgs.nerd-fonts.profont}/share/fonts/truetype/NerdFonts/ProFont/ProFontWindowsNerdFontMono-Regular.ttf "$themeDir/font.ttf" + runHook postInstall + ''; + }; +in +{ + system.build.sddmTheme = theme; + environment.systemPackages = [ theme ]; + services.displayManager.sddm.theme = "custom-environment"; + services.displayManager.sddm.extraPackages = [ pkgs.qt6.qtimageformats ]; +} diff --git a/sddm/Main.qml b/sddm/Main.qml new file mode 100644 index 0000000..c3348c3 --- /dev/null +++ b/sddm/Main.qml @@ -0,0 +1,205 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects + +Rectangle { + id: root + width: 1920 + height: 1080 + color: "#223638" + property date now: new Date() + property bool busy: false + property string message: "" + readonly property color cream: "#eee8dd" + readonly property color muted: "#bcb5ab" + readonly property color red: "#bc3b3b" + readonly property color dark: "#1c1717" + + FontLoader { id: face; source: "font.ttf" } + Timer { interval: 1000; running: true; repeat: true; onTriggered: root.now = new Date() } + + function login() { + if (busy) return + if (!username.text.trim() || session.count === 0) { + message = "Enter a username and select a session." + return + } + message = "" + busy = true + sddm.login(username.text.trim(), password.text, session.currentIndex) + } + + Connections { + target: sddm + function onLoginFailed() { + root.busy = false + root.message = "Login failed. Please try again." + password.clear() + password.forceActiveFocus() + } + function onLoginSucceeded() { root.message = "Welcome back!" } + function onInformationMessage(message) { root.message = message } + } + + Image { + id: wallpaper + anchors.fill: parent + source: "background.jpg" + fillMode: Image.PreserveAspectCrop + visible: false + } + MultiEffect { + anchors.fill: parent + source: wallpaper + blurEnabled: true + blurMax: 64 + blur: 1.0 + brightness: -0.2 + } + Rectangle { anchors.fill: parent; color: "#30223638" } + + component Card: Rectangle { + radius: 25 + color: "#991c2827" + } + component LabelText: Text { + color: root.cream + font.family: face.name + font.pixelSize: 20 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + component Action: Button { + id: action + property color fill: "#cda67d" + font.family: face.name + font.pixelSize: 18 + background: Rectangle { + radius: 20 + color: action.down ? Qt.darker(action.fill, 1.2) : action.fill + border.width: action.activeFocus ? 2 : 0 + border.color: root.cream + opacity: action.enabled ? 1 : 0.5 + } + contentItem: Text { + text: action.text + font: action.font + color: root.dark + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Item { + width: 760 + height: 650 + anchors.centerIn: parent + scale: Math.min(1, root.width / 900, root.height / 780) + + Card { + width: 360; height: 380 + LabelText { x: 20; y: 32; width: 320; text: "NixOS · " + sddm.hostName; color: root.muted } + LabelText { x: 20; y: 76; width: 320; text: "Log in"; font.pixelSize: 56 } + + Rectangle { + x: 20; y: 185; width: 320; height: 70; radius: 20; color: root.red + LabelText { x: 18; y: 6; text: "Username"; color: root.dark; font.pixelSize: 14 } + TextField { + id: username + x: 10; y: 25; width: 300; height: 40 + text: userModel.lastUser + color: root.dark + font.family: face.name; font.pixelSize: 28 + selectByMouse: true + enabled: !root.busy + background: Item {} + onAccepted: password.forceActiveFocus() + KeyNavigation.tab: password + } + } + Rectangle { + x: 20; y: 275; width: 320; height: 70; radius: 20; color: root.red + LabelText { x: 18; y: 6; text: "Password"; color: root.dark; font.pixelSize: 14 } + TextField { + id: password + x: 10; y: 25; width: 300; height: 40 + color: root.dark + font.family: face.name; font.pixelSize: 28 + echoMode: TextInput.Password + selectByMouse: true + enabled: !root.busy + background: Item {} + onAccepted: root.login() + KeyNavigation.tab: session + KeyNavigation.backtab: username + } + } + } + + Card { + y: 410; width: 360; height: 160 + LabelText { y: 4; width: 360; height: 116; text: Qt.formatDateTime(root.now, "HH:mm"); font.pixelSize: 96 } + LabelText { y: 122; width: 360; text: "Welcome back"; color: root.muted; font.pixelSize: 18 } + } + + Card { + x: 400; width: 360; height: 650 + Rectangle { + x: 116; y: 26; width: 128; height: 128 + radius: 24; color: root.cream; border.width: 4; border.color: root.red + Image { anchors.fill: parent; anchors.margins: 6; source: "avatar.png"; fillMode: Image.PreserveAspectFit } + } + LabelText { y: 168; width: 360; text: Qt.formatDateTime(root.now, "ddd").toUpperCase(); font.pixelSize: 56; font.bold: true } + LabelText { y: 236; width: 360; text: Qt.formatDateTime(root.now, "dd MMMM, yyyy"); color: root.muted } + } + + Card { + x: 400; y: 330; width: 360; height: 320; color: "#f01c1717" + LabelText { y: 16; width: 360; text: "Desktop session"; color: root.muted; font.pixelSize: 16 } + ComboBox { + id: session + x: 20; y: 48; width: 320; height: 42 + model: sessionModel + textRole: "name" + currentIndex: Math.max(0, sessionModel.lastIndex) + enabled: !root.busy + font.family: face.name; font.pixelSize: 18 + palette.button: "#332a29" + palette.buttonText: root.cream + palette.text: root.cream + palette.base: root.dark + palette.highlight: root.red + KeyNavigation.tab: loginButton + KeyNavigation.backtab: password + } + Row { + x: 20; y: 112; spacing: 20 + Action { width: 150; height: 75; text: "Restart"; enabled: sddm.canReboot && !root.busy; onClicked: sddm.reboot() } + Action { width: 150; height: 75; text: "Power off"; enabled: sddm.canPowerOff && !root.busy; onClicked: sddm.powerOff() } + } + Action { + id: loginButton + x: 20; y: 205; width: 320; height: 70 + fill: root.red + text: root.busy ? "Logging in…" : "Log in" + font.pixelSize: 30 + enabled: !root.busy && session.count > 0 + onClicked: root.login() + KeyNavigation.tab: username + KeyNavigation.backtab: session + } + LabelText { + x: 12; y: 282; width: 336; height: 32 + text: root.message + font.pixelSize: 12 + color: root.cream + wrapMode: Text.WordWrap + } + } + } + + Component.onCompleted: { + if (username.text) password.forceActiveFocus() + else username.forceActiveFocus() + } +} diff --git a/sddm/metadata.desktop b/sddm/metadata.desktop new file mode 100644 index 0000000..e96539b --- /dev/null +++ b/sddm/metadata.desktop @@ -0,0 +1,9 @@ +[SddmGreeterTheme] +Name=Custom environment +Description=Two-column login screen with ProFont and translucent cards +Type=sddm-theme +Version=1.0 +MainScript=Main.qml +Theme-Id=custom-environment +Theme-API=2.0 +QtVersion=6