commit b7399031c7745caf74e266a1f8ef615809915152 Author: Anry Das Date: Sun Oct 18 21:23:16 2026 +0300 Initial Das OS 0.1.0: Void Linux live ISO with Xfce - xbps-src templates: das-theme (adw-gtk3-based GTK themes, generated xfwm4 frames, wallpapers, logo), das-settings (Xfce, panel, LightDM defaults in /etc/xdg/das), adw-gtk3 and font-jetbrains-mono - void-mklive profile in live/: packages, runit services, rootfs overlay, Ukrainian locale, live user with autologin - Void builder container, build scripts, QEMU runner and Makefile - Bilingual README with screenshots Co-Authored-By: Claude Opus 5.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f67456a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Build output: repository, ISO images, void-packages clone, QEMU disks, caches +/out/ + +# Go +srcpkgs/das-theme/files/xfwm4gen/xfwm4gen + +# IDE +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a8f7f83 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Das + +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/Makefile b/Makefile new file mode 100644 index 0000000..89e6db5 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# Das OS build entry points. Everything except QEMU runs in the Void builder container. + +.PHONY: help packages iso qemu qemu-bios qemu-disk test clean + +help: + @echo "make packages build srcpkgs/* into the local repository (out/hostdir/binpkgs)" + @echo "make iso build packages, then the live ISO (out/iso)" + @echo "make qemu boot the newest ISO in QEMU with UEFI" + @echo "make qemu-bios boot the newest ISO in QEMU with legacy BIOS" + @echo "make qemu-disk boot with a 20 GB virtual disk attached (for installer tests)" + @echo "make test run Go tests" + @echo "make clean remove build output (keeps download caches)" + +packages: + scripts/builder.sh /work/scripts/container/build-packages.sh + +iso: packages + scripts/builder.sh /work/scripts/container/build-iso.sh + +qemu: + scripts/run-qemu.sh + +qemu-bios: + scripts/run-qemu.sh --bios + +qemu-disk: + scripts/run-qemu.sh --disk + +test: + cd srcpkgs/das-theme/files/xfwm4gen && go test ./... + +# Build output may be owned by root when a build was interrupted, so remove it from the container +clean: + scripts/builder.sh rm -rf /work/out/hostdir/binpkgs /work/out/iso + rm -rf out/qemu diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4017c5 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +## Das OS +### _A light, friendly Void Linux–based desktop with Xfce and a Ukrainian touch_ + +[![Void Linux](https://img.shields.io/badge/Void_Linux-x86__64_glibc-green)](https://voidlinux.org) [![Xfce](https://img.shields.io/badge/Xfce-4.20-orange)](https://xfce.org) [![runit](https://img.shields.io/badge/init-runit-blue)](https://docs.voidlinux.org/config/services/) [![Go](https://img.shields.io/badge/Go-1.22+-blue)](https://go.dev) [![Version](https://img.shields.io/badge/version-0.1.0-lightgrey)](#) + +🇺🇦 [Українська версія](README_ua.md) + +### 💡 Purpose +Void Linux is fast, simple and independent: runit instead of systemd, and its own xbps package manager. Out of the box, though, it is a bare system, and getting it ready for daily use takes hours of setup. Das OS builds a live ISO that boots straight into a configured Xfce desktop. It has its own look, a Ukrainian interface and keyboard layout, and the usual desktop apps. Everything Das OS adds comes from its own xbps packages, so the setup can be reproduced and updated like any other package. + +### 📃 Features +- [Live ISO](#LiveISO) that boots on UEFI and BIOS straight into the desktop +- [Das OS look](#Look): light and dark themes, generated window frames, wallpapers and logo +- [Desktop defaults](#Defaults) for Xfce that don't overwrite stock files +- [Own package repository](#Repository) built by xbps-src from in-tree templates +- [Containerised build](#Build) that runs on any Linux host with Docker, and QEMU scripts for testing + +### ⭐ Advantages +- **Void underneath** – a fast runit boot, the xbps package manager and fresh software, with none of the manual setup. +- **Light** – no systemd, plus Xfce, LightDM and PipeWire, so it runs well on older laptops too. +- **Nothing hidden** – all customisations ship as ordinary xbps packages, so `xbps-query -f das-settings` shows exactly what changed. +- **Users keep control** – defaults live in `/etc/xdg/das`, so a user's own settings always take precedence and upgrades never reset them. +- **Builds anywhere** – the build runs in a Void container, so the host can be Mint, Ubuntu or Fedora. + +### 📱 Screenshots + + + + + + + + + + + + + + + + + + + + + +
Live desktopTerminal and window framesBoot menu (UEFI)
Live desktopTerminal and window framesBoot menu (UEFI)
Light wallpaperDark wallpaperWindow frames
Light wallpaperDark wallpaperWindow frames: light and dark, active and inactive
+ +### 📌 Using + +#### Live ISO +**_The live system logs in to the desktop on its own, with no installation needed._** +- Boots on UEFI (GRUB) and on legacy BIOS (isolinux), both with a Das OS splash screen. +- The *graphics disabled* entry boots with `nomodeset` for machines whose screen stays black, and the *RAM* entry copies the whole system to memory. +- User `live`, password `live`, with `sudo` that needs no password. The user is created at boot and exists only in the live session. +- Ukrainian interface (`uk_UA.UTF-8`) and the Kyiv time zone by default. Keyboard layouts are `us,ua`, switched with Alt+Shift. +- Includes NetworkManager, Bluetooth, PipeWire sound and Firefox with the Ukrainian language pack. Services run under runit: `dbus`, `elogind`, `polkitd`, `NetworkManager`, `bluetoothd`, `lightdm`. +- Includes guest tools for QEMU/SPICE and VirtualBox. + +#### Das OS look +**_A modern minimal style with a blue and yellow eight-petal star from Ukrainian embroidery._** +- The GTK themes `DasOS` and `DasOS-Dark` build on adw-gtk3 and add the Das OS accent colours. They also style the Xfce panel, which adw-gtk3 leaves black. +- The xfwm4 window frames (rounded corners, round buttons, a red close button on hover) are generated as PNG files by `xfwm4gen` (Go) from a single palette. +- Light and dark wallpapers show the star over a rushnyk-style embroidered ribbon. The same star is the Das OS logo and the menu button. +- Fonts are Inter for the interface and JetBrains Mono in the terminal. Icons are Papirus and the cursor theme is Adwaita. + +#### Desktop defaults +**_The das-settings package configures Xfce without touching files owned by other packages._** +- `/etc/profile.d/das-xdg.sh` puts `/etc/xdg/das` before `/etc/xdg`, and xfconf reads the Das OS defaults from there. +- One bottom panel holds the Whisker menu, window buttons, tray, keyboard layout, volume, notifications, clock and a show-desktop button. +- At the first login, `das-session-init` sets the wallpaper on every connected monitor, because Xfce stores wallpapers per monitor name. +- LightDM uses the GTK greeter with the Das OS theme and wallpaper. + +#### Own package repository +**_Every Das OS file is installed and updated by xbps._** +- `srcpkgs/das-theme` contains the themes, xfwm4 frames, wallpapers and logo. +- `srcpkgs/das-settings` contains the Xfce, LightDM and session defaults. +- `srcpkgs/adw-gtk3` and `srcpkgs/font-jetbrains-mono` package upstream releases that Void does not have. +- `make packages` lays the templates over a shallow clone of void-packages and builds them with `xbps-src` into `out/hostdir/binpkgs`. The ISO build installs them from there. + +#### Containerised build +**_The whole build runs in a Docker container, so only Docker and QEMU are needed on the host._** +1. `make packages` builds the local xbps repository. +2. `make iso` builds the packages and then the ISO with `void-mklive`, using the profile in `live/` (package list, services, root file system overlay). +3. `make qemu` boots the newest ISO with UEFI. `make qemu-bios` uses legacy BIOS, and `make qemu-disk` also attaches a 20 GB virtual disk. + +### 🚀 Building the application +Requirements: Linux with Docker (the user must be in the `docker` group), QEMU and OVMF for testing, and Go 1.22+ for `make test`. About 10 GB of free disk space. +```shell +make iso # build packages and the live ISO (the first run downloads ~2 GB) +make qemu # boot it in QEMU with UEFI +make test # run Go tests +make clean # remove build output, keep the download cache +``` +The ISO appears in `out/iso/dasos--x86_64.iso`. Write it to a USB stick with `dd` or Ventoy. + +**Note:** Das OS is an early version (0.1.0) and the live ISO cannot install itself yet. The Calamares installer, Btrfs snapshots with rollback, the Go utilities `das-welcome` and `das-update`, the developer profile and signed repository hosting are planned for upcoming stages. Das OS is based on Void Linux but is not affiliated with it. + +###### _Made by -=:dAs:=-_ diff --git a/README_ua.md b/README_ua.md new file mode 100644 index 0000000..5d9b6f4 --- /dev/null +++ b/README_ua.md @@ -0,0 +1,99 @@ +## Das OS +### _Легкий і дружній десктоп на базі Void Linux з Xfce та українським колоритом_ + +[![Void Linux](https://img.shields.io/badge/Void_Linux-x86__64_glibc-green)](https://voidlinux.org) [![Xfce](https://img.shields.io/badge/Xfce-4.20-orange)](https://xfce.org) [![runit](https://img.shields.io/badge/init-runit-blue)](https://docs.voidlinux.org/config/services/) [![Go](https://img.shields.io/badge/Go-1.22+-blue)](https://go.dev) [![Version](https://img.shields.io/badge/version-0.1.0-lightgrey)](#) + +🇬🇧 [English version](README.md) + +### 💡 Призначення +Void Linux швидкий, простий і незалежний: runit замість systemd і власний менеджер пакетів xbps. Проте з коробки це гола система, і щоб довести її до щоденного використання, треба кілька годин налаштовувати. Das OS збирає live-образ ISO, який одразу завантажується в налаштований десктоп Xfce. У ньому власне оформлення, український інтерфейс і розкладка та звичні програми. Усе, що додає Das OS, постачається власними xbps-пакетами, тож налаштування можна відтворити й оновлювати, як будь-який інший пакет. + +### 📃 Можливості +- [Live ISO](#LiveISO), що завантажується з UEFI і BIOS одразу в робочий стіл +- [Оформлення Das OS](#Look): світла й темна теми, згенеровані рамки вікон, шпалери та логотип +- [Налаштування за замовчуванням](#Defaults) для Xfce, які не перезаписують стандартні файли +- [Власний репозиторій пакетів](#Repository), який xbps-src збирає з шаблонів у дереві проєкту +- [Збирання в контейнері](#Build), яке працює на будь-якому Linux із Docker, і скрипти QEMU для тестування + +### ⭐ Переваги +- **Void усередині** – швидке завантаження з runit, менеджер пакетів xbps і свіжі програми без ручного налаштування. +- **Легкість** – без systemd, а з Xfce, LightDM і PipeWire система добре працює навіть на старих ноутбуках. +- **Нічого прихованого** – усі зміни постачаються звичайними xbps-пакетами, і `xbps-query -f das-settings` показує, що саме змінено. +- **Контроль у користувача** – налаштування за замовчуванням лежать у `/etc/xdg/das`, тож власні налаштування користувача завжди мають пріоритет і не скидаються після оновлень. +- **Збирається будь-де** – збирання відбувається в контейнері Void, тому хостом може бути Mint, Ubuntu чи Fedora. + +### 📱 Скріншоти + + + + + + + + + + + + + + + + + + + + + +
Live-робочий стілТермінал і рамки віконМеню завантаження (UEFI)
Live-робочий стілТермінал і рамки віконМеню завантаження (UEFI)
Світлі шпалериТемні шпалериРамки вікон
Світлі шпалериТемні шпалериРамки вікон: світлі й темні, активні й неактивні
+ +### 📌 Використання + +#### Live ISO
+**_Live-система сама входить у робочий стіл, без встановлення._** +- Завантажується з UEFI (GRUB) і зі старого BIOS (isolinux), в обох випадках із заставкою Das OS. +- Пункт *graphics disabled* завантажує систему з параметром `nomodeset`, якщо на якомусь комп'ютері екран лишається чорним, а пункт *RAM* копіює всю систему в пам'ять. +- Користувач `live`, пароль `live`, `sudo` працює без пароля. Користувач створюється під час завантаження й існує лише в live-сесії. +- Український інтерфейс (`uk_UA.UTF-8`) і часовий пояс Києва за замовчуванням. Розкладки `us,ua`, перемикання Alt+Shift. +- Є NetworkManager, Bluetooth, звук PipeWire і Firefox з українською мовою. Сервіси працюють під runit: `dbus`, `elogind`, `polkitd`, `NetworkManager`, `bluetoothd`, `lightdm`. +- Є гостьові інструменти для QEMU/SPICE та VirtualBox. + +#### Оформлення Das OS +**_Сучасний мінімалізм із синьо-жовтою восьмипелюстковою зіркою з української вишивки._** +- GTK-теми `DasOS` і `DasOS-Dark` побудовані на adw-gtk3 і додають акцентні кольори Das OS. Вони також оформлюють панель Xfce, яку adw-gtk3 лишає чорною. +- Рамки вікон xfwm4 (заокруглені кути, круглі кнопки, червона кнопка закриття при наведенні) генерує у PNG утиліта `xfwm4gen` (Go) з однієї палітри. +- Світлі й темні шпалери зображують зірку над смугою орнаменту в стилі рушника. Та сама зірка є логотипом Das OS і кнопкою меню. +- Шрифти: Inter для інтерфейсу і JetBrains Mono в терміналі. Іконки Papirus, тема курсора Adwaita. + +#### Налаштування за замовчуванням +**_Пакет das-settings налаштовує Xfce, не чіпаючи файлів інших пакетів._** +- `/etc/profile.d/das-xdg.sh` ставить `/etc/xdg/das` перед `/etc/xdg`, і xfconf бере звідти налаштування Das OS. +- Одна нижня панель містить меню Whisker, кнопки вікон, трей, розкладку клавіатури, гучність, сповіщення, годинник і кнопку «показати стільницю». +- При першому вході `das-session-init` встановлює шпалери на кожному підключеному моніторі, бо Xfce зберігає шпалери за назвою монітора. +- LightDM використовує GTK-вітальню з темою і шпалерами Das OS. + +#### Власний репозиторій пакетів +**_Кожен файл Das OS встановлює й оновлює xbps._** +- `srcpkgs/das-theme` містить теми, рамки xfwm4, шпалери й логотип. +- `srcpkgs/das-settings` містить налаштування Xfce, LightDM і сесії. +- `srcpkgs/adw-gtk3` і `srcpkgs/font-jetbrains-mono` пакують релізи з GitHub, яких немає у Void. +- `make packages` накладає шаблони на неглибокий клон void-packages і збирає їх через `xbps-src` в `out/hostdir/binpkgs`. Збирання ISO встановлює їх звідти. + +#### Збирання в контейнері +**_Усе збирання відбувається в Docker-контейнері, тож на хості потрібні лише Docker і QEMU._** +1. `make packages` збирає локальний xbps-репозиторій. +2. `make iso` збирає пакети, а потім ISO через `void-mklive` за профілем у `live/` (список пакетів, сервіси, оверлей кореневої файлової системи). +3. `make qemu` завантажує найновіший ISO з UEFI. `make qemu-bios` використовує старий BIOS, а `make qemu-disk` ще й підключає віртуальний диск на 20 ГБ. + +### 🚀 Збирання застосунку +Вимоги: Linux із Docker (користувач має бути в групі `docker`), QEMU та OVMF для тестування, Go 1.22+ для `make test`. Приблизно 10 ГБ вільного місця. +```shell +make iso # зібрати пакети й live-ISO (перший запуск завантажує ~2 ГБ) +make qemu # запустити його в QEMU з UEFI +make test # запустити Go-тести +make clean # видалити результати збирання, зберегти кеш завантажень +``` +ISO з'являється в `out/iso/dasos-<версія>-x86_64.iso`. Записати його на флешку можна через `dd` або Ventoy. + +**Примітка:** Das OS — рання версія (0.1.0), і live-ISO поки не вміє встановлюватися на диск. Інсталятор Calamares, знімки Btrfs із відкатом, Go-утиліти `das-welcome` і `das-update`, профіль розробника та підписаний репозиторій на хостингу заплановані на наступні етапи. Das OS базується на Void Linux, але не пов'язана з ним офіційно. + +###### _Made by -=:dAs:=-_ diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/docker/builder/Dockerfile b/docker/builder/Dockerfile new file mode 100644 index 0000000..c5b9a42 --- /dev/null +++ b/docker/builder/Dockerfile @@ -0,0 +1,17 @@ +# Build environment for Das OS packages and ISO images. +# +# Void Linux base on purpose (not Alpine): xbps-src and void-mklive need xbps, +# and packages must be built against the same glibc they ship with. +FROM ghcr.io/void-linux/void-glibc-full:latest + +RUN xbps-install -Syu xbps \ + && xbps-install -yu \ + && xbps-install -y bash dosfstools e2fsprogs git librsvg-utils sudo \ + && xbps-remove -Oy + +# Pinned so ISO builds do not change under us; bump deliberately. +ARG MKLIVE_COMMIT=8ca31d1 +RUN git clone https://github.com/void-linux/void-mklive /opt/void-mklive \ + && git -C /opt/void-mklive checkout --quiet "$MKLIVE_COMMIT" + +WORKDIR /work diff --git a/images/boot-menu.png b/images/boot-menu.png new file mode 100644 index 0000000..56c7b9f Binary files /dev/null and b/images/boot-menu.png differ diff --git a/images/desktop-terminal.png b/images/desktop-terminal.png new file mode 100644 index 0000000..1d2554b Binary files /dev/null and b/images/desktop-terminal.png differ diff --git a/images/desktop.png b/images/desktop.png new file mode 100644 index 0000000..fe92e17 Binary files /dev/null and b/images/desktop.png differ diff --git a/images/wallpaper-dark.png b/images/wallpaper-dark.png new file mode 100644 index 0000000..00e60b6 Binary files /dev/null and b/images/wallpaper-dark.png differ diff --git a/images/wallpaper-light.png b/images/wallpaper-light.png new file mode 100644 index 0000000..f109f94 Binary files /dev/null and b/images/wallpaper-light.png differ diff --git a/images/window-frames.png b/images/window-frames.png new file mode 100644 index 0000000..7c26c7e Binary files /dev/null and b/images/window-frames.png differ diff --git a/live/branding/splash.svg b/live/branding/splash.svg new file mode 100644 index 0000000..a5451a5 --- /dev/null +++ b/live/branding/splash.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/live/include/etc/X11/xorg.conf.d/00-keyboard.conf b/live/include/etc/X11/xorg.conf.d/00-keyboard.conf new file mode 100644 index 0000000..d92a163 --- /dev/null +++ b/live/include/etc/X11/xorg.conf.d/00-keyboard.conf @@ -0,0 +1,6 @@ +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us,ua" + Option "XkbOptions" "grp:alt_shift_toggle" +EndSection diff --git a/live/include/etc/lightdm/.session b/live/include/etc/lightdm/.session new file mode 100644 index 0000000..8c4ca0c --- /dev/null +++ b/live/include/etc/lightdm/.session @@ -0,0 +1 @@ +xfce diff --git a/live/packages.x86_64 b/live/packages.x86_64 new file mode 100644 index 0000000..443e426 --- /dev/null +++ b/live/packages.x86_64 @@ -0,0 +1,60 @@ +# Packages added to the Void base-system in the live image. + +# Desktop session +dbus +elogind +lightdm +polkit +xfce4 +xorg-minimal +xorg-input-drivers +xorg-video-drivers +setxkbmap +xauth +mesa-dri + +# Xfce extras +file-roller +gvfs +gvfs-mtp +mousepad +ristretto +thunar-archive-plugin +xfce4-clipman-plugin +xfce4-screenshooter +xfce4-taskmanager + +# Network, Bluetooth, sound +NetworkManager +network-manager-applet +bluez +blueman +pipewire +alsa-pipewire +wireplumber +pavucontrol + +# Applications +firefox +firefox-i18n-uk +udisks2 +xdg-user-dirs +xdg-utils + +# System tools for the live medium +btrfs-progs +cryptsetup +dosfstools +gptfdisk +ntfs-3g +parted +sudo + +# Virtual machine guests +qemu-ga +spice-vdagent +virtualbox-ose-guest + +# Das OS (local repository) +das-settings +das-theme diff --git a/live/services b/live/services new file mode 100644 index 0000000..aadc533 --- /dev/null +++ b/live/services @@ -0,0 +1,8 @@ +# runit services enabled in the live image (/etc/sv/) +dbus +elogind +polkitd +NetworkManager +bluetoothd +lightdm +spice-vdagentd diff --git a/scripts/builder.sh b/scripts/builder.sh new file mode 100755 index 0000000..84db00b --- /dev/null +++ b/scripts/builder.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Runs a command inside the Void Linux builder container. +# The project is mounted at /work; downloaded packages are cached in out/cache/xbps. +set -euo pipefail + +root=$(cd "$(dirname "$0")/.." && pwd) +image=dasos-builder + +docker build --quiet --tag "$image" "$root/docker/builder" >/dev/null +mkdir -p "$root/out/cache/xbps" + +# --privileged: mklive mounts /proc, /sys, /dev and a loop device for the root image +exec docker run --rm --privileged \ + --volume "$root:/work" \ + --volume "$root/out/cache/xbps:/var/cache/xbps" \ + --env HOST_UID="$(id -u)" \ + --env HOST_GID="$(id -g)" \ + "$image" "$@" diff --git a/scripts/container/build-iso.sh b/scripts/container/build-iso.sh new file mode 100755 index 0000000..153542d --- /dev/null +++ b/scripts/container/build-iso.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Builds the Das OS live ISO with void-mklive from the profile in live/. +# Runs inside the builder container, see scripts/builder.sh. +set -euo pipefail + +repo=/work/out/hostdir/binpkgs +version=$(< /work/VERSION) +iso=/work/out/iso/dasos-${version}-x86_64.iso + +if ! compgen -G "$repo/*-repodata" >/dev/null; then + echo "build-iso: the local package repository is missing, run 'make packages' first" >&2 + exit 1 +fi + +# Reads a list file: one item per line, '#' starts a comment +read_list() { + sed -e 's/#.*//' -e '/^[[:space:]]*$/d' "$1" | xargs +} + +work=$(mktemp -d) +mkdir -p /work/out/iso +trap 'rm -rf "$work"; chown -R "$HOST_UID:$HOST_GID" /work/out/iso' EXIT + +# Root file system overlay: static files from live/include plus generated links +include=$work/include +cp -a /work/live/include "$include" +ln -sf /usr/share/zoneinfo/Europe/Kyiv "$include/etc/localtime" +# PipeWire as the sound server, wired up the same way as Void's own live images +mkdir -p "$include"/etc/xdg/autostart "$include"/etc/pipewire/pipewire.conf.d "$include"/etc/alsa/conf.d +ln -sf /usr/share/applications/pipewire.desktop "$include"/etc/xdg/autostart/ +ln -sf /usr/share/examples/wireplumber/10-wireplumber.conf "$include"/etc/pipewire/pipewire.conf.d/ +ln -sf /usr/share/examples/pipewire/20-pipewire-pulse.conf "$include"/etc/pipewire/pipewire.conf.d/ +ln -sf /usr/share/alsa/alsa.conf.d/50-pipewire.conf "$include"/etc/alsa/conf.d/ +ln -sf /usr/share/alsa/alsa.conf.d/99-pipewire-default.conf "$include"/etc/alsa/conf.d/ + +# mklive's dracut module creates the live user, hostname and passwords at boot. +# Rebrand its defaults in this container's copy (the image itself stays untouched). +adduser=/opt/void-mklive/dracut/vmklive/adduser.sh +for pattern in 'echo void-live >' 'voidlinux'; do + if ! grep -q "$pattern" "$adduser"; then + echo "build-iso: '$pattern' not found in $adduser, mklive changed; update this script" >&2 + exit 1 + fi +done +sed -i -e 's/echo void-live >/echo dasos >/' -e 's/voidlinux/live/g' "$adduser" + +rsvg-convert /work/live/branding/splash.svg --output "$work/splash.png" +rm -f "$iso" + +cd /opt/void-mklive +SPLASH_IMAGE=$work/splash.png ./mklive.sh \ + -a x86_64 \ + -r "$repo" \ + -c /var/cache/xbps \ + -p "$(read_list /work/live/packages.x86_64)" \ + -S "$(read_list /work/live/services)" \ + -I "$include" \ + -l uk_UA.UTF-8 \ + -k us \ + -T "Das OS" \ + -C "live.user=live quiet loglevel=3" \ + -o "$iso" diff --git a/scripts/container/build-packages.sh b/scripts/container/build-packages.sh new file mode 100755 index 0000000..e81ad99 --- /dev/null +++ b/scripts/container/build-packages.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Builds every template in srcpkgs/ with xbps-src into the local repository +# out/hostdir/binpkgs. Runs inside the builder container, see scripts/builder.sh. +# +# xbps-src needs the whole void-packages tree (build styles, shared hooks), so our +# templates are overlaid onto a shallow clone of it in out/void-packages. +set -euo pipefail + +vp=/work/out/void-packages +hostdir=/work/out/hostdir + +# xbps-src refuses to run as root. Like Void's own CI, build as an unprivileged +# member of xbuilder (allowed to use xbps-uchroot); the host's UID keeps out/ owned by the user. +useradd --uid "$HOST_UID" --groups xbuilder --no-create-home builder +as_builder() { + sudo --preserve-env=HOME -u builder "$@" +} +export HOME=/tmp +mkdir -p "$hostdir" +chown "$HOST_UID:$HOST_GID" /work/out "$hostdir" + +if [[ -d $vp/.git ]]; then + echo "==> Updating void-packages" + as_builder git -C "$vp" fetch --quiet --depth 1 origin master + as_builder git -C "$vp" reset --quiet --hard FETCH_HEAD + as_builder git -C "$vp" clean --quiet -fd srcpkgs +else + echo "==> Cloning void-packages" + as_builder git clone --quiet --depth 1 https://github.com/void-linux/void-packages "$vp" +fi + +cat > "$vp/etc/conf" </dev/null; then + echo "warning: $name is now in void-packages, consider dropping our template" >&2 + fi + rm -rf "$vp/srcpkgs/$name" + as_builder cp -rL "$dir" "$vp/srcpkgs/$name" + names+=("$name") +done + +cd "$vp" +# The bootstrapped masterdir stays in out/void-packages, so later builds skip this step +if [[ ! -f masterdir-x86_64/.xbps_chroot_init ]]; then + as_builder ./xbps-src -H "$hostdir" binary-bootstrap +fi +for name in "${names[@]}"; do + echo "==> Building $name" + as_builder ./xbps-src -H "$hostdir" -f pkg "$name" +done + +echo "==> Repository: $hostdir/binpkgs" +ls -1 "$hostdir"/binpkgs/*.xbps diff --git a/scripts/run-qemu.sh b/scripts/run-qemu.sh new file mode 100755 index 0000000..ba218c2 --- /dev/null +++ b/scripts/run-qemu.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Boots a Das OS ISO in QEMU (KVM when available). +# +# Usage: scripts/run-qemu.sh [--bios] [--disk] [path/to/image.iso] +# --bios boot with legacy BIOS instead of UEFI +# --disk attach out/qemu/disk.qcow2 (20 GB, created on first use) to test installing +# Without a path the newest ISO in out/iso is used. +set -euo pipefail + +root=$(cd "$(dirname "$0")/.." && pwd) +state=$root/out/qemu +firmware=uefi +disk=false +iso="" + +die() { + echo "run-qemu: $*" >&2 + exit 1 +} + +for arg in "$@"; do + case $arg in + --bios) firmware=bios ;; + --disk) disk=true ;; + -h | --help) + sed -n '2,8s/^# \{0,1\}//p' "$0" + exit 0 + ;; + -*) die "unknown option: $arg" ;; + *) iso=$arg ;; + esac +done + +if [[ -z $iso ]]; then + iso=$(ls -t "$root"/out/iso/*.iso 2>/dev/null | head -n 1) || true +fi +[[ -n $iso && -f $iso ]] || die "no ISO found, run 'make iso' first" + +args=( + -machine q35 -smp 4 -m 4096 + -device virtio-vga -display gtk,zoom-to-fit=on + -device virtio-net-pci,netdev=net0 -netdev user,id=net0 + -device qemu-xhci -device usb-tablet + -cdrom "$iso" -boot order=d +) + +if [[ -w /dev/kvm ]]; then + args+=(-accel kvm -cpu host) +else + echo "run-qemu: /dev/kvm is not accessible, falling back to slow emulation" >&2 + args+=(-accel tcg -cpu max) +fi + +mkdir -p "$state" + +if [[ $firmware == uefi ]]; then + code="" + # Debian/Ubuntu/Mint, then Arch, then Fedora file names + for candidate in /usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/edk2/x64/OVMF_CODE.4m.fd /usr/share/edk2/ovmf/OVMF_CODE.fd; do + [[ -f $candidate ]] && code=$candidate && break + done + [[ -n $code ]] || die "UEFI firmware (OVMF) not found, install the ovmf package or use --bios" + vars_template=$(dirname "$code")/$(basename "$code" | sed 's/CODE/VARS/') + [[ -f $state/OVMF_VARS.fd ]] || cp "$vars_template" "$state/OVMF_VARS.fd" + args+=( + -drive "if=pflash,format=raw,readonly=on,file=$code" + -drive "if=pflash,format=raw,file=$state/OVMF_VARS.fd" + ) +fi + +if [[ $disk == true ]]; then + [[ -f $state/disk.qcow2 ]] || qemu-img create -f qcow2 "$state/disk.qcow2" 20G + args+=(-drive "file=$state/disk.qcow2,if=virtio,format=qcow2") +fi + +exec qemu-system-x86_64 "${args[@]}" diff --git a/srcpkgs/adw-gtk3/template b/srcpkgs/adw-gtk3/template new file mode 100644 index 0000000..6e624f3 --- /dev/null +++ b/srcpkgs/adw-gtk3/template @@ -0,0 +1,18 @@ +# Template file for 'adw-gtk3' +# Not in the Void repositories; the base of the Das OS GTK themes. +pkgname=adw-gtk3 +version=6.5 +revision=1 +create_wrksrc=yes +short_desc="Unofficial GTK3 port of libadwaita" +maintainer="Das " +license="LGPL-2.1-or-later" +homepage="https://github.com/lassekongo83/adw-gtk3" +distfiles="https://github.com/lassekongo83/adw-gtk3/releases/download/v${version}/adw-gtk3v${version}.tar.xz" +checksum=a81780fadfc432be0fc3d89c4ebb41aa28e4f032d42c36f9789c57dd10cfa41c + +do_install() { + vmkdir usr/share/themes + vcopy adw-gtk3 usr/share/themes + vcopy adw-gtk3-dark usr/share/themes +} diff --git a/srcpkgs/das-settings/files/LICENSE b/srcpkgs/das-settings/files/LICENSE new file mode 120000 index 0000000..5853aae --- /dev/null +++ b/srcpkgs/das-settings/files/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/srcpkgs/das-settings/files/autostart/das-session-init.desktop b/srcpkgs/das-settings/files/autostart/das-session-init.desktop new file mode 100644 index 0000000..08189fe --- /dev/null +++ b/srcpkgs/das-settings/files/autostart/das-session-init.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=Das OS session setup +Comment=Applies Das OS defaults that depend on the user's monitors +Exec=/usr/libexec/das/das-session-init +OnlyShowIn=XFCE; +NoDisplay=true diff --git a/srcpkgs/das-settings/files/bin/das-session-init b/srcpkgs/das-settings/files/bin/das-session-init new file mode 100755 index 0000000..f82ba16 --- /dev/null +++ b/srcpkgs/das-settings/files/bin/das-session-init @@ -0,0 +1,35 @@ +#!/bin/bash +# Applies Das OS per-user defaults that cannot be shipped as static xfconf files. +# xfdesktop keys wallpapers by monitor connector name (eDP-1, HDMI-1, Virtual-1...), +# which is only known inside the session. Runs once per user. +set -u + +marker="${XDG_CONFIG_HOME:-$HOME/.config}/das/session-init.done" +[[ -e $marker ]] && exit 0 + +wallpaper=/usr/share/backgrounds/das/das-light.svg +if [[ $(xfconf-query -c xsettings -p /Net/ThemeName 2>/dev/null) == *-Dark ]]; then + wallpaper=/usr/share/backgrounds/das/das-dark.svg +fi + +# Sets an xfconf property, creating it when it does not exist yet. +set_prop() { + local prop=$1 type=$2 value=$3 + xfconf-query -c xfce4-desktop -p "$prop" -s "$value" 2>/dev/null || + xfconf-query -c xfce4-desktop -p "$prop" -n -t "$type" -s "$value" +} + +# "xrandr --listactivemonitors" prints a header, then one line per monitor ending in its connector name. +monitors=$(xrandr --listactivemonitors 2>/dev/null | awk 'NR > 1 { print $NF }') +if [[ -z $monitors ]]; then + echo "das-session-init: no active monitors found, will retry at next login" >&2 + exit 1 +fi + +for monitor in $monitors; do + base="/backdrop/screen0/monitor${monitor}/workspace0" + set_prop "$base/last-image" string "$wallpaper" + set_prop "$base/image-style" int 5 # zoomed: fill the screen, keep proportions +done + +mkdir -p "${marker%/*}" && touch "$marker" diff --git a/srcpkgs/das-settings/files/lightdm/50-das-greeter.conf b/srcpkgs/das-settings/files/lightdm/50-das-greeter.conf new file mode 100644 index 0000000..0f0964f --- /dev/null +++ b/srcpkgs/das-settings/files/lightdm/50-das-greeter.conf @@ -0,0 +1,13 @@ +# Das OS look for the login screen. +[greeter] +background=/usr/share/backgrounds/das/das-light.svg +theme-name=DasOS +icon-theme-name=Papirus +font-name=Inter 11 +cursor-theme-name=Adwaita +cursor-theme-size=24 +xft-antialias=true +xft-hintstyle=hintslight +xft-rgba=rgb +indicators=~host;~spacer;~clock;~spacer;~layout;~language;~session;~a11y;~power +clock-format=%H:%M %d.%m.%Y diff --git a/srcpkgs/das-settings/files/lightdm/50-das.conf b/srcpkgs/das-settings/files/lightdm/50-das.conf new file mode 100644 index 0000000..7572bd5 --- /dev/null +++ b/srcpkgs/das-settings/files/lightdm/50-das.conf @@ -0,0 +1,4 @@ +# Das OS: LightDM with the GTK greeter starting an Xfce session. +[Seat:*] +greeter-session=lightdm-gtk-greeter +user-session=xfce diff --git a/srcpkgs/das-settings/files/profile.d/das-xdg.sh b/srcpkgs/das-settings/files/profile.d/das-xdg.sh new file mode 100644 index 0000000..d2c01c4 --- /dev/null +++ b/srcpkgs/das-settings/files/profile.d/das-xdg.sh @@ -0,0 +1,6 @@ +# Das OS desktop defaults (/etc/xdg/das) take precedence over the stock Xfce ones in /etc/xdg. +# LightDM sources /etc/profile before the session starts, and startxfce4 keeps this order. +case ":${XDG_CONFIG_DIRS:-}:" in + *:/etc/xdg/das:*) ;; + *) export XDG_CONFIG_DIRS="/etc/xdg/das:${XDG_CONFIG_DIRS:-/etc/xdg}" ;; +esac diff --git a/srcpkgs/das-settings/files/xfconf/xfce4-desktop.xml b/srcpkgs/das-settings/files/xfconf/xfce4-desktop.xml new file mode 100644 index 0000000..079b1e4 --- /dev/null +++ b/srcpkgs/das-settings/files/xfconf/xfce4-desktop.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-settings/files/xfconf/xfce4-panel.xml b/srcpkgs/das-settings/files/xfconf/xfce4-panel.xml new file mode 100644 index 0000000..77b3d7c --- /dev/null +++ b/srcpkgs/das-settings/files/xfconf/xfce4-panel.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-settings/files/xfconf/xfce4-terminal.xml b/srcpkgs/das-settings/files/xfconf/xfce4-terminal.xml new file mode 100644 index 0000000..87ddc48 --- /dev/null +++ b/srcpkgs/das-settings/files/xfconf/xfce4-terminal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-settings/files/xfconf/xfwm4.xml b/srcpkgs/das-settings/files/xfconf/xfwm4.xml new file mode 100644 index 0000000..04570ec --- /dev/null +++ b/srcpkgs/das-settings/files/xfconf/xfwm4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/srcpkgs/das-settings/files/xfconf/xsettings.xml b/srcpkgs/das-settings/files/xfconf/xsettings.xml new file mode 100644 index 0000000..befa45b --- /dev/null +++ b/srcpkgs/das-settings/files/xfconf/xsettings.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-settings/template b/srcpkgs/das-settings/template new file mode 100644 index 0000000..87fbd71 --- /dev/null +++ b/srcpkgs/das-settings/template @@ -0,0 +1,34 @@ +# Template file for 'das-settings' +# In-tree package: sources are in files/ (FILESDIR). +# +# Defaults go to /etc/xdg/das, which /etc/profile.d/das-xdg.sh puts in front of +# /etc/xdg: stock Xfce files stay untouched and users' own changes still win. +pkgname=das-settings +version=0.1.0 +revision=1 +create_wrksrc=yes +depends="das-theme lightdm lightdm-gtk-greeter xfce4-notifyd xfce4-panel + xfce4-pulseaudio-plugin xfce4-whiskermenu-plugin xfce4-xkb-plugin xfconf + xfdesktop xfwm4 xrandr" +short_desc="Das OS desktop defaults: Xfce panel, theme, fonts, login screen" +maintainer="Das " +license="MIT" +homepage="https://git.das.org.ua/das/Das-OS" +conf_files="/etc/profile.d/das-xdg.sh /etc/xdg/das/xfce4/xfconf/xfce-perchannel-xml/*.xml" + +do_install() { + local xml + + vinstall "${FILESDIR}/profile.d/das-xdg.sh" 644 etc/profile.d + for xml in "${FILESDIR}"/xfconf/*.xml; do + vinstall "${xml}" 644 etc/xdg/das/xfce4/xfconf/xfce-perchannel-xml + done + + vinstall "${FILESDIR}/bin/das-session-init" 755 usr/libexec/das + vinstall "${FILESDIR}/autostart/das-session-init.desktop" 644 etc/xdg/autostart + + vinstall "${FILESDIR}/lightdm/50-das.conf" 644 usr/share/lightdm/lightdm.conf.d + vinstall "${FILESDIR}/lightdm/50-das-greeter.conf" 644 \ + usr/share/lightdm/lightdm-gtk-greeter.conf.d 50-das.conf + vlicense "${FILESDIR}/LICENSE" +} diff --git a/srcpkgs/das-theme/files/LICENSE b/srcpkgs/das-theme/files/LICENSE new file mode 120000 index 0000000..5853aae --- /dev/null +++ b/srcpkgs/das-theme/files/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/srcpkgs/das-theme/files/backgrounds/das-dark.svg b/srcpkgs/das-theme/files/backgrounds/das-dark.svg new file mode 100644 index 0000000..349a7e8 --- /dev/null +++ b/srcpkgs/das-theme/files/backgrounds/das-dark.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-theme/files/backgrounds/das-light.svg b/srcpkgs/das-theme/files/backgrounds/das-light.svg new file mode 100644 index 0000000..49865c2 --- /dev/null +++ b/srcpkgs/das-theme/files/backgrounds/das-light.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-theme/files/branding/das-logo.svg b/srcpkgs/das-theme/files/branding/das-logo.svg new file mode 100644 index 0000000..deb666d --- /dev/null +++ b/srcpkgs/das-theme/files/branding/das-logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcpkgs/das-theme/files/gtk/DasOS-Dark/gtk-3.0/gtk.css b/srcpkgs/das-theme/files/gtk/DasOS-Dark/gtk-3.0/gtk.css new file mode 100644 index 0000000..f6b2426 --- /dev/null +++ b/srcpkgs/das-theme/files/gtk/DasOS-Dark/gtk-3.0/gtk.css @@ -0,0 +1,14 @@ +/* Das OS dark: adw-gtk3-dark with the Das OS accent colours. */ +@import url("../../adw-gtk3-dark/gtk-3.0/gtk.css"); + +@define-color accent_bg_color #3f78e4; +@define-color accent_fg_color #ffffff; +@define-color accent_color #7aa7f5; + +/* A dark panel that matches the window frames instead of pure black. */ +@define-color panel_bg_color #1b1f27; +@define-color panel_fg_color #e3e6ec; + +.xfce4-panel.background { + border-top: 1px solid alpha(@panel_fg_color, 0.08); +} diff --git a/srcpkgs/das-theme/files/gtk/DasOS-Dark/index.theme b/srcpkgs/das-theme/files/gtk/DasOS-Dark/index.theme new file mode 100644 index 0000000..d3be016 --- /dev/null +++ b/srcpkgs/das-theme/files/gtk/DasOS-Dark/index.theme @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=X-GNOME-Metatheme +Name=DasOS-Dark +Comment=Das OS dark theme +Encoding=UTF-8 + +[X-GNOME-Metatheme] +GtkTheme=DasOS-Dark +MetacityTheme=DasOS-Dark +IconTheme=Papirus +CursorTheme=Adwaita +ButtonLayout=menu:minimize,maximize,close diff --git a/srcpkgs/das-theme/files/gtk/DasOS/gtk-3.0/gtk.css b/srcpkgs/das-theme/files/gtk/DasOS/gtk-3.0/gtk.css new file mode 100644 index 0000000..4fb8e75 --- /dev/null +++ b/srcpkgs/das-theme/files/gtk/DasOS/gtk-3.0/gtk.css @@ -0,0 +1,23 @@ +/* Das OS light: adw-gtk3 with the Das OS accent colours. */ +@import url("../../adw-gtk3/gtk-3.0/gtk.css"); + +@define-color accent_bg_color #2f6fde; +@define-color accent_fg_color #ffffff; +@define-color accent_color #2a63c8; + +/* adw-gtk3 paints the Xfce panel black like the GNOME top bar; Das OS keeps it light. */ +@define-color panel_bg_color #f6f7fa; +@define-color panel_fg_color #1f2430; + +.xfce4-panel.background { + border-top: 1px solid alpha(@panel_fg_color, 0.12); +} + +/* adw-gtk3 draws white desktop labels with a shadow, made for dark wallpapers. */ +XfdesktopIconView.view { + color: #1f2430; +} + +XfdesktopIconView.view .label { + text-shadow: none; +} diff --git a/srcpkgs/das-theme/files/gtk/DasOS/index.theme b/srcpkgs/das-theme/files/gtk/DasOS/index.theme new file mode 100644 index 0000000..79bf54a --- /dev/null +++ b/srcpkgs/das-theme/files/gtk/DasOS/index.theme @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=X-GNOME-Metatheme +Name=DasOS +Comment=Das OS light theme +Encoding=UTF-8 + +[X-GNOME-Metatheme] +GtkTheme=DasOS +MetacityTheme=DasOS +IconTheme=Papirus +CursorTheme=Adwaita +ButtonLayout=menu:minimize,maximize,close diff --git a/srcpkgs/das-theme/files/xfwm4gen/go.mod b/srcpkgs/das-theme/files/xfwm4gen/go.mod new file mode 100644 index 0000000..8948b50 --- /dev/null +++ b/srcpkgs/das-theme/files/xfwm4gen/go.mod @@ -0,0 +1,3 @@ +module dasos/xfwm4gen + +go 1.22 diff --git a/srcpkgs/das-theme/files/xfwm4gen/main.go b/srcpkgs/das-theme/files/xfwm4gen/main.go new file mode 100644 index 0000000..3d866b2 --- /dev/null +++ b/srcpkgs/das-theme/files/xfwm4gen/main.go @@ -0,0 +1,343 @@ +// Command xfwm4gen renders the Das OS xfwm4 window decorations. +// +// xfwm4 themes are a directory of image slices plus a themerc file. Drawing +// ~90 slices by hand is error-prone, so they are generated from one palette: +// +// xfwm4gen -variant light -out build/DasOS/xfwm4 +package main + +import ( + "flag" + "fmt" + "image" + "image/color" + "image/png" + "log" + "math" + "os" + "path/filepath" + "strings" +) + +const ( + titleHeight = 32 + frameWidth = 3 + cornerRadius = 8.0 + cornerWidth = 12 + buttonWidth = 30 + circleRadius = 11.0 + glyphHalf = 4.0 + strokeWidth = 1.5 + samples = 4 // supersampling per axis, gives 16 samples per pixel +) + +type palette struct { + titleBg, border, separator, glyph, text color.NRGBA +} + +type variant struct { + active, inactive palette + hover, pressed color.NRGBA + closeHover, closePressed, onClose color.NRGBA +} + +var variants = map[string]variant{ + "light": { + active: palette{ + titleBg: rgb(0xe9ecf1), border: rgb(0xb4bcc9), separator: rgb(0xd3d8e1), + glyph: rgb(0x2e3440), text: rgb(0x1f2430), + }, + inactive: palette{ + titleBg: rgb(0xf4f5f8), border: rgb(0xccd2db), separator: rgb(0xe2e5eb), + glyph: rgb(0x9aa1ad), text: rgb(0x8a909c), + }, + hover: rgb(0xd5dae3), pressed: rgb(0xc3cad6), + closeHover: rgb(0xe0464c), closePressed: rgb(0xc2353b), onClose: rgb(0xffffff), + }, + "dark": { + active: palette{ + titleBg: rgb(0x2b2f38), border: rgb(0x0e1014), separator: rgb(0x1d2026), + glyph: rgb(0xe3e6ec), text: rgb(0xeceff4), + }, + inactive: palette{ + titleBg: rgb(0x24272e), border: rgb(0x15171b), separator: rgb(0x1b1d22), + glyph: rgb(0x7c8391), text: rgb(0x8a909c), + }, + hover: rgb(0x3b404b), pressed: rgb(0x4a505d), + closeHover: rgb(0xe0464c), closePressed: rgb(0xc2353b), onClose: rgb(0xffffff), + }, +} + +func rgb(v uint32) color.NRGBA { + return color.NRGBA{R: uint8(v >> 16), G: uint8(v >> 8), B: uint8(v), A: 0xff} +} + +func hex(c color.NRGBA) string { + return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B) +} + +// shader returns the colour of a point in pixel coordinates; transparent outside the shape. +type shader func(x, y float64) color.NRGBA + +// render rasterises a shader with supersampling, averaging in premultiplied space. +func render(w, h int, fn shader) *image.NRGBA { + img := image.NewNRGBA(image.Rect(0, 0, w, h)) + const n = samples * samples + for py := 0; py < h; py++ { + for px := 0; px < w; px++ { + var r, g, b, a float64 + for sy := 0; sy < samples; sy++ { + for sx := 0; sx < samples; sx++ { + c := fn(float64(px)+(float64(sx)+0.5)/samples, float64(py)+(float64(sy)+0.5)/samples) + ca := float64(c.A) / 255 + r += float64(c.R) * ca + g += float64(c.G) * ca + b += float64(c.B) * ca + a += ca + } + } + if a == 0 { + continue + } + img.SetNRGBA(px, py, color.NRGBA{ + R: uint8(math.Round(r / a)), G: uint8(math.Round(g / a)), B: uint8(math.Round(b / a)), + A: uint8(math.Round(a / n * 255)), + }) + } + } + return img +} + +// titleRow is the colour of the title bar at height y: top border, fill, separator above the client. +func titleRow(p palette, y float64) color.NRGBA { + switch { + case y < 1: + return p.border + case y >= titleHeight-1: + return p.separator + default: + return p.titleBg + } +} + +// insideRounded reports whether (x, y) lies in a shape whose top-left corner is +// rounded with radius r at origin (ox, oy) and which extends right and down. +func insideRounded(x, y, ox, oy, r float64) bool { + if x < ox || y < oy { + return false + } + if x < ox+r && y < oy+r { + return math.Hypot(x-(ox+r), y-(oy+r)) <= r + } + return true +} + +func topLeft(p palette) shader { + return func(x, y float64) color.NRGBA { + switch { + case !insideRounded(x, y, 0, 0, cornerRadius): + return color.NRGBA{} + case !insideRounded(x, y, 1, 1, cornerRadius-1): + return p.border + case x < frameWidth: + return p.titleBg + default: + return titleRow(p, y) + } + } +} + +func mirror(fn shader, w int) shader { + return func(x, y float64) color.NRGBA { return fn(float64(w)-x, y) } +} + +func leftEdge(p palette) shader { + return func(x, _ float64) color.NRGBA { + if x < 1 { + return p.border + } + return p.titleBg + } +} + +func bottomEdge(p palette) shader { + return func(_, y float64) color.NRGBA { + if y >= frameWidth-1 { + return p.border + } + return p.titleBg + } +} + +func bottomLeft(p palette) shader { + return func(x, y float64) color.NRGBA { + if x < 1 || y >= frameWidth-1 { + return p.border + } + return p.titleBg + } +} + +type segment struct{ x1, y1, x2, y2 float64 } + +// glyph is a button symbol drawn around the button centre. +type glyph struct { + lines []segment + ring float64 // stroked circle radius, 0 for none + dot float64 // filled circle radius, 0 for none +} + +func square(x1, y1, x2, y2 float64) []segment { + return []segment{{x1, y1, x2, y1}, {x2, y1, x2, y2}, {x2, y2, x1, y2}, {x1, y2, x1, y1}} +} + +var glyphs = func() map[string]glyph { + const g = glyphHalf + return map[string]glyph{ + "close": {lines: []segment{{-g, -g, g, g}, {-g, g, g, -g}}}, + "hide": {lines: []segment{{-g, 3, g, 3}}}, + "maximize": {lines: square(-g, -g, g, g)}, + "maximize-toggled": {lines: append(square(-g, -g+2.5, g-2.5, g), segment{-g + 2.5, -g, g, -g}, segment{g, -g, g, g - 2.5})}, + "menu": {lines: []segment{{-g, -3, g, -3}, {-g, 0, g, 0}, {-g, 3, g, 3}}}, + "shade": {lines: []segment{{-g, 2, 0, -2}, {0, -2, g, 2}}}, + "shade-toggled": {lines: []segment{{-g, -2, 0, 2}, {0, 2, g, -2}}}, + "stick": {ring: g}, + "stick-toggled": {dot: g}, + } +}() + +func distToSegment(px, py float64, s segment) float64 { + dx, dy := s.x2-s.x1, s.y2-s.y1 + t := ((px-s.x1)*dx + (py-s.y1)*dy) / (dx*dx + dy*dy) + t = math.Max(0, math.Min(1, t)) + return math.Hypot(px-(s.x1+t*dx), py-(s.y1+t*dy)) +} + +func (g glyph) covers(x, y float64) bool { + for _, s := range g.lines { + if distToSegment(x, y, s) <= strokeWidth/2 { + return true + } + } + r := math.Hypot(x, y) + if g.ring > 0 && math.Abs(r-g.ring) <= strokeWidth/2 { + return true + } + return g.dot > 0 && r <= g.dot +} + +func button(p palette, g glyph, circle, ink color.NRGBA) shader { + cx, cy := buttonWidth/2.0, titleHeight/2.0 + return func(x, y float64) color.NRGBA { + dx, dy := x-cx, y-cy + switch { + case g.covers(dx, dy): + return ink + case circle.A != 0 && math.Hypot(dx, dy) <= circleRadius: + return circle + default: + return titleRow(p, y) + } + } +} + +type piece struct { + name string + w, h int + fn shader +} + +func pieces(v variant) []piece { + var out []piece + for _, state := range []string{"active", "inactive"} { + p := v.active + if state == "inactive" { + p = v.inactive + } + out = append(out, + piece{"top-left-" + state, cornerWidth, titleHeight, topLeft(p)}, + piece{"top-right-" + state, cornerWidth, titleHeight, mirror(topLeft(p), cornerWidth)}, + piece{"left-" + state, frameWidth, 1, leftEdge(p)}, + piece{"right-" + state, frameWidth, 1, mirror(leftEdge(p), frameWidth)}, + piece{"bottom-" + state, 1, frameWidth, bottomEdge(p)}, + piece{"bottom-left-" + state, frameWidth, frameWidth, bottomLeft(p)}, + piece{"bottom-right-" + state, frameWidth, frameWidth, mirror(bottomLeft(p), frameWidth)}, + ) + for i := 1; i <= 5; i++ { + out = append(out, piece{fmt.Sprintf("title-%d-%s", i, state), 1, titleHeight, + func(_, y float64) color.NRGBA { return titleRow(p, y) }}) + } + } + for name, g := range glyphs { + hover, pressed, ink := v.hover, v.pressed, v.active.glyph + if name == "close" { + hover, pressed, ink = v.closeHover, v.closePressed, v.onClose + } + out = append(out, + piece{name + "-active", buttonWidth, titleHeight, button(v.active, g, color.NRGBA{}, v.active.glyph)}, + piece{name + "-inactive", buttonWidth, titleHeight, button(v.inactive, g, color.NRGBA{}, v.inactive.glyph)}, + piece{name + "-prelight", buttonWidth, titleHeight, button(v.active, g, hover, ink)}, + piece{name + "-pressed", buttonWidth, titleHeight, button(v.active, g, pressed, ink)}, + ) + } + return out +} + +func themerc(v variant) string { + return strings.Join([]string{ + "# Generated by xfwm4gen, do not edit.", + "active_text_color=" + hex(v.active.text), + "inactive_text_color=" + hex(v.inactive.text), + "button_offset=4", + "button_spacing=0", + "full_width_title=true", + "maximized_offset=0", + "show_app_icon=true", + "title_alignment=center", + "title_horizontal_offset=0", + "title_shadow_active=false", + "title_shadow_inactive=false", + "title_vertical_offset_active=0", + "title_vertical_offset_inactive=0", + "", + }, "\n") +} + +func writePNG(path string, img image.Image) error { + f, err := os.Create(path) + if err != nil { + return err + } + if err := png.Encode(f, img); err != nil { + f.Close() + return fmt.Errorf("encode %s: %w", path, err) + } + return f.Close() +} + +func generate(v variant, dir string) error { + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + for _, p := range pieces(v) { + if err := writePNG(filepath.Join(dir, p.name+".png"), render(p.w, p.h, p.fn)); err != nil { + return err + } + } + return os.WriteFile(filepath.Join(dir, "themerc"), []byte(themerc(v)), 0o644) +} + +func main() { + name := flag.String("variant", "light", "colour variant: light or dark") + out := flag.String("out", "", "output directory (the theme's xfwm4 folder)") + flag.Parse() + + v, ok := variants[*name] + if !ok || *out == "" { + flag.Usage() + os.Exit(2) + } + if err := generate(v, *out); err != nil { + log.Fatalf("xfwm4gen: %v", err) + } +} diff --git a/srcpkgs/das-theme/files/xfwm4gen/main_test.go b/srcpkgs/das-theme/files/xfwm4gen/main_test.go new file mode 100644 index 0000000..d32dfa2 --- /dev/null +++ b/srcpkgs/das-theme/files/xfwm4gen/main_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +// xfwm4 falls back to an ugly built-in frame when any of these slices is missing. +var requiredSlices = []string{ + "top-left", "top-right", "left", "right", "bottom", "bottom-left", "bottom-right", + "title-1", "title-2", "title-3", "title-4", "title-5", +} + +var requiredButtons = []string{ + "close", "hide", "maximize", "maximize-toggled", "menu", + "shade", "shade-toggled", "stick", "stick-toggled", +} + +func TestGenerateWritesCompleteTheme(t *testing.T) { + for name, v := range variants { + t.Run(name, func(t *testing.T) { + dir := t.TempDir() + if err := generate(v, dir); err != nil { + t.Fatal(err) + } + want := []string{"themerc"} + for _, s := range requiredSlices { + want = append(want, s+"-active.png", s+"-inactive.png") + } + for _, b := range requiredButtons { + for _, state := range []string{"active", "inactive", "prelight", "pressed"} { + want = append(want, b+"-"+state+".png") + } + } + for _, f := range want { + if _, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("missing %s", f) + } + } + }) + } +} + +func TestTopLeftCornerIsTransparentOutsideRadius(t *testing.T) { + img := render(cornerWidth, titleHeight, topLeft(variants["light"].active)) + if a := img.NRGBAAt(0, 0).A; a != 0 { + t.Errorf("corner pixel alpha = %d, want 0", a) + } + if a := img.NRGBAAt(cornerWidth-1, titleHeight/2).A; a != 0xff { + t.Errorf("title pixel alpha = %d, want 255", a) + } +} + +func TestButtonsMatchTitleHeight(t *testing.T) { + for _, p := range pieces(variants["dark"]) { + if p.w == buttonWidth && p.h != titleHeight { + t.Errorf("%s height = %d, want %d", p.name, p.h, titleHeight) + } + } +} diff --git a/srcpkgs/das-theme/template b/srcpkgs/das-theme/template new file mode 100644 index 0000000..505dea2 --- /dev/null +++ b/srcpkgs/das-theme/template @@ -0,0 +1,39 @@ +# Template file for 'das-theme' +# In-tree package: sources are in files/ (FILESDIR). +pkgname=das-theme +version=0.1.0 +revision=1 +create_wrksrc=yes +hostmakedepends="go" +depends="adw-gtk3 adwaita-icon-theme font-inter font-jetbrains-mono noto-fonts-ttf + noto-fonts-emoji papirus-icon-theme" +short_desc="Das OS look: GTK and xfwm4 themes, wallpapers and logo" +maintainer="Das " +license="MIT" +homepage="https://git.das.org.ua/das/Das-OS" + +do_build() { + cp -rL "${FILESDIR}/." . + cd xfwm4gen + export GOCACHE="${wrksrc}/.gocache" GOTOOLCHAIN=local GOFLAGS="-trimpath" + go test ./... + go build -o xfwm4gen . + ./xfwm4gen -variant light -out "${wrksrc}/gtk/DasOS/xfwm4" + ./xfwm4gen -variant dark -out "${wrksrc}/gtk/DasOS-Dark/xfwm4" +} + +do_install() { + local theme + for theme in DasOS DasOS-Dark; do + vinstall "gtk/${theme}/index.theme" 644 "usr/share/themes/${theme}" + vinstall "gtk/${theme}/gtk-3.0/gtk.css" 644 "usr/share/themes/${theme}/gtk-3.0" + vmkdir "usr/share/themes/${theme}/xfwm4" + install -m644 "gtk/${theme}/xfwm4/"* "${DESTDIR}/usr/share/themes/${theme}/xfwm4/" + done + + vinstall backgrounds/das-light.svg 644 usr/share/backgrounds/das + vinstall backgrounds/das-dark.svg 644 usr/share/backgrounds/das + vinstall branding/das-logo.svg 644 usr/share/icons/hicolor/scalable/apps + vinstall branding/das-logo.svg 644 usr/share/pixmaps + vlicense LICENSE +} diff --git a/srcpkgs/font-jetbrains-mono/template b/srcpkgs/font-jetbrains-mono/template new file mode 100644 index 0000000..67cfb68 --- /dev/null +++ b/srcpkgs/font-jetbrains-mono/template @@ -0,0 +1,20 @@ +# Template file for 'font-jetbrains-mono' +# Not in the Void repositories; the Das OS monospace font. +pkgname=font-jetbrains-mono +version=2.304 +revision=1 +create_wrksrc=yes +depends="font-util" +short_desc="Typeface for developers by JetBrains" +maintainer="Das " +license="OFL-1.1" +homepage="https://www.jetbrains.com/lp/mono/" +distfiles="https://github.com/JetBrains/JetBrainsMono/releases/download/v${version}/JetBrainsMono-${version}.zip" +checksum=6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf +font_dirs="/usr/share/fonts/TTF" + +do_install() { + vmkdir usr/share/fonts/TTF + install -m644 fonts/ttf/*.ttf "${DESTDIR}/usr/share/fonts/TTF/" + vlicense OFL.txt +}