feat: add custom SDDM login theme
This commit is contained in:
parent
3f0609bcfc
commit
7ae4243548
4 changed files with 241 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
../../modules/desktop.nix
|
../../modules/desktop.nix
|
||||||
../../modules/hardware.nix
|
../../modules/hardware.nix
|
||||||
|
../../modules/login-theme.nix
|
||||||
../../modules/packages
|
../../modules/packages
|
||||||
../../modules/system.nix
|
../../modules/system.nix
|
||||||
../../modules/users.nix
|
../../modules/users.nix
|
||||||
|
|
|
||||||
26
nixos/modules/login-theme.nix
Normal file
26
nixos/modules/login-theme.nix
Normal file
|
|
@ -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 ];
|
||||||
|
}
|
||||||
205
sddm/Main.qml
Normal file
205
sddm/Main.qml
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
9
sddm/metadata.desktop
Normal file
9
sddm/metadata.desktop
Normal file
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue