Taidan
Taidan (opens in a new tab) is the out-of-box experience (opens in a new tab) for Ultramarine 42 and newer. It's built with Rust and libhelium (opens in a new tab).
Taidan is standalone from Readymade, meaning you can combine it with any installer, or even lack thereof (in the case of preinstalled images.)
History
Driver installation was previously handled by Stellar (opens in a new tab), "a quick-and-dirty GUI post-install menu […] meant to be used for Ultramarine Linux 39's Anaconda post-install menu"1.
We messed this up, and ended up repurposing Stellar to install Nvidia and Broadcom drivers if you connected to the internet during installation.
We decided pretty early in Readymade's design that the system should be configured on first-boot rather than in the installer, leading to us building Taidan. Development spanned less than 3 months from October '24 to January '25.
Configuration
Taidan handles a few important tasks:
- System language
- Keyboard layout and input method
- User creation
The list of keymaps and languages are generated at compile time!
Keymaps are read from /usr/share/X11/xkb/rules/evdev.lst at
compile-time.
The basics should just work on most distributions! If you need more, Taidan can also offer:
- Setting the theme and accent colour
- Enabling scheduled screen tinting (night light)
- System tweaks (distro-defined, e.g. swapping default Flatpaks for RPMs)
- Provide a selection of apps for the user to pick from
Taidan generates part of the configurations from the /etc/os-release file.
It requires the NAME= and VARIANT_ID= fields to be present, otherwise the
app panics. This behavior will change in future versions to cater for other
distributions.
Runtime Fluent Loader
Fluent is the only supported i18n framework. Distributions may choose to provide custom translations
in /usr/share/taidan/po/. These translations are preferred over the compile-time ones.
Tweaks
Taidan allows distributions to present "tweaks", options to be presented to the user, in Ultramarine we use tweaks to offer RPMs over Flatpaks for those who prefer it.
Tweaks are read at runtime.
For the most up-to-date documentation, visit the source
file at src/backend/tweaks.rs.
Tweaks MUST be stored in TWEAKS_DIR, i.e.
/usr/share/taidan/tweaks. Here is an example of the file hierarchy:
/usr/share/taidan/tweaks/ (TWEAKS_DIR)
┣╸my_tweak/
┃ ├╴tweak.toml (optional)
┃ └╴up (required, MUST be executable)
┗╸other_tweak/
├╴tweak.toml
└╴upThe ID of a tweak is determined by the directory name (my_tweak, other_tweak).
The tweak.toml file, if present, MUST specify the following two fields:
ftl_name(aliasname): the fluent ID for the name of the tweak- fallback:
<id>-name
- fallback:
ftl_desc(aliasdesc): the fluent ID for the description of the tweak- fallback:
<id>-desc
- fallback:
Distributions SHOULD provide translations for their tweaks, see Runtime Fluent Loader.
If either of the fields are not provided, the TOML file cannot be parsed, or the file does not exist, Taidan will instead use the aforementioned fallback values.
The up executable will be given a single argument of either 1 or 0, denoting whether the user enables this tweak.
In addition, the Settings struct is serialized as JSON and fed to the executable via stdin.
Catalogue Customisations
Taidan reads the /etc/com.fyralabs.Taidan/catalogue/ folder for the software catalogue.
This may be changed by the runtime envvar TAIDAN_CATALOGUE_DIR.
Here is an example of the catalogue yml configuration file (taken from (opens in a new tab) here (opens in a new tab)):
category: Browsers
icon: web-browser-symbolic
choices:
- name: Firefox
provider: Mozilla
description: Firefox is an open source web browser using the Gecko engine. Firefox has historically been the default in Ultramarine.
note: Some features like WebUSB may be unavailable.
actions: ;
- name: Edge
provider: Microsoft
description: Edge is a Chromium-based browser centered around the Microsoft ecosystem, including many convenient and AI features.
options:
- radio: [Edge Stable, Edge Dev, Edge Beta]
actions:
- shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-stable
- shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-dev
- shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-beta
- name: Chrome
provider: Google
description: Chrome is the world's most popular web browser and the base for many others.
note: Chrome may say it is managed by an organization, this is because of Fedora's bookmarks package.
options:
- radio: [Chrome Stable, Chrome Dev, Chrome Beta]
actions:
- enable_yum_repo:google-chrome;rpm:google-chrome-stable
- enable_yum_repo:google-chrome;rpm:google-chrome-unstable
- enable_yum_repo:google-chrome;rpm:google-chrome-beta
- name: Lutris
provider: Lutris Team
description: |
Lutris helps you install and play video games from all eras and from most gaming systems. By leveraging and combining
existing emulators, engine re-implementations and compatibility layers, it gives you a central interface to launch all your games.
options:
- checkbox: flatpak
actions:
- rpm:lutris
- flatpak:net.lutris.LutrisEach YAML file in the catalogue directory should specify the name of category:, gtk icon: id,
and a list of choices:. Each choice should contain the name:, provider:, description: and
actions:. editions:, note: and options: are optional fields. editions: must be a list of
strings. The app will be shown to systems with a matching edition (we get this from the variant field in os-release) listed in this field.
An option listed in options: must either be a - radio: [...] or a - checkbox: ....
The following section uses the term "choices" to refer to user preferences on each app they would
like to install, which is different from choices:.
Denote a list of options of length N as opts[N], such that for i in 0..N (excluding N),
opts[i] must be a list of choices with length C. For radio buttons, the list is in the form of
choices[C] = ["Edge Stable", "Edge Dev", "Edge Beta"], but for checkboxes, the list is in the
form of choices[2] = [nil, "flatpak"] (see the Lutrix example). This means opts[][] is a
2-dimensional nested array of optional strings.
Let opts_lengths[N] be an array of integers, where for i in 0..N, opts_lengths[i] is the
length (denoted as C for each choice list) of the list of choices[C] stored in opts[i].
Thus, actions: is an N-dimentional nested array (or it could be a unit value when N = 0)
storing the corresponding action that should be done when the corresponding choices are selected.
Let's look at a very complicated example:
- name: Test
provider: Meow
description: Meow
options:
- checkbox: 1 # ← opt[0] = [nil, "1"] │ opts_lengths[0] = 2
- radio: [a, b, c] # ← opt[1] = ["a", "b", "c"] │ opts_lengths[1] = 3
- radio: [A, B, C] # ← opt[2] = ["A", "B", "C"] │ opts_lengths[2] = 3
- checkbox: meow # ← opt[3] = [nil, "meow"] │ opts_lengths[3] = 2
actions:
- # when checkbox (opt[0]) not pressed
- # when radio (opt[1]) has "a" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "no select 1, select a, select A, no select meow"
- shell:echo "no select 1, select a, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "no select 1, select a, select B, no select meow"
- shell:echo "no select 1, select a, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "no select 1, select a, select C, no select meow"
- shell:echo "no select 1, select a, select C, select meow"
- # when radio (opt[1]) has "b" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "no select 1, select b, select A, no select meow"
- shell:echo "no select 1, select b, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "no select 1, select b, select B, no select meow"
- shell:echo "no select 1, select b, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "no select 1, select b, select C, no select meow"
- shell:echo "no select 1, select b, select C, select meow"
- # when radio (opt[1]) has "c" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "no select 1, select c, select A, no select meow"
- shell:echo "no select 1, select c, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "no select 1, select c, select B, no select meow"
- shell:echo "no select 1, select c, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "no select 1, select a, select C, no select meow"
- shell:echo "no select 1, select a, select C, select meow"
- # when checkbox (opt[0]) is pressed
- # when radio (opt[1]) has "a" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "select 1, select a, select A, no select meow"
- shell:echo "select 1, select a, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "select 1, select a, select B, no select meow"
- shell:echo "select 1, select a, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "select 1, select a, select C, no select meow"
- shell:echo "select 1, select a, select C, select meow"
- # when radio (opt[1]) has "b" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "select 1, select b, select A, no select meow"
- shell:echo "select 1, select b, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "select 1, select b, select B, no select meow"
- shell:echo "select 1, select b, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "select 1, select b, select C, no select meow"
- shell:echo "select 1, select b, select C, select meow"
- # when radio (opt[1]) has "c" selected
- # when radio (opt[2]) has "A" selected
- shell:echo "select 1, select c, select A, no select meow"
- shell:echo "select 1, select c, select A, select meow"
- # when radio (opt[2]) has "B" selected
- shell:echo "select 1, select c, select B, no select meow"
- shell:echo "select 1, select c, select B, select meow"
- # when radio (opt[2]) has "C" selected
- shell:echo "select 1, select c, select C, no select meow"
- shell:echo "select 1, select c, select C, select meow"
# ↑ ↑ ↑ ↑
# 2 3 3 2
# ┬ ┬ ┬ ┬
# └╼┿━┿━┿━━ should appear 2 times
# └╼┿━┿━━ should appear 3 times per above item, i.e. total of 2×3 = 6 times
# └╼┿━━ should appear 3 times per above item, i.e. total of 2×3×3 = 18 times
# └—— should appear 2 times per above item, i.e. total of 2×3×3×2 = 36 timesAs of version 0.1.9, it's required to explicitly write out all possible choice
combinations, this might change in the future.
Possible action values include copr:..., enable_yum_repo:..., rpm:..., shell:...,
flatpak:..., or a semicolon (;)-separated list of the above values for running multiple
actions, or todo.
Catalogue App Icons and Screenshots
App icons should be stored in data/catalogue/{category}/{app}.svg. The exact names are not
important; in fact, you may store them in other places relative to data/ (we'll explain in a moment).
Screenshots are similarly stored in data/screenshots/.
The data/icons.gresource.xml must be modified such that the GResource path
/com/fyralabs/Taidan/screenshots/ contains all screenshots with aliases in the format of
ss-{category}-{app}.png, where {category} is the name of the YAML file excluding extensions,
and {app} is in lowercase letters and spaces are replaced by - dashes.
Similarly, the /com/fyralabs/Taidan/icons/symbolic/actions/ GResource path must contain icons in
the format of ctlg-{category}-{app}.svg.
In addition, you should include other icons you have chosen to use for the icon: field for
the categories, in /com/fyralabs/Taidan/icons/symbolic/actions/.
Footnotes
-
quote the readme file: "This script will be deprecated in the future when we implement our own OOBE (probably 41). For now, it's a stopgap in our transitional OOBE (Readymade install -> Anaconda/DE OOBE -> Stellar)". ↩