From 1390140314cc8f5799014f69f4a6c514462d5ef9 Mon Sep 17 00:00:00 2001 From: "Wiktor A. Falkowski" Date: Thu, 15 Oct 2026 22:18:50 +0000 Subject: [PATCH] Add MIT license and configurable greeter palette --- LICENSE | 21 ++++++ colors.ini.example | 14 ++++ src/main.rs | 163 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 LICENSE create mode 100644 colors.ini.example diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e493b58 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Wiktor A. Falkowski + +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/colors.ini.example b/colors.ini.example new file mode 100644 index 0000000..af7e4dc --- /dev/null +++ b/colors.ini.example @@ -0,0 +1,14 @@ +[colors] +background = #151515 +panel = #181818 +surface = #202020 +hover = #282828 +border = #2F2F2F +focus = #454545 +text = #D0D0D0 +muted = #757575 +dim = #505050 +emphasis = #F5F5F5 +accent = #6C99BB +accent_secondary = #547C99 +warning = #D3A04D diff --git a/src/main.rs b/src/main.rs index 563bb69..77435a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::io::{Read, Write}; use std::os::unix::net::UnixStream; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; use std::time::Duration; @@ -87,6 +87,121 @@ struct Session { command: Vec, } +#[derive(Clone)] +struct Palette { + background: String, + panel: String, + surface: String, + hover: String, + border: String, + focus: String, + text: String, + muted: String, + dim: String, + emphasis: String, + accent: String, + accent_secondary: String, + warning: String, +} + +impl Default for Palette { + fn default() -> Self { + Self { + background: "#151515".into(), + panel: "#181818".into(), + surface: "#202020".into(), + hover: "#282828".into(), + border: "#2F2F2F".into(), + focus: "#454545".into(), + text: "#D0D0D0".into(), + muted: "#757575".into(), + dim: "#505050".into(), + emphasis: "#F5F5F5".into(), + accent: "#6C99BB".into(), + accent_secondary: "#547C99".into(), + warning: "#D3A04D".into(), + } + } +} + +impl Palette { + fn load() -> Self { + let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else { + eprintln!("HOME is unset; using the built-in greeter palette"); + return Self::default(); + }; + let path = home.join(".config/wayter/colors.ini"); + match fs::read_to_string(&path) { + Ok(contents) => Self::parse(&contents, &path), + Err(error) => { + eprintln!( + "Could not read {}: {error}; using the built-in palette", + path.display() + ); + Self::default() + } + } + } + + fn parse(contents: &str, path: &Path) -> Self { + let mut palette = Self::default(); + let mut in_colors = false; + for (index, raw_line) in contents.lines().enumerate() { + let line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') || line.starts_with(';') { + continue; + } + if line.starts_with('[') && line.ends_with(']') { + in_colors = line == "[colors]"; + continue; + } + if !in_colors { + continue; + } + let Some((key, value)) = line.split_once('=') else { + eprintln!("{}:{}: expected key = #RRGGBB", path.display(), index + 1); + continue; + }; + let key = key.trim(); + let value = value.trim(); + let Some(color) = Self::valid_color(value) else { + eprintln!( + "{}:{}: invalid color for {key}; keeping its default", + path.display(), + index + 1 + ); + continue; + }; + let target = match key { + "background" => &mut palette.background, + "panel" => &mut palette.panel, + "surface" => &mut palette.surface, + "hover" => &mut palette.hover, + "border" => &mut palette.border, + "focus" => &mut palette.focus, + "text" => &mut palette.text, + "muted" => &mut palette.muted, + "dim" => &mut palette.dim, + "emphasis" => &mut palette.emphasis, + "accent" => &mut palette.accent, + "accent_secondary" => &mut palette.accent_secondary, + "warning" => &mut palette.warning, + _ => continue, + }; + *target = color; + } + palette + } + + fn valid_color(value: &str) -> Option { + let hex = value.strip_prefix('#')?; + if hex.len() != 6 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) { + return None; + } + Some(format!("#{}", hex.to_ascii_uppercase())) + } +} + fn main() { let app = gtk::Application::builder() .application_id("dev.wfalkowski.WaylandGreeter") @@ -536,21 +651,39 @@ fn split_exec(input: &str) -> Vec { } fn install_style() { - let css = r#" - .backdrop { background: #151515; color: #d0d0d0; font-family: "Maple Mono Normal"; font-size: 9pt; } - .card { background: #151515; border: 1px solid #505050; border-radius: 1px; padding: 18px; } - .field-label { color: #505050; font-size: 9pt; } - entry, combobox button { color: #d0d0d0; background: #151515; border: 1px solid #505050; border-radius: 1px; min-height: 36px; padding: 0 10px; caret-color: #f5f5f5; } - entry:focus, combobox button:focus { border-color: #6c99bb; } - entry placeholder { color: #505050; } - button.login-button { color: #d0d0d0; background: #151515; border: 1px solid #505050; border-radius: 1px; min-height: 36px; margin-top: 2px; } - button.login-button:hover { color: #f5f5f5; background: #547c99; } - button.login-button:focus { border-color: #6c99bb; } - button.login-button:disabled { color: #505050; background: #151515; border-color: #505050; } - .status { color: #d3a04d; font-size: 9pt; } - "#; + let palette = Palette::load(); + let css = format!( + r#" + .backdrop {{ background: {background}; color: {text}; font-family: "Maple Mono Normal"; font-size: 9pt; }} + .card {{ background: {panel}; border: 1px solid {border}; border-radius: 1px; padding: 18px; }} + .field-label {{ color: {muted}; font-size: 9pt; }} + entry, combobox button {{ color: {text}; background: {surface}; border: 1px solid {border}; border-radius: 1px; min-height: 36px; padding: 0 10px; caret-color: {emphasis}; }} + entry:focus, combobox button:focus {{ border-color: {accent}; }} + entry placeholder {{ color: {dim}; }} + button.login-button {{ color: {text}; background: {surface}; border: 1px solid {border}; border-radius: 1px; min-height: 36px; margin-top: 2px; }} + button.login-button:hover {{ color: {emphasis}; background: {accent_secondary}; }} + button.login-button:focus {{ border-color: {focus}; }} + button.login-button:disabled {{ color: {dim}; background: {panel}; border-color: {border}; }} + .status {{ color: {warning}; font-size: 9pt; }} + .field-label:disabled {{ color: {dim}; }} + entry:selected, combobox button:selected {{ background: {hover}; }} + "#, + background = palette.background, + panel = palette.panel, + surface = palette.surface, + hover = palette.hover, + border = palette.border, + focus = palette.focus, + text = palette.text, + muted = palette.muted, + dim = palette.dim, + emphasis = palette.emphasis, + accent = palette.accent, + accent_secondary = palette.accent_secondary, + warning = palette.warning, + ); let provider = gtk::CssProvider::new(); - provider.load_from_data(css); + provider.load_from_data(&css); if let Some(display) = gtk::gdk::Display::default() { gtk::style_context_add_provider_for_display( &display,