← Otto User GuideTheming
Otto’s look is set entirely from the config file. There is no theme picker GUI.
Light and dark
theme_scheme = "Light" # or "Dark"
This drives the compositor’s own chrome (the dock, app switcher, exposé,
workspace selector) and is also exported to applications through the XDG
Desktop Portal Settings interface as org.freedesktop.appearance color-scheme.
That means GTK apps, Firefox, Chromium, Electron apps and the top bar all follow this one setting, and switch within about a second when you change it. No restart needed for clients that listen for the change signal.
For this to work, the Otto portal backend must be selected for the Settings
interface. See Screen Sharing, which covers
the same portals.conf.
# gtk_theme = "Adwaita"
gtk_theme is recorded for reference only; Otto does not apply it. Set your GTK
theme the usual way (gsettings, ~/.config/gtk-3.0/settings.ini).
Accent colour
accent_color = "blue"
The accent tints selection borders in the workspace selector, the exposé window
highlight, and the controls in Otto’s own apps: toggles, sliders, focus rings
and selected rows. Applications outside Otto can follow it too: it is published
as accent-color in the org.freedesktop.appearance portal namespace.
Changing it takes effect immediately; no restart is needed. Available values:
red, orange, yellow, green, mint, teal, cyan, blue, indigo,
purple, pink, gray, brown
A named accent follows the light and dark palettes, shifting to the right shade for the current scheme. You can also give a hex literal for a colour the palette has no name for:
accent_color = "#7fd4e6"
#RGB and #RRGGBB are both accepted, and the # is required — it is what
tells a colour from a name. A literal is used exactly as written in both
schemes.
Rounded corners
rounded_corners = true
Rounds the corners of the dock, the top bar, Otto-drawn window decorations and
the desktop’s own panels. Set it to false for square corners throughout. It
takes effect immediately: Otto repaints its own chrome, and the applications
drawing their own (the top bar, and anything built on otto-kit) are told
through the portal’s org.otto.desktop rounded-corners, the same door
color-scheme comes through.
Frosting
frosting = true
The translucent, blurred material behind the dock, the top bar, the launcher,
the app switcher, the exposé labels, the OSD and the selectors. Set it to
false and the same surfaces are drawn nearly opaque instead, and Otto stops
blurring the desktop for them, which also saves the work of keeping those blurs
fresh under a playing video. Menus are the most solid, since they open over
whatever is on screen. The dock and the top bar let a little more of the
wallpaper through, since nothing else passes under them. It takes effect
immediately, by the same route as the corners: Otto repaints its own chrome,
and the bar and the launcher are told through the portal’s
org.otto.desktop frosting.
Wallpaper
background_image = "/usr/share/otto/wallpaper.jpg"
background_color = "#102E95"
background_mode = "fill"
background_position_x = 0.5
background_position_y = 0.5
background_zoom = 1.0
Otto’s packages ship a wallpaper at /usr/share/otto/wallpaper.jpg, and the
default configuration uses it. Point background_image at any other image to
replace it: JPEG, PNG, WebP, GIF, BMP or SVG. A camera’s rotation tag and an
embedded colour profile are honoured.
background_color is shown when there is no image, or it fails to load, as a
gradient with this as the bottom colour. It is also what shows around a fitted
or centred image and through a transparent one.
background_mode decides how the image fills the screen:
| Mode | What it does |
|---|---|
fill | Scaled to cover the screen, the excess cropped. The default. |
fit | Scaled to fit inside the screen, whole, with the colour around it. |
stretch | Stretched to the screen’s shape, ignoring the image’s own. |
center | At its own size, with the colour around it, or cropped if larger. |
tile | Repeated at its own size from the top-left corner. |
span | One image across all your displays, as if they were one; each shows its part. |
In center and tile an image pixel is a point, so the image is the same
size on a HiDPI display as on a normal one.
background_position_x and background_position_y pick which part of a
cropped image shows, and where a smaller one sits, as fractions across and
down, the way CSS object-position does: 0.5 centres, 0.0 keeps the left
or top edge, 1.0 the right or bottom. A portrait whose subject is near the
top stays in view with background_position_y = 0.2. In the Settings app,
click the part of the preview you want to keep.
background_zoom (1.0 to 8.0) magnifies a fill or span image beyond just
covering the screen; the position then pans it.
The wallpaper is resampled once for each display, at that display’s resolution, and drawn pixel for pixel, so it is as sharp as the image allows and stays still however the screen around a moving window is redrawn.
One display, or one workspace
Any of these settings can be set for one display, and for one workspace:
[background_outputs."DP-1"]
image = "/home/me/Pictures/ultrawide.jpg"
mode = "fit"
color = "#000000"
[workspaces.entries."eDP-1:2".background]
image = "/home/me/Pictures/focus.png"
The names are those of the global settings without background_. A workspace
takes the global settings, then its display’s, then its own, one setting at a
time. image = "" means no image. The key "eDP-1:2" is the display and the
workspace’s position counting from 0; a workspace’s wallpaper moves with it
when you reorder workspaces.
Both are easier set with otto-msg:
otto-msg 'output DP-1 bg ~/Pictures/ultrawide.jpg fit #000000'
otto-msg 'background ~/Pictures/focus.png' # the focused workspace
otto-msg 'output DP-1 bg reset'
Otto also supports the wlr-layer-shell background layer, so wallpaper daemons
like swaybg and swww work if you want animated or per-monitor backgrounds:
[[exec_once]]
cmd = "swaybg"
args = ["-i", "/path/to/wallpaper.jpg", "-m", "fill"]
Fonts
font_family = "Inter"
The font used by Otto’s own UI: dock labels, exposé window titles, the app switcher. It is also offered to applications as the interface font, through the desktop portal (GTK 4), GNOME’s settings store (GTK 3, and Qt through its GTK platform theme) and XSETTINGS (X11 apps), along with your fontconfig antialiasing and hinting; an application’s own font preference, where it has one, still wins. Changing it takes effect after restarting Otto.
Otto ships with Inter as its default. Any font installed on the system works.
Cursor
cursor_theme = "Otto-MacTahoe"
cursor_size = 24
The icon theme Otto ships, Otto-MacTahoe, carries its own cursors, so the
default configuration uses it for both.
Cursor theme names are the directory names under /usr/share/icons/ and
~/.local/share/icons/, and they are case-sensitive. Check what you have:
ls /usr/share/icons ~/.local/share/icons
Otto also implements wp-cursor-shape-v1, so applications that use the modern
cursor-shape protocol get themed cursors without shipping their own bitmaps.
Icon theme
icon_theme = "Otto-MacTahoe"
Used for application icons in the dock, the app switcher and notification islands. Commented out or absent, Otto auto-detects a reasonable theme from what is installed.
Otto’s packages ship the
MacTahoe icon theme
under Otto’s own name — Otto-MacTahoe, Otto-MacTahoe-light and
Otto-MacTahoe-dark — and the default configuration uses it, so a first
install has a complete set of icons. The separate name keeps it from
clashing with a MacTahoe you install yourself, which you can select by its
own name, MacTahoe.
Any other theme under /usr/share/icons works the same way.
The theme draws apps, files and tray icons. The glyphs on the controls of Otto’s own apps (toolbar buttons, menu ticks, the sidebars of Files and Settings) are Otto’s own, from the Solar icon set by 480 Design, and look the same whatever theme you choose.
Popular choices: Otto-MacTahoe, MacTahoe, WhiteSur, Adwaita, Papirus, Fluent. Otto’s own
screenshots use the
Fluent icon theme.
Icons are looked up per the freedesktop icon theme specification, including inheritance: a theme that lacks an icon falls back to its parent.
Language
locales = ["en_US", "en"]
Preferred languages for application names and descriptions read from .desktop
files. Use standard locale identifiers, most specific first, and prefer the
region-qualified form (lang_COUNTRY): ["fr_FR", "fr"] means “French (France)
if the entry has that variant, plain French otherwise, English as the ultimate
fallback from the entry’s untranslated name”. Matching follows the freedesktop
Desktop Entry spec and falls back automatically from region to bare language, so
listing both zh_CN and zh catches entries that localize either form.
This affects names shown in the dock, app switcher and menus, and it is also Otto’s own UI language: the bar, settings, Files and the other bundled apps read the same list and pick a translation from it. Otto ships catalogues for German, English (GB and US), Spanish, French, Italian, Japanese, Polish, Portuguese (Brazil), Russian, Ukrainian and Simplified Chinese; anything else falls back to English.
Looks
A look is a set of wallpaper, icon theme, cursor theme and settings (dock
position and tint included) that belong together. otto-look installs one and
applies it to the running session:
otto-look list # the published looks
otto-look ember # the latest version of Ember
otto-look ember@1 # a given version
otto-look ./my-look # a look of your own: a folder holding look.toml,
otto-look ./my-look.tar.gz # that folder packed,
otto-look https://example.org/looks/mine/look.toml # or either one online
Published looks come from
nongio/otto-looks, each one a package
checked against its digest. The wallpaper goes in
~/.local/share/backgrounds/otto-looks/, and the settings apply at once and
survive a restart. A theme that is not
installed yet is downloaded from its makers, checked against the sha256 in the
look, and installed for you alone, in ~/.local/share/icons. otto-look never
uses sudo. It needs curl, tar, gzip, sha256sum and busctl, plus unzip for a
theme shipped as a zip, and prints the credits for each piece when it’s done.
A worked example
A dark HiDPI setup:
screen_scale = 2.0
theme_scheme = "Dark"
accent_color = "purple"
font_family = "Inter"
background_image = "/home/me/Pictures/wallpaper.jpg"
background_color = "#101018"
cursor_theme = "Adwaita"
cursor_size = 32
icon_theme = "Papirus-Dark"
locales = ["en_US", "en"]
Troubleshooting
Icon or cursor theme not found. Names are case-sensitive and must match the
directory name exactly. Check with ls /usr/share/icons/. Some themes need a
separate package for their cursor variant.
Applications ignore light/dark. GTK 4 apps, Firefox and Chromium read it
from the XDG Settings portal, which needs xdg-desktop-portal running with
Otto selected for org.freedesktop.impl.portal.Settings in your
portals.conf. GTK 3, libadwaita and Qt apps read GNOME’s settings store
instead, which Otto’s own session writes; check it with
gsettings get org.gnome.desktop.interface color-scheme, which should say
'prefer-dark' or 'default'. For the portal, verify with:
gdbus call --session --dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.Settings.Read \
org.freedesktop.appearance color-scheme
1 means dark, 2 means light, 0 means no preference.
The wallpaper is a gradient instead of my image. The path is wrong or
unreadable, or the file is not an image Otto can decode. Check the log. Otto
reports the failure (the Settings app and otto-msg say why) and falls back to
background_color. An override in [background_outputs] or a workspace’s
background wins over the global setting, so check those too.
Not yet supported
- Per-monitor or per-workspace wallpaper
- Automatic light/dark switching by time of day
- Animated wallpaper without an external daemon