Initial commit — TANINUX (camel): management app + distro packaging
- app: GTK System Settings (tsettings) + Software Hub (thub) + TUI - distro/: camel.toml manifest + MANIFEST.md (Arch + [tanin] repo model) - packaging/: taninux, tanin-desktop (niri metapackage), tanin-greet, tanin-libadwaita, tanin-setup - docs/, data/, LICENSE (GPL-3.0-or-later) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# Maintainer: Karim <karim@gabrielevarano.ch>
|
||||
#
|
||||
# Metapackage: pulls the complete TANINUX (camel) desktop — niri + eww, managed
|
||||
# by the TANINUX apps. Own packages come from the [tanin] repo, everything else
|
||||
# from the official Arch repos. AUR-only runtime deps (eww, tiramisu, waypaper,
|
||||
# cliphist) are rebuilt into [tanin] so this never hard-depends on the AUR.
|
||||
# Calamares just installs this package at the end.
|
||||
pkgname=tanin-desktop
|
||||
pkgver=0.2.0
|
||||
pkgrel=1
|
||||
pkgdesc="TANINUX desktop — metapackage pulling the full niri/eww environment"
|
||||
arch=('any')
|
||||
url="https://taninux.kgva.ch"
|
||||
license=('GPL-3.0-or-later')
|
||||
|
||||
depends=(
|
||||
# --- own packages ([tanin] repo) ---
|
||||
'taninux' # System Settings + Software Hub + TUI
|
||||
'tanin-greet' # greetd greeter (GTK4, Fuji topbar)
|
||||
'tanin-libadwaita' # Fuji recolour for GTK4/libadwaita + GTK3
|
||||
'tanin-eww' # eww bar/dock/panels + niri config -> /usr/share/tanin/skel
|
||||
'tanin-setup' # seeds ~/.config + session portal on first run
|
||||
|
||||
# --- compositor + login ---
|
||||
'niri' # Wayland compositor (niri-only)
|
||||
'greetd' # display manager (greeter = tanin-greet)
|
||||
'cage' # kiosk compositor hosting the greeter
|
||||
|
||||
# --- session portals ---
|
||||
'xdg-desktop-portal'
|
||||
'xdg-desktop-portal-gtk' # Settings/Appearance portal (dark mode!)
|
||||
'xdg-desktop-portal-gnome' # screencast backend recommended for niri
|
||||
|
||||
# --- shell / eww stack (eww, tiramisu, waypaper, cliphist via [tanin]) ---
|
||||
'eww' # bar/dock/panels/spotlight
|
||||
'tiramisu' # headless notification daemon -> eww UI
|
||||
'waypaper' # wallpaper (niri autostart: waypaper --restore)
|
||||
'cliphist' # clipboard history for Spotlight
|
||||
|
||||
# --- terminal + CLI used by the bar/scripts ---
|
||||
'foot' 'jq' 'wl-clipboard' 'xdg-utils' 'libqalculate' # libqalculate -> qalc
|
||||
'grim' 'slurp' # screenshots
|
||||
|
||||
# --- connectivity / audio / hardware ---
|
||||
'networkmanager' 'bluez' 'bluez-utils'
|
||||
'pipewire' 'pipewire-pulse' 'wireplumber' # wpctl / pactl
|
||||
'brightnessctl' 'playerctl'
|
||||
|
||||
# --- packages / updates ---
|
||||
'flatpak' 'pacman-contrib' # checkupdates / paccache
|
||||
|
||||
# --- theming / fonts ---
|
||||
'adw-gtk3' # GTK3 base theme tanin-libadwaita recolours
|
||||
'adwaita-icon-theme' 'papirus-icon-theme'
|
||||
'inter-font' 'ttf-iosevka-nerd' # UI font + nf-md-* glyphs (verify pkg name)
|
||||
)
|
||||
|
||||
optdepends=(
|
||||
'paru: AUR support in the Software Hub (opt-in)'
|
||||
'wlsunset: night light'
|
||||
'cups: printer management'
|
||||
'timeshift: system snapshots/backups'
|
||||
'waybar: fallback bar if eww is missing (launch.sh falls back to it)'
|
||||
)
|
||||
|
||||
# Metapackage — no files of its own.
|
||||
package() {
|
||||
:
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Maintainer: Karim <karim@gabrielevarano.ch>
|
||||
#
|
||||
# tanin-greet — TANINUX custom GTK4 greeter for greetd. Black top bar like the
|
||||
# eww topbar, wallpaper background, user+password centered, session in a corner.
|
||||
# After install: systemctl enable greetd (disable any other DM first)
|
||||
pkgname=tanin-greet
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="TANINUX login — custom GTK4 greeter for greetd (mirrors the topbar)"
|
||||
arch=('any')
|
||||
url="https://taninux.kgva.ch"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('greetd' 'cage' 'gtk4' 'python' 'python-gobject'
|
||||
'adwaita-icon-theme' 'inter-font')
|
||||
optdepends=('tanin-backgrounds: default login wallpaper at /usr/share/backgrounds/tanin/login.jpg')
|
||||
backup=('etc/greetd/config.toml' 'etc/greetd/tanin-greet.css')
|
||||
|
||||
package() {
|
||||
install -Dm755 "$startdir/tanin-greet" "$pkgdir/usr/bin/tanin-greet"
|
||||
install -Dm644 "$startdir/tanin-greet.css" "$pkgdir/etc/greetd/tanin-greet.css"
|
||||
install -Dm644 "$startdir/config.toml" "$pkgdir/etc/greetd/config.toml"
|
||||
# The greeter reads its CSS from /etc/greetd by default; no wallpaper is
|
||||
# shipped here (provide one at /usr/share/backgrounds/tanin/login.jpg, e.g.
|
||||
# via a tanin-backgrounds package — the greeter falls back to a dark scrim).
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# tanin-greet
|
||||
|
||||
TANINUX's custom **GTK4 greeter** for greetd. Built because ReGreet's layout is
|
||||
fixed — this gives full control to match the design:
|
||||
|
||||
- a **black top bar** like the eww topbar (`TANINUX` · clock · power)
|
||||
- a **wallpaper** background under a dark scrim
|
||||
- **user + password** centered as pills (Fuji violet focus ring, Enter submits)
|
||||
- the **session selector** tucked tiny in the **bottom-right corner**
|
||||
|
||||
It speaks the greetd IPC directly (create_session → auth → start_session) and is
|
||||
hosted by `cage`.
|
||||
|
||||
## Files
|
||||
- `tanin-greet` → `/usr/bin/tanin-greet` — the GTK4 app (PyGObject)
|
||||
- `tanin-greet.css` → `/etc/greetd/tanin-greet.css` — the look (mirrors `eww.scss`)
|
||||
- `config.toml` → `/etc/greetd/config.toml` — greetd; `cage -s -- tanin-greet`
|
||||
- `dev/` → preview helper + greetd mock (not packaged)
|
||||
|
||||
## Preview (no logout, no real auth)
|
||||
```sh
|
||||
./dev/preview.sh # uses your current wallpaper path
|
||||
./dev/preview.sh /path/to/wall.jpg
|
||||
# screenshot lands at /tmp/tanin-greet.png ; override output with GREET_OUTPUT=HDMI-A-1
|
||||
```
|
||||
The greeter renders even without greetd; `dev/mockgreetd.py` provides a fake
|
||||
socket so the password prompt and login flow work for a full dry-run.
|
||||
|
||||
## Env overrides (used by the preview)
|
||||
- `TANIN_GREET_CSS` stylesheet path (default `/etc/greetd/tanin-greet.css`)
|
||||
- `TANIN_GREET_BG` wallpaper path (default `/usr/share/backgrounds/tanin/login.jpg`)
|
||||
- `TANIN_GREET_SESSIONS` sessions dir (default `/usr/share/wayland-sessions`)
|
||||
- `TANIN_GREET_BRAND` top-bar text (default `TANINUX`)
|
||||
|
||||
## Install (on the distro)
|
||||
```sh
|
||||
makepkg -si
|
||||
sudo systemctl enable greetd # disable any other DM first
|
||||
```
|
||||
Drop a login wallpaper at `/usr/share/backgrounds/tanin/login.jpg` (or ship a
|
||||
`tanin-backgrounds` package). Without one, the greeter shows a dark scrim.
|
||||
|
||||
## Notes
|
||||
- Sessions are read from `/usr/share/wayland-sessions/*.desktop`; Niri is the
|
||||
default selection. Users are read from `/etc/passwd` (uid 1000–65533, real shell).
|
||||
- Icons (`avatar-default-symbolic`, `system-shutdown-symbolic`) come from
|
||||
`adwaita-icon-theme`.
|
||||
@@ -0,0 +1,8 @@
|
||||
# greetd config for tanin-greet (TANINUX custom GTK4 greeter).
|
||||
# The greeter is a GTK app, so it's hosted by `cage` (one-window kiosk compositor).
|
||||
[terminal]
|
||||
vt = 1
|
||||
|
||||
[default_session]
|
||||
command = "cage -s -- tanin-greet"
|
||||
user = "greeter"
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
# Minimal greetd IPC mock — just enough for tanin-greet to render and dry-run a
|
||||
# login WITHOUT real auth. greetd framing: native-endian u32 length + JSON.
|
||||
# Usage: python3 mockgreetd.py /tmp/mock-greetd.sock
|
||||
import json, os, socket, struct, sys
|
||||
|
||||
SOCK = sys.argv[1] if len(sys.argv) > 1 else "/tmp/mock-greetd.sock"
|
||||
try:
|
||||
os.unlink(SOCK)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
srv.bind(SOCK)
|
||||
srv.listen(8)
|
||||
print(f"mock greetd listening on {SOCK}", flush=True)
|
||||
|
||||
|
||||
def recv(conn):
|
||||
hdr = conn.recv(4)
|
||||
if len(hdr) < 4:
|
||||
return None
|
||||
n = struct.unpack("=I", hdr)[0]
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = conn.recv(n - len(buf))
|
||||
if not chunk:
|
||||
break
|
||||
buf += chunk
|
||||
return json.loads(buf)
|
||||
|
||||
|
||||
def send(conn, obj):
|
||||
data = json.dumps(obj).encode()
|
||||
conn.sendall(struct.pack("=I", len(data)) + data)
|
||||
|
||||
|
||||
while True:
|
||||
conn, _ = srv.accept()
|
||||
try:
|
||||
while True:
|
||||
req = recv(conn)
|
||||
if req is None:
|
||||
break
|
||||
if req.get("type") == "create_session":
|
||||
send(conn, {"type": "auth_message",
|
||||
"auth_message_type": "secret",
|
||||
"auth_message": "Password:"})
|
||||
else: # post_auth_message_response / start_session / cancel_session
|
||||
send(conn, {"type": "success"})
|
||||
except Exception as e: # noqa: BLE001
|
||||
print("conn error:", e, flush=True)
|
||||
finally:
|
||||
conn.close()
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Preview tanin-greet from the repo in a nested cage window (no logout, no real
|
||||
# auth) and screenshot it. Uses the mock greetd socket so the UI fully renders.
|
||||
# ./dev/preview.sh [wallpaper.jpg]
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SOCK=/tmp/mock-greetd.sock
|
||||
SHOT=/tmp/tanin-greet.png
|
||||
|
||||
export GREETD_SOCK="$SOCK"
|
||||
export TANIN_GREET_CSS="$HERE/tanin-greet.css"
|
||||
export TANIN_GREET_BG="${1:-$HOME/Downloads/karen-z-OW5F_DMfb0E-unsplash.jpg}"
|
||||
|
||||
python3 "$HERE/dev/mockgreetd.py" "$SOCK" >/tmp/mockgreetd.log 2>&1 &
|
||||
MOCK=$!
|
||||
trap 'kill "$MOCK" 2>/dev/null; pkill -x cage 2>/dev/null' EXIT
|
||||
sleep 0.4
|
||||
|
||||
timeout 8 cage -s -- python3 "$HERE/tanin-greet" >/tmp/tanin-greet.log 2>&1 &
|
||||
sleep 4.5
|
||||
grim -o "${GREET_OUTPUT:-DP-3}" "$SHOT" && echo "screenshot: $SHOT"
|
||||
pkill -x cage 2>/dev/null || true
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install tanin-greet as the greetd greeter on THIS machine. Run with sudo:
|
||||
# sudo bash packaging/tanin-greet/install-local.sh
|
||||
# Reversible: the current greetd config is backed up first.
|
||||
set -euo pipefail
|
||||
SRC="$(cd "$(dirname "$0")" && pwd)"
|
||||
ts="$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
# 1) greeter binary + stylesheet
|
||||
install -Dm755 "$SRC/tanin-greet" /usr/bin/tanin-greet
|
||||
install -Dm644 "$SRC/tanin-greet.css" /etc/greetd/tanin-greet.css
|
||||
|
||||
# 2) optional login wallpaper (greeter falls back to a dark scrim if absent)
|
||||
if [ -f "${TANIN_LOGIN_BG:-}" ]; then
|
||||
install -Dm644 "$TANIN_LOGIN_BG" /usr/share/backgrounds/tanin/login.jpg
|
||||
fi
|
||||
|
||||
# 3) point greetd at tanin-greet (back up the working config first)
|
||||
[ -f /etc/greetd/config.toml ] && cp -a /etc/greetd/config.toml "/etc/greetd/config.toml.bak-$ts"
|
||||
install -Dm644 "$SRC/config.toml" /etc/greetd/config.toml
|
||||
|
||||
cat <<EOF
|
||||
tanin-greet installed.
|
||||
greeter: /usr/bin/tanin-greet
|
||||
css: /etc/greetd/tanin-greet.css
|
||||
greetd: /etc/greetd/config.toml -> cage -s -- tanin-greet
|
||||
backup: /etc/greetd/config.toml.bak-$ts
|
||||
|
||||
greetd is already enabled — it shows on the NEXT login. To see it now: log out
|
||||
(or 'sudo systemctl restart greetd' from a TTY — Ctrl+Alt+F2).
|
||||
If anything is wrong, switch to a TTY and revert:
|
||||
sudo cp /etc/greetd/config.toml.bak-$ts /etc/greetd/config.toml
|
||||
EOF
|
||||
Executable
+328
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
"""tanin-greet — TANINUX login greeter (GTK4 / PyGObject).
|
||||
|
||||
Custom layout: a black top bar like the eww topbar (brand · clock · power), a
|
||||
wallpaper background, user + password centered as pills, and a small session
|
||||
selector tucked in the bottom-right corner. Talks to greetd over GREETD_SOCK.
|
||||
|
||||
Run by greetd via cage: cage -s -- tanin-greet
|
||||
Preview without greetd: just run it — the UI renders; only login needs the
|
||||
socket (set GREETD_SOCK via a mock for a full dry-run).
|
||||
|
||||
Env overrides (handy for previewing from the repo):
|
||||
TANIN_GREET_CSS path to the stylesheet (default /etc/greetd/tanin-greet.css)
|
||||
TANIN_GREET_BG wallpaper image path (default /usr/share/backgrounds/tanin/login.jpg)
|
||||
TANIN_GREET_SESSIONS wayland-sessions dir (default /usr/share/wayland-sessions)
|
||||
"""
|
||||
import gi, os, sys, json, socket, struct, shlex, glob, pwd
|
||||
|
||||
gi.require_version("Gtk", "4.0")
|
||||
from gi.repository import Gtk, GLib, Gdk, Gio # noqa: E402
|
||||
|
||||
CSS_PATH = os.environ.get("TANIN_GREET_CSS", "/etc/greetd/tanin-greet.css")
|
||||
WALLPAPER = os.environ.get("TANIN_GREET_BG", "/usr/share/backgrounds/tanin/login.jpg")
|
||||
SESSION_DIR = os.environ.get("TANIN_GREET_SESSIONS", "/usr/share/wayland-sessions")
|
||||
BRAND = os.environ.get("TANIN_GREET_BRAND", "TANINUX")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# greetd IPC client — native-endian u32 length prefix + JSON payload.
|
||||
# --------------------------------------------------------------------------- #
|
||||
class Greetd:
|
||||
def __init__(self):
|
||||
path = os.environ.get("GREETD_SOCK")
|
||||
self.sock = None
|
||||
if path:
|
||||
try:
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.connect(path)
|
||||
except OSError:
|
||||
# No reachable greetd (e.g. preview without a daemon). Render the
|
||||
# UI anyway; login() will report the missing socket on submit.
|
||||
self.sock = None
|
||||
|
||||
def _recvn(self, n):
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = self.sock.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise RuntimeError("greetd closed the connection")
|
||||
buf += chunk
|
||||
return buf
|
||||
|
||||
def _send(self, obj):
|
||||
data = json.dumps(obj).encode()
|
||||
self.sock.sendall(struct.pack("=I", len(data)) + data)
|
||||
|
||||
def _recv(self):
|
||||
n = struct.unpack("=I", self._recvn(4))[0]
|
||||
return json.loads(self._recvn(n))
|
||||
|
||||
def login(self, username, password, cmd_argv):
|
||||
"""Authenticate and start the session. Raises on failure."""
|
||||
if self.sock is None:
|
||||
raise RuntimeError("GREETD_SOCK not set (preview mode — cannot log in)")
|
||||
self._send({"type": "create_session", "username": username})
|
||||
while True:
|
||||
r = self._recv()
|
||||
t = r.get("type")
|
||||
if t == "auth_message":
|
||||
amt = r.get("auth_message_type")
|
||||
if amt in ("secret", "visible"):
|
||||
self._send({"type": "post_auth_message_response", "response": password})
|
||||
else: # info / error — acknowledge with no response
|
||||
self._send({"type": "post_auth_message_response"})
|
||||
elif t == "success":
|
||||
break
|
||||
elif t == "error":
|
||||
self._send({"type": "cancel_session"})
|
||||
raise RuntimeError(r.get("description", "authentication failed"))
|
||||
else:
|
||||
raise RuntimeError(f"unexpected greetd reply: {r}")
|
||||
# authenticated → launch the chosen session
|
||||
self._send({"type": "start_session", "cmd": cmd_argv, "env": []})
|
||||
r = self._recv()
|
||||
if r.get("type") != "success":
|
||||
self._send({"type": "cancel_session"})
|
||||
raise RuntimeError(r.get("description", "failed to start session"))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# System enumeration
|
||||
# --------------------------------------------------------------------------- #
|
||||
def list_users():
|
||||
users = []
|
||||
for p in pwd.getpwall():
|
||||
if 1000 <= p.pw_uid < 65534 and p.pw_shell not in ("/usr/bin/nologin", "/sbin/nologin", "/bin/false"):
|
||||
users.append(p.pw_name)
|
||||
return users or [os.environ.get("USER", "user")]
|
||||
|
||||
|
||||
def list_sessions():
|
||||
sessions = [] # (name, exec_argv)
|
||||
for f in sorted(glob.glob(os.path.join(SESSION_DIR, "*.desktop"))):
|
||||
name = exec_ = None
|
||||
try:
|
||||
for line in open(f, encoding="utf-8"):
|
||||
if line.startswith("Name=") and name is None:
|
||||
name = line[5:].strip()
|
||||
elif line.startswith("Exec=") and exec_ is None:
|
||||
exec_ = line[5:].strip()
|
||||
except OSError:
|
||||
continue
|
||||
if name and exec_:
|
||||
sessions.append((name, shlex.split(exec_)))
|
||||
return sessions or [("Niri", ["niri-session"])]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# UI
|
||||
# --------------------------------------------------------------------------- #
|
||||
class Greeter(Gtk.Application):
|
||||
def __init__(self):
|
||||
super().__init__(application_id="ch.gabrielevarano.TaninGreet")
|
||||
self.greetd = Greetd()
|
||||
self.users = list_users()
|
||||
self.sessions = list_sessions()
|
||||
|
||||
def do_activate(self):
|
||||
self._load_css()
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
|
||||
|
||||
win = Gtk.ApplicationWindow(application=self)
|
||||
win.set_decorated(False)
|
||||
win.fullscreen()
|
||||
win.add_css_class("greeter")
|
||||
|
||||
overlay = Gtk.Overlay()
|
||||
win.set_child(overlay)
|
||||
|
||||
# background image (falls back to the scrim colour if missing)
|
||||
if os.path.exists(WALLPAPER):
|
||||
bg = Gtk.Picture.new_for_filename(WALLPAPER)
|
||||
bg.set_content_fit(Gtk.ContentFit.COVER)
|
||||
overlay.set_child(bg)
|
||||
else:
|
||||
overlay.set_child(Gtk.Box())
|
||||
|
||||
# dark scrim so text stays readable over any wallpaper
|
||||
scrim = Gtk.Box()
|
||||
scrim.add_css_class("scrim")
|
||||
scrim.set_hexpand(True)
|
||||
scrim.set_vexpand(True)
|
||||
overlay.add_overlay(scrim)
|
||||
|
||||
# main column: top bar + centered login
|
||||
col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
col.append(self._topbar())
|
||||
center = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
center.set_vexpand(True)
|
||||
center.set_valign(Gtk.Align.CENTER)
|
||||
center.set_halign(Gtk.Align.CENTER)
|
||||
center.append(self._login_card())
|
||||
col.append(center)
|
||||
col.append(self._footer()) # TANINUX wordmark, bottom center
|
||||
overlay.add_overlay(col)
|
||||
|
||||
win.present()
|
||||
self.password.grab_focus()
|
||||
|
||||
# debug: auto-open the session dropdown so previews can screenshot the
|
||||
# popover without a click (TANIN_GREET_POPUP=1). No effect in production.
|
||||
if os.environ.get("TANIN_GREET_POPUP"):
|
||||
def _open():
|
||||
btn = self.session_dd.get_first_child()
|
||||
try:
|
||||
btn.set_active(True)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return False
|
||||
GLib.timeout_add(900, _open)
|
||||
|
||||
# ---- pieces ----------------------------------------------------------- #
|
||||
def _topbar(self):
|
||||
bar = Gtk.CenterBox()
|
||||
bar.add_css_class("topbar")
|
||||
|
||||
# top-left corner: session dropdown
|
||||
bar.set_start_widget(self._session_dd())
|
||||
|
||||
# center: clock — time · weekday, date
|
||||
self.clock = Gtk.Label()
|
||||
self.clock.add_css_class("clock")
|
||||
bar.set_center_widget(self.clock)
|
||||
self._tick()
|
||||
GLib.timeout_add_seconds(1, self._tick)
|
||||
|
||||
# top-right corner: power
|
||||
bar.set_end_widget(self._power_button())
|
||||
return bar
|
||||
|
||||
def _session_dd(self):
|
||||
self.session_dd = Gtk.DropDown.new_from_strings([s[0] for s in self.sessions])
|
||||
self.session_dd.add_css_class("session-dd")
|
||||
for i, (name, _) in enumerate(self.sessions):
|
||||
if name.lower().startswith("niri"):
|
||||
self.session_dd.set_selected(i)
|
||||
break
|
||||
return self.session_dd
|
||||
|
||||
def _footer(self):
|
||||
box = Gtk.Box()
|
||||
box.set_halign(Gtk.Align.CENTER)
|
||||
box.set_valign(Gtk.Align.END)
|
||||
box.set_margin_bottom(18)
|
||||
lbl = Gtk.Label(label=BRAND)
|
||||
lbl.add_css_class("brand")
|
||||
box.append(lbl)
|
||||
return box
|
||||
|
||||
def _power_button(self):
|
||||
btn = Gtk.MenuButton()
|
||||
btn.add_css_class("power")
|
||||
btn.set_icon_name("system-shutdown-symbolic")
|
||||
pop = Gtk.Popover()
|
||||
pop.add_css_class("menu")
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
for label, argv in (("Reboot", ["systemctl", "reboot"]),
|
||||
("Power Off", ["systemctl", "poweroff"])):
|
||||
b = Gtk.Button(label=label)
|
||||
b.add_css_class("menuitem")
|
||||
b.connect("clicked", lambda _w, a=argv: self._run(a))
|
||||
box.append(b)
|
||||
pop.set_child(box)
|
||||
btn.set_popover(pop)
|
||||
return btn
|
||||
|
||||
def _login_card(self):
|
||||
card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=14)
|
||||
card.add_css_class("logincard") # NOT "card" — libadwaita's .card adds a bg+shadow
|
||||
card.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
avatar = Gtk.Image.new_from_icon_name("avatar-default-symbolic")
|
||||
avatar.set_pixel_size(64)
|
||||
avatar.add_css_class("avatar")
|
||||
avatar.set_halign(Gtk.Align.CENTER)
|
||||
card.append(avatar)
|
||||
|
||||
# user row — just the NAME as text, with a small dropdown chevron on the
|
||||
# right edge (aligned with where the password pill ends). Not a pill.
|
||||
name_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
name_row.add_css_class("namerow")
|
||||
name_row.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
self.name_label = Gtk.Label(label=self.users[0])
|
||||
self.name_label.add_css_class("username")
|
||||
self.name_label.set_xalign(0.5) # centered
|
||||
self.name_label.set_hexpand(True)
|
||||
name_row.append(self.name_label)
|
||||
|
||||
self.user_btn = Gtk.MenuButton()
|
||||
self.user_btn.add_css_class("userswitch")
|
||||
self.user_btn.set_icon_name("pan-down-symbolic")
|
||||
self.user_btn.set_visible(len(self.users) > 1) # no point if there's one user
|
||||
upop = Gtk.Popover()
|
||||
upop.add_css_class("menu")
|
||||
ubox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
||||
for u in self.users:
|
||||
b = Gtk.Button(label=u)
|
||||
b.add_css_class("menuitem")
|
||||
b.connect("clicked", lambda _w, name=u: (self.name_label.set_text(name),
|
||||
self.user_btn.popdown()))
|
||||
ubox.append(b)
|
||||
upop.set_child(ubox)
|
||||
self.user_btn.set_popover(upop)
|
||||
name_row.append(self.user_btn)
|
||||
card.append(name_row)
|
||||
|
||||
# password pill
|
||||
self.password = Gtk.PasswordEntry()
|
||||
self.password.set_show_peek_icon(True)
|
||||
self.password.set_property("placeholder-text", "Password")
|
||||
self.password.add_css_class("pill")
|
||||
self.password.connect("activate", self._on_submit)
|
||||
card.append(self.password)
|
||||
|
||||
self.error = Gtk.Label()
|
||||
self.error.add_css_class("error")
|
||||
self.error.set_visible(False)
|
||||
card.append(self.error)
|
||||
return card
|
||||
|
||||
# ---- behaviour -------------------------------------------------------- #
|
||||
def _tick(self):
|
||||
now = GLib.DateTime.new_now_local()
|
||||
self.clock.set_text(now.format("%H:%M · %A, %d %B %Y"))
|
||||
return True
|
||||
|
||||
def _on_submit(self, _entry):
|
||||
user = self.name_label.get_text()
|
||||
pw = self.password.get_text()
|
||||
cmd = self.sessions[self.session_dd.get_selected()][1]
|
||||
try:
|
||||
self.greetd.login(user, pw, cmd)
|
||||
self.quit() # greetd takes over from here
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.password.set_text("")
|
||||
self.error.set_text(str(e))
|
||||
self.error.set_visible(True)
|
||||
|
||||
def _run(self, argv):
|
||||
try:
|
||||
Gio.Subprocess.new(argv, Gio.SubprocessFlags.NONE)
|
||||
except GLib.Error as e:
|
||||
self.error.set_text(str(e))
|
||||
self.error.set_visible(True)
|
||||
|
||||
def _load_css(self):
|
||||
if not os.path.exists(CSS_PATH):
|
||||
return
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_path(CSS_PATH)
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(), provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(Greeter().run(None))
|
||||
@@ -0,0 +1,176 @@
|
||||
/* =========================================================================
|
||||
* tanin-greet — stylesheet. Mirrors the eww topbar (eww.scss): Fuji palette,
|
||||
* monochrome, pill shapes, Inter Medium. Accent is the brand Fuji violet.
|
||||
* ========================================================================= */
|
||||
@define-color bg #0f0f0f;
|
||||
@define-color bg_alt #191919;
|
||||
@define-color bg_hi #242424;
|
||||
@define-color fg #f0f0f0;
|
||||
@define-color yuki #f0f0f0; /* snow white — pill fill */
|
||||
@define-color muted #8a8a8a;
|
||||
@define-color accent #a39ec4;
|
||||
|
||||
/* No font-family override — inherit the system UI font. */
|
||||
.greeter {
|
||||
font-weight: 500;
|
||||
color: @fg;
|
||||
}
|
||||
* { text-shadow: none; -gtk-icon-shadow: none; outline: none; }
|
||||
|
||||
/* dark scrim over the wallpaper — keeps text readable, keeps it moody */
|
||||
.scrim { background-color: alpha(black, 0.55); }
|
||||
|
||||
/* ---- top bar: the eww bar look (opaque #0f0f0f strip) ------------------- *
|
||||
* left = session dropdown · center = clock (time/day/date) · right = power. */
|
||||
.topbar {
|
||||
background-color: @bg;
|
||||
min-height: 34px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.topbar .clock {
|
||||
color: @fg;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* session dropdown, top-left corner — quiet on the black bar, no box */
|
||||
.session-dd,
|
||||
.session-dd > button {
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
min-height: 0;
|
||||
padding: 2px 6px;
|
||||
color: @muted;
|
||||
font-size: 12px;
|
||||
}
|
||||
.session-dd > button:hover { color: @fg; }
|
||||
|
||||
/* power button — NO background at all, just the glyph */
|
||||
.topbar .power,
|
||||
.topbar .power > button {
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: 2px 6px;
|
||||
color: @muted;
|
||||
}
|
||||
.topbar .power:hover,
|
||||
.topbar .power > button:hover { color: @accent; background: transparent; }
|
||||
|
||||
/* TANINUX wordmark — bottom center, understated */
|
||||
.brand {
|
||||
color: @fg;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
letter-spacing: 3px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ---- centered login card (flat, no heavy frame — minimal) --------------- *
|
||||
* NB: class is .logincard, never .card — libadwaita's built-in .card recipe
|
||||
* paints an elevated background + shadow (the "solid box, too big" bug). */
|
||||
.logincard { padding: 8px; background: transparent; box-shadow: none; }
|
||||
|
||||
.avatar {
|
||||
color: @fg;
|
||||
background-color: alpha(@accent, 0.14);
|
||||
border-radius: 999px;
|
||||
padding: 16px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* username row — plain name + chevron on the right, same width as the pill so
|
||||
* the chevron sits where the password pill ends. No background (just the name). */
|
||||
.namerow { min-width: 300px; }
|
||||
.username {
|
||||
color: @yuki;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.userswitch {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding: 2px 4px;
|
||||
color: @muted;
|
||||
}
|
||||
.userswitch:hover { color: @fg; }
|
||||
|
||||
/* password pill — snow white (yuki), dark text, accent focus ring.
|
||||
* border-radius 999px like the bar's workspaces/clock. */
|
||||
.pill {
|
||||
min-width: 300px;
|
||||
min-height: 0;
|
||||
background-color: alpha(@bg_alt, 0.45); /* greyish, slightly transparent */
|
||||
color: @fg;
|
||||
border: 1.5px solid alpha(@fg, 0.22); /* grey border (unfocused) */
|
||||
border-radius: 999px;
|
||||
padding: 11px 18px;
|
||||
box-shadow: none;
|
||||
}
|
||||
entry.pill { caret-color: @yuki; }
|
||||
entry.pill text { color: @fg; }
|
||||
entry.pill text > placeholder { color: alpha(@fg, 0.45); }
|
||||
entry.pill image { color: alpha(@fg, 0.6); } /* peek-eye icon */
|
||||
/* clicked / focused → white border */
|
||||
.pill:focus-within {
|
||||
border-color: @yuki;
|
||||
background-color: alpha(@bg_alt, 0.55);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* popup lists (session dropdown, user switcher).
|
||||
* IMPORTANT: only style `popover > contents` — the outer `popover` node also
|
||||
* covers the reserved shadow/margin area, so a solid bg there paints a huge
|
||||
* solid box. Keep the outer node fully transparent. */
|
||||
popover {
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
popover > contents {
|
||||
background-color: @bg_alt;
|
||||
color: @fg;
|
||||
border: 1px solid alpha(@fg, 0.06);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px alpha(black, 0.45);
|
||||
padding: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
/* kill the inner list's OWN themed background (the lighter nested box) so the
|
||||
* whole popover reads as one uniform surface. */
|
||||
popover listview,
|
||||
popover scrolledwindow,
|
||||
popover scrolledwindow > viewport,
|
||||
popover .view {
|
||||
background: transparent;
|
||||
background-color: transparent;
|
||||
color: @fg;
|
||||
}
|
||||
popover listview > row,
|
||||
popover .menuitem { border-radius: 8px; padding: 5px 10px; }
|
||||
popover listview > row:selected,
|
||||
popover listview > row:hover,
|
||||
popover row:selected,
|
||||
popover .menuitem:hover { background-color: alpha(@accent, 0.14); color: @accent; }
|
||||
|
||||
.error { color: #e0a3a3; font-size: 12px; margin-top: 4px; }
|
||||
|
||||
/* power popover buttons */
|
||||
.menuitem {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 8px;
|
||||
padding: 6px 14px;
|
||||
color: @fg;
|
||||
}
|
||||
.menuitem:hover { background-color: alpha(@accent, 0.14); color: @accent; }
|
||||
@@ -0,0 +1,24 @@
|
||||
# Maintainer: Karim <karim@gabrielevarano.ch>
|
||||
#
|
||||
# tanin-libadwaita — Fuji recolour for GTK4/libadwaita AND GTK3 (light + dark).
|
||||
# Recolours libadwaita apps (tsettings/thub, Nautilus, …) via named Adwaita
|
||||
# colors, and GTK3 apps (pavucontrol, nm-connection-editor, …) via adw-gtk3,
|
||||
# with pill-shaped controls. One switch covers both toolkits:
|
||||
# tanin-libadwaita {dark|light|auto|watch}
|
||||
pkgname=tanin-libadwaita
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="TANINUX Fuji recolour for GTK4/libadwaita + GTK3 — light + dark, pills"
|
||||
arch=('any')
|
||||
url="https://taninux.kgva.ch"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('glib2' 'adw-gtk3') # gsettings + GTK3 base theme for the recolour
|
||||
optdepends=('tanin-setup: seed the variant + start the color-scheme watcher on login')
|
||||
|
||||
package() {
|
||||
install -Dm644 "$startdir/gtk-4.0/dark.css" "$pkgdir/usr/share/tanin/libadwaita/gtk-4.0/dark.css"
|
||||
install -Dm644 "$startdir/gtk-4.0/light.css" "$pkgdir/usr/share/tanin/libadwaita/gtk-4.0/light.css"
|
||||
install -Dm644 "$startdir/gtk-3.0/dark.css" "$pkgdir/usr/share/tanin/libadwaita/gtk-3.0/dark.css"
|
||||
install -Dm644 "$startdir/gtk-3.0/light.css" "$pkgdir/usr/share/tanin/libadwaita/gtk-3.0/light.css"
|
||||
install -Dm755 "$startdir/tanin-libadwaita" "$pkgdir/usr/bin/tanin-libadwaita"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
# tanin-libadwaita
|
||||
|
||||
Fuji recolour for **libadwaita / GTK4** — a **light** and a **dark** variant so
|
||||
the whole OS is covered. Recolours every libadwaita app (tsettings, thub,
|
||||
Nautilus, GNOME apps, …) by overriding Adwaita's *named colors*, and makes
|
||||
buttons/entries **pill-shaped** to match the bar and greeter.
|
||||
|
||||
## Files
|
||||
- `tanin-dark.css` → `/usr/share/tanin/libadwaita/tanin-dark.css`
|
||||
- `tanin-light.css` → `/usr/share/tanin/libadwaita/tanin-light.css`
|
||||
- `tanin-libadwaita` → `/usr/bin/tanin-libadwaita` — switch script
|
||||
|
||||
## Use
|
||||
```sh
|
||||
tanin-libadwaita dark # or: light
|
||||
```
|
||||
Symlinks the variant to `~/.config/gtk-4.0/gtk.css` and sets
|
||||
`color-scheme` + `accent-color`. A pre-existing real `gtk.css` is backed up to
|
||||
`gtk.css.pre-tanin` once.
|
||||
|
||||
## Why named-color overrides (not a full theme)
|
||||
libadwaita ignores `gtk-theme-name`; the supported way to restyle it is the
|
||||
user `gtk.css`, where overriding `@define-color accent_color` / `window_bg_color`
|
||||
/ `card_bg_color` / … recolours all libadwaita apps at once. There are no CSS
|
||||
media queries, so light/dark is two files the switch script flips between.
|
||||
|
||||
## Preview from the repo (isolated, no change to your config)
|
||||
```sh
|
||||
mkdir -p /tmp/t/gtk-4.0 && cp tanin-dark.css /tmp/t/gtk-4.0/gtk.css
|
||||
XDG_CONFIG_HOME=/tmp/t cage -s -- gnome-text-editor # or any libadwaita app
|
||||
```
|
||||
|
||||
## Palettes
|
||||
- **Dark:** bg `#0f0f0f`, fg `#f0f0f0`, accent `#a39ec4` (Fuji violet).
|
||||
- **Light:** bg `#f3f1ec`, fg `#2a2926`, accent `#6f67a8` (deeper violet for contrast).
|
||||
|
||||
## Notes
|
||||
- App content that uses its own style scheme (e.g. gnome-text-editor's
|
||||
GtkSourceView syntax theme) is NOT controlled here — that's app-internal.
|
||||
- GTK3 apps (incl. eww) aren't covered by this; they use `adw-gtk3` / their own
|
||||
CSS. A matching GTK3 `Tanin` theme could follow if wanted.
|
||||
@@ -0,0 +1,66 @@
|
||||
/* =========================================================================
|
||||
* tanin-libadwaita — Fuji DARK recolour for GTK3 (rides on adw-gtk3-dark).
|
||||
* Covers the gaps the libadwaita/GTK4 recolour can't reach: GTK3 apps
|
||||
* (pavucontrol, nm-connection-editor, …). Same palette as the GTK4 dark.
|
||||
* ========================================================================= */
|
||||
|
||||
/* libadwaita-style names (adw-gtk3 honours these) */
|
||||
@define-color accent_color #a39ec4;
|
||||
@define-color accent_bg_color #a39ec4;
|
||||
@define-color accent_fg_color #0f0f0f;
|
||||
@define-color window_bg_color #0f0f0f;
|
||||
@define-color window_fg_color #f0f0f0;
|
||||
@define-color view_bg_color #141414;
|
||||
@define-color view_fg_color #f0f0f0;
|
||||
@define-color headerbar_bg_color #0f0f0f;
|
||||
@define-color headerbar_fg_color #f0f0f0;
|
||||
@define-color card_bg_color #191919;
|
||||
@define-color card_fg_color #f0f0f0;
|
||||
@define-color popover_bg_color #191919;
|
||||
@define-color popover_fg_color #f0f0f0;
|
||||
@define-color dialog_bg_color #141414;
|
||||
@define-color dialog_fg_color #f0f0f0;
|
||||
@define-color sidebar_bg_color #131313;
|
||||
|
||||
/* legacy GTK3 / Adwaita names */
|
||||
@define-color theme_bg_color #0f0f0f;
|
||||
@define-color theme_fg_color #f0f0f0;
|
||||
@define-color theme_base_color #141414;
|
||||
@define-color theme_text_color #f0f0f0;
|
||||
@define-color theme_selected_bg_color #a39ec4;
|
||||
@define-color theme_selected_fg_color #0f0f0f;
|
||||
@define-color insensitive_bg_color #161616;
|
||||
@define-color insensitive_fg_color #6a6a6a;
|
||||
@define-color insensitive_base_color #121212;
|
||||
@define-color borders rgba(240, 240, 240, 0.10);
|
||||
|
||||
@define-color destructive_color #e0a3a3;
|
||||
@define-color success_color #9ec4a3;
|
||||
@define-color warning_color #c4bd9e;
|
||||
@define-color error_color #e0a3a3;
|
||||
|
||||
/* pill-shaped controls */
|
||||
button,
|
||||
entry,
|
||||
combobox button,
|
||||
spinbutton,
|
||||
notebook > header > tabs > tab { border-radius: 999px; }
|
||||
|
||||
/* Force the Fuji accent. adw-gtk3 otherwise follows the GNOME accent-color
|
||||
* gsetting (would stay blue), so we override the accented widgets explicitly. */
|
||||
scale highlight,
|
||||
scale trough progress,
|
||||
progressbar progress,
|
||||
levelbar block.filled { background-color: @accent_color; }
|
||||
checkbutton check:checked,
|
||||
radiobutton radio:checked,
|
||||
check:checked, radio:checked { background-color: @accent_color; border-color: @accent_color; }
|
||||
switch:checked { background-color: @accent_color; }
|
||||
notebook > header tab:checked { box-shadow: inset 0 -3px @accent_color; }
|
||||
notebook > header tab:checked label { color: @accent_color; }
|
||||
row:selected, list row:selected,
|
||||
treeview.view:selected, .view:selected,
|
||||
menuitem:hover, menu menuitem:hover,
|
||||
*:selected { background-color: @accent_color; color: @accent_fg_color; }
|
||||
button.suggested-action { background-color: @accent_color; color: @accent_fg_color; }
|
||||
entry:focus, entry:focus-within { border-color: @accent_color; }
|
||||
@@ -0,0 +1,65 @@
|
||||
/* =========================================================================
|
||||
* tanin-libadwaita — Fuji LIGHT recolour for GTK3 (rides on adw-gtk3).
|
||||
* Same warm Fuji light palette as the GTK4 light variant.
|
||||
* ========================================================================= */
|
||||
|
||||
/* libadwaita-style names (adw-gtk3 honours these) */
|
||||
@define-color accent_color #6f67a8;
|
||||
@define-color accent_bg_color #6f67a8;
|
||||
@define-color accent_fg_color #ffffff;
|
||||
@define-color window_bg_color #f3f1ec;
|
||||
@define-color window_fg_color #2a2926;
|
||||
@define-color view_bg_color #faf8f4;
|
||||
@define-color view_fg_color #2a2926;
|
||||
@define-color headerbar_bg_color #ece8e1;
|
||||
@define-color headerbar_fg_color #2a2926;
|
||||
@define-color card_bg_color #ffffff;
|
||||
@define-color card_fg_color #2a2926;
|
||||
@define-color popover_bg_color #ffffff;
|
||||
@define-color popover_fg_color #2a2926;
|
||||
@define-color dialog_bg_color #faf8f4;
|
||||
@define-color dialog_fg_color #2a2926;
|
||||
@define-color sidebar_bg_color #ece8e1;
|
||||
|
||||
/* legacy GTK3 / Adwaita names */
|
||||
@define-color theme_bg_color #f3f1ec;
|
||||
@define-color theme_fg_color #2a2926;
|
||||
@define-color theme_base_color #faf8f4;
|
||||
@define-color theme_text_color #2a2926;
|
||||
@define-color theme_selected_bg_color #6f67a8;
|
||||
@define-color theme_selected_fg_color #ffffff;
|
||||
@define-color insensitive_bg_color #ebe7e0;
|
||||
@define-color insensitive_fg_color #9b968c;
|
||||
@define-color insensitive_base_color #f1eee8;
|
||||
@define-color borders rgba(0, 0, 0, 0.12);
|
||||
|
||||
@define-color destructive_color #b04848;
|
||||
@define-color success_color #4f7a57;
|
||||
@define-color warning_color #8a7f3a;
|
||||
@define-color error_color #b04848;
|
||||
|
||||
/* pill-shaped controls */
|
||||
button,
|
||||
entry,
|
||||
combobox button,
|
||||
spinbutton,
|
||||
notebook > header > tabs > tab { border-radius: 999px; }
|
||||
|
||||
/* Force the Fuji accent. adw-gtk3 otherwise follows the GNOME accent-color
|
||||
* gsetting, so we override the accented widgets explicitly. */
|
||||
scale highlight,
|
||||
scale trough progress,
|
||||
progressbar progress,
|
||||
levelbar block.filled { background-color: @accent_color; }
|
||||
checkbutton check:checked,
|
||||
radiobutton radio:checked,
|
||||
check:checked, radio:checked { background-color: @accent_color; border-color: @accent_color; }
|
||||
switch:checked { background-color: @accent_color; }
|
||||
notebook > header tab:checked { box-shadow: inset 0 -3px @accent_color; }
|
||||
notebook > header tab:checked label { color: @accent_color; }
|
||||
row:selected, list row:selected,
|
||||
treeview.view:selected, .view:selected,
|
||||
menuitem:hover, menu menuitem:hover,
|
||||
*:selected { background-color: @accent_color; color: @accent_fg_color; }
|
||||
button.suggested-action { background-color: @accent_color; color: @accent_fg_color; }
|
||||
entry:focus, entry:focus-within { border-color: @accent_color; }
|
||||
@@ -0,0 +1,74 @@
|
||||
/* =========================================================================
|
||||
* tanin-theme — Fuji recolour for libadwaita / GTK4.
|
||||
* Drop-in at ~/.config/gtk-4.0/gtk.css. Overrides libadwaita's NAMED colors,
|
||||
* which recolours every libadwaita app (tsettings/thub, Nautilus, …) without a
|
||||
* full custom theme. Palette is 1:1 with eww.scss / the greeter.
|
||||
* ========================================================================= */
|
||||
|
||||
/* ---- Fuji palette -------------------------------------------------------- */
|
||||
@define-color fuji_bg #0f0f0f;
|
||||
@define-color fuji_bg_alt #191919;
|
||||
@define-color fuji_bg_hi #242424;
|
||||
@define-color fuji_fg #f0f0f0;
|
||||
@define-color fuji_muted #8a8a8a;
|
||||
@define-color fuji_accent #a39ec4;
|
||||
|
||||
/* ---- accent -------------------------------------------------------------- */
|
||||
@define-color accent_color #a39ec4;
|
||||
@define-color accent_bg_color #a39ec4;
|
||||
@define-color accent_fg_color #0f0f0f; /* dark text on the light violet */
|
||||
|
||||
/* ---- window / base surfaces --------------------------------------------- */
|
||||
@define-color window_bg_color #0f0f0f;
|
||||
@define-color window_fg_color #f0f0f0;
|
||||
@define-color view_bg_color #141414;
|
||||
@define-color view_fg_color #f0f0f0;
|
||||
|
||||
/* ---- headerbar ----------------------------------------------------------- */
|
||||
@define-color headerbar_bg_color #0f0f0f;
|
||||
@define-color headerbar_fg_color #f0f0f0;
|
||||
@define-color headerbar_border_color #f0f0f0;
|
||||
@define-color headerbar_backdrop_color #0f0f0f;
|
||||
@define-color headerbar_shade_color rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* ---- cards / dialogs / popovers / sidebar ------------------------------- */
|
||||
@define-color card_bg_color #191919;
|
||||
@define-color card_fg_color #f0f0f0;
|
||||
@define-color card_shade_color rgba(0, 0, 0, 0.45);
|
||||
@define-color dialog_bg_color #141414;
|
||||
@define-color dialog_fg_color #f0f0f0;
|
||||
@define-color popover_bg_color #191919;
|
||||
@define-color popover_fg_color #f0f0f0;
|
||||
@define-color sidebar_bg_color #131313;
|
||||
@define-color sidebar_fg_color #f0f0f0;
|
||||
@define-color sidebar_backdrop_color #0f0f0f;
|
||||
@define-color sidebar_shade_color rgba(0, 0, 0, 0.45);
|
||||
|
||||
/* ---- semantic ------------------------------------------------------------ */
|
||||
@define-color destructive_color #e0a3a3;
|
||||
@define-color destructive_bg_color #b45c5c;
|
||||
@define-color destructive_fg_color #0f0f0f;
|
||||
@define-color success_color #9ec4a3;
|
||||
@define-color warning_color #c4bd9e;
|
||||
@define-color error_color #e0a3a3;
|
||||
|
||||
@define-color shade_color rgba(0, 0, 0, 0.4);
|
||||
@define-color scrollbar_outline_color rgba(255, 255, 255, 0.06);
|
||||
|
||||
/* ---- pill-shaped controls (Fuji house style) --------------------------- *
|
||||
* Buttons, entries and toggles become stadium pills like the bar/greeter. */
|
||||
button,
|
||||
button.toggle,
|
||||
menubutton > button,
|
||||
dropdown > button,
|
||||
splitbutton > button,
|
||||
entry,
|
||||
spinbutton,
|
||||
.pill {
|
||||
border-radius: 999px;
|
||||
min-height: 0;
|
||||
}
|
||||
/* containers stay softly rounded, not stadium-shaped */
|
||||
.card,
|
||||
popover > contents,
|
||||
.osd { border-radius: 14px; }
|
||||
@@ -0,0 +1,71 @@
|
||||
/* =========================================================================
|
||||
* tanin-libadwaita — Fuji LIGHT recolour for libadwaita / GTK4.
|
||||
* Warm "Yuki/Fuji" light palette (1:1 with eww.scss light tokens), deeper
|
||||
* violet accent so it reads on white. Pair with color-scheme=prefer-light.
|
||||
* ========================================================================= */
|
||||
|
||||
/* ---- Fuji light palette -------------------------------------------------- */
|
||||
@define-color fuji_bg #f3f1ec; /* soft warm white */
|
||||
@define-color fuji_bg_alt #e8e4dc; /* cards / tiles */
|
||||
@define-color fuji_bg_hi #ddd8ce; /* hover */
|
||||
@define-color fuji_fg #2a2926; /* near-black ink */
|
||||
@define-color fuji_muted #6f6b62;
|
||||
@define-color fuji_accent #6f67a8; /* deeper violet — readable on light */
|
||||
|
||||
/* ---- accent -------------------------------------------------------------- */
|
||||
@define-color accent_color #6f67a8;
|
||||
@define-color accent_bg_color #6f67a8;
|
||||
@define-color accent_fg_color #ffffff;
|
||||
|
||||
/* ---- window / base surfaces --------------------------------------------- */
|
||||
@define-color window_bg_color #f3f1ec;
|
||||
@define-color window_fg_color #2a2926;
|
||||
@define-color view_bg_color #faf8f4;
|
||||
@define-color view_fg_color #2a2926;
|
||||
|
||||
/* ---- headerbar ----------------------------------------------------------- */
|
||||
@define-color headerbar_bg_color #ece8e1;
|
||||
@define-color headerbar_fg_color #2a2926;
|
||||
@define-color headerbar_border_color #2a2926;
|
||||
@define-color headerbar_backdrop_color #f3f1ec;
|
||||
@define-color headerbar_shade_color rgba(0, 0, 0, 0.08);
|
||||
|
||||
/* ---- cards / dialogs / popovers / sidebar ------------------------------- */
|
||||
@define-color card_bg_color #ffffff;
|
||||
@define-color card_fg_color #2a2926;
|
||||
@define-color card_shade_color rgba(0, 0, 0, 0.07);
|
||||
@define-color dialog_bg_color #faf8f4;
|
||||
@define-color dialog_fg_color #2a2926;
|
||||
@define-color popover_bg_color #ffffff;
|
||||
@define-color popover_fg_color #2a2926;
|
||||
@define-color sidebar_bg_color #ece8e1;
|
||||
@define-color sidebar_fg_color #2a2926;
|
||||
@define-color sidebar_backdrop_color #f3f1ec;
|
||||
@define-color sidebar_shade_color rgba(0, 0, 0, 0.07);
|
||||
|
||||
/* ---- semantic ------------------------------------------------------------ */
|
||||
@define-color destructive_color #b04848;
|
||||
@define-color destructive_bg_color #b04848;
|
||||
@define-color destructive_fg_color #ffffff;
|
||||
@define-color success_color #4f7a57;
|
||||
@define-color warning_color #8a7f3a;
|
||||
@define-color error_color #b04848;
|
||||
|
||||
@define-color shade_color rgba(0, 0, 0, 0.07);
|
||||
@define-color scrollbar_outline_color rgba(0, 0, 0, 0.08);
|
||||
|
||||
/* ---- pill-shaped controls (Fuji house style) --------------------------- */
|
||||
button,
|
||||
button.toggle,
|
||||
menubutton > button,
|
||||
dropdown > button,
|
||||
splitbutton > button,
|
||||
entry,
|
||||
spinbutton,
|
||||
.pill {
|
||||
border-radius: 999px;
|
||||
min-height: 0;
|
||||
}
|
||||
.card,
|
||||
popover > contents,
|
||||
.osd { border-radius: 14px; }
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# tanin-libadwaita {dark|light|auto|watch}
|
||||
# Activates the Fuji recolour for BOTH GTK4/libadwaita and GTK3 by symlinking
|
||||
# the chosen variant into ~/.config/gtk-{4.0,3.0}/gtk.css.
|
||||
# dark|light set that mode (and the GNOME color-scheme + gtk-theme)
|
||||
# auto follow the CURRENT color-scheme once (don't change it)
|
||||
# watch follow color-scheme changes live (daemon) — the appearance
|
||||
# toggle in settings then drives light/dark automatically
|
||||
set -euo pipefail
|
||||
|
||||
SHARE="${TANIN_LIBADWAITA_DIR:-/usr/share/tanin/libadwaita}"
|
||||
CFG="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
|
||||
# Symlink both toolkits' gtk.css to the variant + pick the matching adw-gtk3.
|
||||
# Does NOT touch color-scheme (so it's safe to call from a color-scheme watcher).
|
||||
link_variant() {
|
||||
local mode="$1" gtktheme
|
||||
case "$mode" in
|
||||
dark) gtktheme="adw-gtk3-dark" ;;
|
||||
light) gtktheme="adw-gtk3" ;;
|
||||
*) return 2 ;;
|
||||
esac
|
||||
local v src dst
|
||||
for v in gtk-4.0 gtk-3.0; do
|
||||
src="$SHARE/$v/$mode.css"; dst="$CFG/$v/gtk.css"
|
||||
[ -f "$src" ] || continue
|
||||
mkdir -p "$CFG/$v"
|
||||
if [ -e "$dst" ] && [ ! -L "$dst" ] && [ ! -e "$dst.pre-tanin" ]; then
|
||||
cp -a "$dst" "$dst.pre-tanin" # back up a real file once
|
||||
fi
|
||||
ln -sf "$src" "$dst"
|
||||
done
|
||||
gsettings set org.gnome.desktop.interface gtk-theme "$gtktheme" 2>/dev/null || true
|
||||
gsettings set org.gnome.desktop.interface accent-color "purple" 2>/dev/null || true
|
||||
echo "tanin-libadwaita: $mode"
|
||||
}
|
||||
|
||||
current_mode() {
|
||||
case "$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null)" in
|
||||
*prefer-light*) echo light ;;
|
||||
*) echo dark ;;
|
||||
esac
|
||||
}
|
||||
|
||||
case "${1:-auto}" in
|
||||
dark|light)
|
||||
link_variant "$1"
|
||||
gsettings set org.gnome.desktop.interface color-scheme \
|
||||
"$([ "$1" = light ] && echo prefer-light || echo prefer-dark)" 2>/dev/null || true
|
||||
;;
|
||||
auto)
|
||||
link_variant "$(current_mode)"
|
||||
;;
|
||||
watch)
|
||||
link_variant "$(current_mode)"
|
||||
gsettings monitor org.gnome.desktop.interface color-scheme | while read -r _; do
|
||||
link_variant "$(current_mode)"
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "usage: tanin-libadwaita {dark|light|auto|watch}" >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -0,0 +1,22 @@
|
||||
# Maintainer: Karim <karim@gabrielevarano.ch>
|
||||
#
|
||||
# First-run-Setup: seedet ~/.config aus /usr/share/tanin/skel und richtet das
|
||||
# Session-Portal ein. Wird vom Metapackage als depends gezogen; nach der
|
||||
# Installation ruft der User (oder ein Autostart) einmal `tanin-setup`.
|
||||
pkgname=tanin-setup
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="TANINUX first-run setup — seed user configs and enable the session portal"
|
||||
arch=('any')
|
||||
url="https://taninux.kgva.ch"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('systemd' 'xdg-desktop-portal' 'xdg-desktop-portal-gtk')
|
||||
|
||||
package() {
|
||||
install -Dm755 "$startdir/tanin-setup" "$pkgdir/usr/bin/tanin-setup"
|
||||
install -Dm644 "$startdir/tanin-session.target" \
|
||||
"$pkgdir/usr/share/tanin/tanin-session.target"
|
||||
# Default-Configs landen hier (Phase 2: tanin-eww liefert die eww/Compositor-
|
||||
# Configs nach /usr/share/tanin/skel/.config/…):
|
||||
install -d "$pkgdir/usr/share/tanin/skel"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=TANINUX graphical session
|
||||
# Startet (per Dependency-Request) graphical-session.target, an dem
|
||||
# xdg-desktop-portal hängt — sonst kann das Portal nicht aktivieren und
|
||||
# libadwaita-Apps (Nautilus …) bleiben im Hell-Fallback.
|
||||
Requires=graphical-session.target
|
||||
After=graphical-session.target
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# TANINUX first-run setup: seedet Default-Configs nach ~/.config und richtet
|
||||
# das Session-Portal ein (damit libadwaita-Apps Hell/Dunkel übernehmen).
|
||||
# Idempotent. --force überschreibt bestehende Configs (mit .bak-Backup).
|
||||
set -euo pipefail
|
||||
|
||||
SKEL="/usr/share/tanin/skel"
|
||||
UNIT="/usr/share/tanin/tanin-session.target"
|
||||
CFG="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
FORCE=0
|
||||
[ "${1:-}" = "--force" ] && FORCE=1
|
||||
|
||||
seed_configs() {
|
||||
[ -d "$SKEL" ] || return 0
|
||||
while IFS= read -r -d '' src; do
|
||||
rel="${src#"$SKEL"/}"
|
||||
dst="$HOME/$rel"
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
if [ -e "$dst" ] && [ "$FORCE" -eq 0 ]; then
|
||||
continue
|
||||
fi
|
||||
[ -e "$dst" ] && cp -a "$dst" "$dst.bak" 2>/dev/null || true
|
||||
cp -a "$src" "$dst"
|
||||
done < <(find "$SKEL" -type f -print0)
|
||||
}
|
||||
|
||||
install_session_target() {
|
||||
[ -f "$UNIT" ] || return 0
|
||||
install -Dm644 "$UNIT" "$CFG/systemd/user/tanin-session.target"
|
||||
systemctl --user daemon-reload 2>/dev/null || true
|
||||
}
|
||||
|
||||
seed_configs
|
||||
install_session_target
|
||||
|
||||
cat <<'EOF'
|
||||
TANINUX setup complete.
|
||||
• configs seeded into ~/.config
|
||||
• session target installed (~/.config/systemd/user/tanin-session.target)
|
||||
|
||||
Make sure your compositor autostart contains:
|
||||
exec-once = dbus-update-activation-environment --systemd --all
|
||||
exec-once = systemctl --user start tanin-session.target
|
||||
|
||||
(The shipped TANINUX compositor config already includes these.)
|
||||
EOF
|
||||
@@ -0,0 +1,56 @@
|
||||
# Maintainer: Karim <karim@gabrielevarano.ch>
|
||||
#
|
||||
# Echtes Paket für die TANINUX-App (tsettings + thub + TUI).
|
||||
# Lokal testen: makepkg -si (zieht die makedepends automatisch)
|
||||
# Vor Publish: url + license bestätigen, source auf GitHub-Tag setzen.
|
||||
pkgname=taninux
|
||||
pkgver=0.2.0
|
||||
pkgrel=1
|
||||
pkgdesc="Central Linux management for Arch — GTK System Settings, Software Hub and TUI"
|
||||
arch=('any')
|
||||
url="https://taninux.kgva.ch"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('python' 'python-gobject' 'gtk4' 'libadwaita' 'polkit')
|
||||
optdepends=(
|
||||
'paru: AUR support in the Software Hub (opt-in)'
|
||||
'flatpak: Flatpak apps and updates'
|
||||
'pacman-contrib: update check (checkupdates) and cache cleanup (paccache)'
|
||||
'networkmanager: network settings'
|
||||
'bluez-utils: bluetooth settings'
|
||||
'wireplumber: audio settings (PipeWire)'
|
||||
'cups: printer management'
|
||||
'brightnessctl: display brightness'
|
||||
'wlsunset: night light'
|
||||
'timeshift: system snapshots/backups'
|
||||
)
|
||||
makedepends=('python-build' 'python-installer' 'python-wheel' 'python-hatchling' 'git')
|
||||
# Lokaler Build aus dem Arbeitsbaum: source leer lassen und in build()/package()
|
||||
# auf "$startdir/../.." zeigen. Für die Veröffentlichung stattdessen den Tag ziehen:
|
||||
#source=("git+https://git.openbureau.ch/karim/taninux.git#tag=v$pkgver")
|
||||
#sha256sums=('SKIP')
|
||||
|
||||
_root="$startdir/../.."
|
||||
|
||||
build() {
|
||||
cd "$_root"
|
||||
python -m build --wheel --no-isolation
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$_root"
|
||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||
|
||||
# Privilegierter Helper — process.py erwartet /usr/lib/taninux/ (mit Repo-Fallback).
|
||||
install -Dm755 src/taninux/gui/helper/taninux-helper \
|
||||
"$pkgdir/usr/lib/taninux/taninux-helper"
|
||||
|
||||
# polkit-Policy (pkexec ohne generischen Admin-Prompt).
|
||||
install -Dm644 src/taninux/gui/polkit/ch.gabrielevarano.Taninux.policy \
|
||||
"$pkgdir/usr/share/polkit-1/actions/ch.gabrielevarano.Taninux.policy"
|
||||
|
||||
# Desktop-Einträge für beide Frontends.
|
||||
install -Dm644 data/applications/ch.gabrielevarano.Taninux.Settings.desktop \
|
||||
"$pkgdir/usr/share/applications/ch.gabrielevarano.Taninux.Settings.desktop"
|
||||
install -Dm644 data/applications/ch.gabrielevarano.Taninux.SoftwareHub.desktop \
|
||||
"$pkgdir/usr/share/applications/ch.gabrielevarano.Taninux.SoftwareHub.desktop"
|
||||
}
|
||||
Reference in New Issue
Block a user