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 <noreply@anthropic.com>
This commit is contained in:
das
2026-10-18 21:23:16 +03:00
co-authored by Claude Opus 5.5
commit b7399031c7
48 changed files with 1548 additions and 0 deletions
+8
View File
@@ -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/
+21
View File
@@ -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.
+35
View File
@@ -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
+99
View File
@@ -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
<table>
<tr>
<td align="center"><img src="images/desktop.png" width="300" alt="Live desktop" /></td>
<td align="center"><img src="images/desktop-terminal.png" width="300" alt="Terminal and window frames" /></td>
<td align="center"><img src="images/boot-menu.png" width="300" alt="Boot menu (UEFI)" /></td>
</tr>
<tr>
<td align="center"><i>Live desktop</i></td>
<td align="center"><i>Terminal and window frames</i></td>
<td align="center"><i>Boot menu (UEFI)</i></td>
</tr>
<tr>
<td align="center"><img src="images/wallpaper-light.png" width="300" alt="Light wallpaper" /></td>
<td align="center"><img src="images/wallpaper-dark.png" width="300" alt="Dark wallpaper" /></td>
<td align="center"><img src="images/window-frames.png" width="300" alt="Window frames" /></td>
</tr>
<tr>
<td align="center"><i>Light wallpaper</i></td>
<td align="center"><i>Dark wallpaper</i></td>
<td align="center"><i>Window frames: light and dark, active and inactive</i></td>
</tr>
</table>
### 📌 Using
#### Live ISO<a id='LiveISO' />
**_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 id='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<a id='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<a id='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<a id='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-<version>-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:=-_
+99
View File
@@ -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.
### 📱 Скріншоти
<table>
<tr>
<td align="center"><img src="images/desktop.png" width="300" alt="Live-робочий стіл" /></td>
<td align="center"><img src="images/desktop-terminal.png" width="300" alt="Термінал і рамки вікон" /></td>
<td align="center"><img src="images/boot-menu.png" width="300" alt="Меню завантаження (UEFI)" /></td>
</tr>
<tr>
<td align="center"><i>Live-робочий стіл</i></td>
<td align="center"><i>Термінал і рамки вікон</i></td>
<td align="center"><i>Меню завантаження (UEFI)</i></td>
</tr>
<tr>
<td align="center"><img src="images/wallpaper-light.png" width="300" alt="Світлі шпалери" /></td>
<td align="center"><img src="images/wallpaper-dark.png" width="300" alt="Темні шпалери" /></td>
<td align="center"><img src="images/window-frames.png" width="300" alt="Рамки вікон" /></td>
</tr>
<tr>
<td align="center"><i>Світлі шпалери</i></td>
<td align="center"><i>Темні шпалери</i></td>
<td align="center"><i>Рамки вікон: світлі й темні, активні й неактивні</i></td>
</tr>
</table>
### 📌 Використання
#### Live ISO<a id='LiveISO' />
**_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<a id='Look' />
**_Сучасний мінімалізм із синьо-жовтою восьмипелюстковою зіркою з української вишивки._**
- GTK-теми `DasOS` і `DasOS-Dark` побудовані на adw-gtk3 і додають акцентні кольори Das OS. Вони також оформлюють панель Xfce, яку adw-gtk3 лишає чорною.
- Рамки вікон xfwm4 (заокруглені кути, круглі кнопки, червона кнопка закриття при наведенні) генерує у PNG утиліта `xfwm4gen` (Go) з однієї палітри.
- Світлі й темні шпалери зображують зірку над смугою орнаменту в стилі рушника. Та сама зірка є логотипом Das OS і кнопкою меню.
- Шрифти: Inter для інтерфейсу і JetBrains Mono в терміналі. Іконки Papirus, тема курсора Adwaita.
#### Налаштування за замовчуванням<a id='Defaults' />
**_Пакет 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.
#### Власний репозиторій пакетів<a id='Repository' />
**_Кожен файл 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 встановлює їх звідти.
#### Збирання в контейнері<a id='Build' />
**_Усе збирання відбувається в 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:=-_
+1
View File
@@ -0,0 +1 @@
0.1.0
+17
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+31
View File
@@ -0,0 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="640" height="480" viewBox="0 0 640 480">
<!-- Background of the GRUB (UEFI) and isolinux (BIOS) boot menus. Both draw their
entries in the upper left area, so the star sits in the lower right corner. -->
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#141820"/>
<stop offset="1" stop-color="#1f2736"/>
</linearGradient>
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#3f78e4"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f2c230"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
</defs>
<rect width="640" height="480" fill="url(#bg)"/>
<g transform="translate(560 330) scale(0.7)">
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,6 @@
Section "InputClass"
Identifier "system-keyboard"
MatchIsKeyboard "on"
Option "XkbLayout" "us,ua"
Option "XkbOptions" "grp:alt_shift_toggle"
EndSection
+1
View File
@@ -0,0 +1 @@
xfce
+60
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
# runit services enabled in the live image (/etc/sv/<name>)
dbus
elogind
polkitd
NetworkManager
bluetoothd
lightdm
spice-vdagentd
+18
View File
@@ -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" "$@"
+62
View File
@@ -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"
+59
View File
@@ -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" <<CONF
XBPS_CHROOT_CMD=uchroot
XBPS_MAKEJOBS=$(nproc)
CONF
names=()
for dir in /work/srcpkgs/*/; do
name=$(basename "$dir")
if git -C "$vp" cat-file -e "HEAD:srcpkgs/$name/template" 2>/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
+76
View File
@@ -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[@]}"
+18
View File
@@ -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 <anry.das@gmail.com>"
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
}
+1
View File
@@ -0,0 +1 @@
../../../LICENSE
@@ -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
+35
View File
@@ -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"
@@ -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
@@ -0,0 +1,4 @@
# Das OS: LightDM with the GTK greeter starting an Xfce session.
[Seat:*]
greeter-session=lightdm-gtk-greeter
user-session=xfce
@@ -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
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Wallpapers are keyed by monitor connector name, so das-session-init sets them at first login. -->
<channel name="xfce4-desktop" version="1.0">
<property name="backdrop" type="empty">
<property name="single-workspace-mode" type="bool" value="true"/>
<property name="single-workspace-number" type="int" value="0"/>
</property>
<property name="desktop-icons" type="empty">
<property name="icon-size" type="uint" value="48"/>
<property name="file-icons" type="empty">
<property name="show-filesystem" type="bool" value="false"/>
<property name="show-home" type="bool" value="true"/>
<property name="show-removable" type="bool" value="true"/>
<property name="show-trash" type="bool" value="true"/>
</property>
</property>
</channel>
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- One full-width bottom panel: menu, windows, tray, layout, sound, notifications, clock. -->
<channel name="xfce4-panel" version="1.0">
<property name="configver" type="int" value="2"/>
<property name="panels" type="array">
<value type="int" value="1"/>
<property name="dark-mode" type="bool" value="false"/>
<property name="panel-1" type="empty">
<property name="position" type="string" value="p=10;x=0;y=0"/>
<property name="length" type="uint" value="100"/>
<property name="position-locked" type="bool" value="true"/>
<property name="size" type="uint" value="40"/>
<property name="icon-size" type="uint" value="0"/>
<property name="plugin-ids" type="array">
<value type="int" value="1"/>
<value type="int" value="2"/>
<value type="int" value="3"/>
<value type="int" value="4"/>
<value type="int" value="5"/>
<value type="int" value="6"/>
<value type="int" value="7"/>
<value type="int" value="8"/>
<value type="int" value="9"/>
<value type="int" value="10"/>
<value type="int" value="11"/>
</property>
</property>
</property>
<property name="plugins" type="empty">
<property name="plugin-1" type="string" value="whiskermenu">
<property name="button-icon" type="string" value="das-logo"/>
<property name="show-button-title" type="bool" value="false"/>
</property>
<property name="plugin-2" type="string" value="separator">
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-3" type="string" value="tasklist">
<property name="grouping" type="uint" value="1"/>
<property name="flat-buttons" type="bool" value="true"/>
<property name="show-handle" type="bool" value="false"/>
</property>
<property name="plugin-4" type="string" value="separator">
<property name="expand" type="bool" value="true"/>
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-5" type="string" value="systray">
<property name="square-icons" type="bool" value="true"/>
</property>
<property name="plugin-6" type="string" value="xkb">
<property name="display-type" type="uint" value="1"/>
<property name="display-scale" type="uint" value="50"/>
</property>
<property name="plugin-7" type="string" value="pulseaudio">
<property name="enable-keyboard-shortcuts" type="bool" value="true"/>
</property>
<property name="plugin-8" type="string" value="notification-plugin"/>
<property name="plugin-9" type="string" value="separator">
<property name="style" type="uint" value="0"/>
</property>
<property name="plugin-10" type="string" value="clock">
<property name="mode" type="uint" value="2"/>
<property name="digital-layout" type="uint" value="1"/>
<property name="digital-time-format" type="string" value="%H:%M"/>
<property name="digital-date-format" type="string" value="%d.%m.%Y"/>
</property>
<property name="plugin-11" type="string" value="showdesktop"/>
</property>
</channel>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xfce4-terminal" version="1.0">
<property name="font-use-system" type="bool" value="false"/>
<property name="font-name" type="string" value="JetBrains Mono 11"/>
<property name="misc-default-geometry" type="string" value="110x32"/>
<property name="misc-menubar-default" type="bool" value="false"/>
<property name="scrolling-unlimited" type="bool" value="true"/>
<property name="color-use-theme" type="bool" value="false"/>
<property name="color-foreground" type="string" value="#e3e6ec"/>
<property name="color-background" type="string" value="#1e222a"/>
<property name="color-cursor" type="string" value="#ffd23f"/>
<property name="color-cursor-use-default" type="bool" value="false"/>
<property name="color-palette" type="string" value="#2b2f38;#e0464c;#7ec16e;#f2c230;#4f86ec;#b07ee8;#4fb8c9;#c8ccd4;#4a505d;#ff6b70;#98d68a;#ffd23f;#7aa7f5;#c9a0f2;#72cfdc;#ffffff"/>
</channel>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xfwm4" version="1.0">
<property name="general" type="empty">
<property name="theme" type="string" value="DasOS"/>
<property name="title_font" type="string" value="Inter Semi-Bold 10"/>
<property name="title_alignment" type="string" value="center"/>
<property name="button_layout" type="string" value="O|HMC"/>
<property name="use_compositing" type="bool" value="true"/>
<property name="show_dock_shadow" type="bool" value="true"/>
<property name="workspace_count" type="int" value="2"/>
</property>
</channel>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xsettings" version="1.0">
<property name="Net" type="empty">
<property name="ThemeName" type="string" value="DasOS"/>
<property name="IconThemeName" type="string" value="Papirus"/>
</property>
<property name="Xft" type="empty">
<property name="Antialias" type="int" value="1"/>
<property name="Hinting" type="int" value="1"/>
<property name="HintStyle" type="string" value="hintslight"/>
<property name="RGBA" type="string" value="rgb"/>
</property>
<property name="Gtk" type="empty">
<property name="FontName" type="string" value="Inter 10"/>
<property name="MonospaceFontName" type="string" value="JetBrains Mono 10"/>
<property name="CursorThemeName" type="string" value="Adwaita"/>
<property name="CursorThemeSize" type="int" value="24"/>
<property name="DecorationLayout" type="string" value="menu:minimize,maximize,close"/>
</property>
</channel>
+34
View File
@@ -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 <anry.das@gmail.com>"
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"
}
+1
View File
@@ -0,0 +1 @@
../../../LICENSE
@@ -0,0 +1,71 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3840" height="2160" viewBox="0 0 1920 1080">
<defs>
<linearGradient id="bg" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1920" y2="1080">
<stop offset="0" stop-color="#141820"/>
<stop offset="1" stop-color="#1f2736"/>
</linearGradient>
<!-- Rushnyk-style ribbon: a column of embroidered diamonds -->
<pattern id="stitch" patternUnits="userSpaceOnUse" x="1366" y="0" width="28" height="36">
<path d="M14 8 L24 18 L14 28 L4 18 Z" fill="#4f86ec"/>
<path d="M14 14 L18 18 L14 22 L10 18 Z" fill="#f7cc3a"/>
</pattern>
<path id="petal" d="M0 0 L38.27 -92.39 L0 -184.78 L-38.27 -92.39 Z"/>
<path id="core" d="M0 -50.81 L17.22 -92.39 L0 -133.97 L-17.22 -92.39 Z"/>
<path id="bead" d="M0 -205 L7 -214 L0 -223 L-7 -214 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#3f78e4"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f2c230"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
<g id="star">
<use xlink:href="#petal"/>
<use xlink:href="#petal" transform="rotate(45)"/>
<use xlink:href="#petal" transform="rotate(90)"/>
<use xlink:href="#petal" transform="rotate(135)"/>
<use xlink:href="#petal" transform="rotate(180)"/>
<use xlink:href="#petal" transform="rotate(225)"/>
<use xlink:href="#petal" transform="rotate(270)"/>
<use xlink:href="#petal" transform="rotate(315)"/>
</g>
</defs>
<rect width="1920" height="1080" fill="url(#bg)"/>
<!-- Faint echo of the star behind everything -->
<use xlink:href="#star" transform="translate(1380 540) scale(2.6)" fill="#8fb3ff" opacity="0.04"/>
<!-- Ribbon with thin side threads, broken around the medallion -->
<g opacity="0.3">
<rect x="1366" y="0" width="28" height="288" fill="url(#stitch)"/>
<rect x="1366" y="792" width="28" height="288" fill="url(#stitch)"/>
<rect x="1357" y="0" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1357" y="792" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1401.5" y="0" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1401.5" y="792" width="1.5" height="288" fill="#4f86ec"/>
</g>
<!-- Medallion -->
<g transform="translate(1380 540)">
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
<g fill="#4f86ec" opacity="0.6">
<use xlink:href="#bead" transform="rotate(22.5)"/>
<use xlink:href="#bead" transform="rotate(67.5)"/>
<use xlink:href="#bead" transform="rotate(112.5)"/>
<use xlink:href="#bead" transform="rotate(157.5)"/>
<use xlink:href="#bead" transform="rotate(202.5)"/>
<use xlink:href="#bead" transform="rotate(247.5)"/>
<use xlink:href="#bead" transform="rotate(292.5)"/>
<use xlink:href="#bead" transform="rotate(337.5)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,71 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3840" height="2160" viewBox="0 0 1920 1080">
<defs>
<linearGradient id="bg" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1920" y2="1080">
<stop offset="0" stop-color="#f7f9fc"/>
<stop offset="1" stop-color="#dfe7f3"/>
</linearGradient>
<!-- Rushnyk-style ribbon: a column of embroidered diamonds -->
<pattern id="stitch" patternUnits="userSpaceOnUse" x="1366" y="0" width="28" height="36">
<path d="M14 8 L24 18 L14 28 L4 18 Z" fill="#2f6fde"/>
<path d="M14 14 L18 18 L14 22 L10 18 Z" fill="#f5c400"/>
</pattern>
<path id="petal" d="M0 0 L38.27 -92.39 L0 -184.78 L-38.27 -92.39 Z"/>
<path id="core" d="M0 -50.81 L17.22 -92.39 L0 -133.97 L-17.22 -92.39 Z"/>
<path id="bead" d="M0 -205 L7 -214 L0 -223 L-7 -214 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#2f6fde"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f5c400"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
<g id="star">
<use xlink:href="#petal"/>
<use xlink:href="#petal" transform="rotate(45)"/>
<use xlink:href="#petal" transform="rotate(90)"/>
<use xlink:href="#petal" transform="rotate(135)"/>
<use xlink:href="#petal" transform="rotate(180)"/>
<use xlink:href="#petal" transform="rotate(225)"/>
<use xlink:href="#petal" transform="rotate(270)"/>
<use xlink:href="#petal" transform="rotate(315)"/>
</g>
</defs>
<rect width="1920" height="1080" fill="url(#bg)"/>
<!-- Faint echo of the star behind everything -->
<use xlink:href="#star" transform="translate(1380 540) scale(2.6)" fill="#2f6fde" opacity="0.05"/>
<!-- Ribbon with thin side threads, broken around the medallion -->
<g opacity="0.35">
<rect x="1366" y="0" width="28" height="288" fill="url(#stitch)"/>
<rect x="1366" y="792" width="28" height="288" fill="url(#stitch)"/>
<rect x="1357" y="0" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1357" y="792" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1401.5" y="0" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1401.5" y="792" width="1.5" height="288" fill="#2f6fde"/>
</g>
<!-- Medallion -->
<g transform="translate(1380 540)">
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
<g fill="#2f6fde" opacity="0.6">
<use xlink:href="#bead" transform="rotate(22.5)"/>
<use xlink:href="#bead" transform="rotate(67.5)"/>
<use xlink:href="#bead" transform="rotate(112.5)"/>
<use xlink:href="#bead" transform="rotate(157.5)"/>
<use xlink:href="#bead" transform="rotate(202.5)"/>
<use xlink:href="#bead" transform="rotate(247.5)"/>
<use xlink:href="#bead" transform="rotate(292.5)"/>
<use xlink:href="#bead" transform="rotate(337.5)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="-100 -100 200 200">
<!-- Eight-petal star from Ukrainian embroidery: rhombi with a 45° apex share their edges. -->
<defs>
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#2f6fde"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f5c400"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
</defs>
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -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);
}
@@ -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
@@ -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;
}
@@ -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
+3
View File
@@ -0,0 +1,3 @@
module dasos/xfwm4gen
go 1.22
+343
View File
@@ -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)
}
}
@@ -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)
}
}
}
+39
View File
@@ -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 <anry.das@gmail.com>"
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
}
+20
View File
@@ -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 <anry.das@gmail.com>"
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
}