Container installed desktop environment is too long, letâs call it gui ct.
I run a gui ct for over half a year. I use it as HTPC. The biggest issues are containerâs wayland compositor will freeze when playing high rate 4K H265 10Bit SDR movie even my gpu can decode it, and host will freeze when CPU or RAM overloaded. Not big issues are canât auto login because sway(wayland) donât support display manager, host X11 will crush if X11 container exist, host X11 need xhost or xauthority or x cookie to let container use host X11 socket.
You donât need to read my old post, I will write from scratch.
First you need something to hold gui ct like X11 window manager or wayland compositor. I think itâs possible we can skip it, but I donât know what a container need to display in host DRM, it may involve creating a wayland compositor from scratch. I want to choose X11 window manager, but terrible user experience, you can try, I wonât stop you. So I have to choose wayland compositor. For host, itâs ok to use tiling. For container, itâs better to use stacking, unless you are a geek, after testing there are only 2 suit for using: labwc and wayfire.
My host OS is debian, container manager is incus, wayland compositor is sway, you can use whatever you like. You donât need to install xwayland in host.
Step for gui in host
In my opinion, xfce4-terminal is better than foot. You donât have to install xfce4-terminal.
apt install sway xfce4-terminal -y
Letâs config sway, otherwize you will see a black screen.
mkdir -p ~/.config/sway
cp /etc/sway/config ~/.config/sway/
nano ~/.config/sway/config
- * means for all display, if you want to change, you need to start sway, then you can see display name using swaymsg -t get_outputs.
- sway should choose right resolution for you display, but if you need to change, this is template: resolution --custom 1920x1200@60Hz
- background image path $HOME/background.jpg change to whatever you want.
- fill will fill you screen with background image, there are others see: Home · swaywm/sway Wiki · GitHub
- pos 0 0 will show frame from top left
output * resolution --custom 1920x1200@60Hz bg $HOME/background.jpg fill pos 0 0
- set default terminal to xfce4-terminal
set $term xfce4- xfce4-terminal
you need to change below contain to let container shows in fullscreen
#hide border
default_border none
default_floating_border none
font pango:monospace 0
titlebar_padding 1
titlebar_border_thickness 0
#bar {
# position top
# When the status_command prints a new line to stdout, swaybar updates.
# The default just shows the current date and time.
# status_command while date +'%Y-%m-%d %I:%M:%S %p'; do sleep 1; done
# colors {
# statusline #ffffff
# background #323232
# inactive_workspace #32323200 #32323200 #5c5c5c
# }
#}
Now you can start sway using sway
in terminal. But if you using VM, WLR_NO_HARDWARE_CURSORS=1 sway
.
Press Win+Enter to open terminal, I donât what Win is in Mac, but I know is a button with icon. If you want sway auto open after login, add this to your .profile:
if [ "$(tty)" = "/dev/tty1" ] ; then
exec sway
fi
Sound server is pipewire, I have problem playing sound using pulseaudio.
apt install pipewire-audio pavucontrol -y
running as user, not as root. Enable and start the new pipewire-pulse service with:
systemctl --user --now enable pipewire pipewire-pulse
systemctl --user --now enable wireplumber.service
Itâs time to check whether or not sound card working. Sometime you need to choose the right sound driver in pavucontrol
.If you want to change volume, use pavucontrol
in host.
That all gui part in host.
Itâs time to prepare for container. Since we can create gui ct, itâs better one user one container. And without display manager, itâs not convenient to switch user.
Step for gui in container
Use id to get your gid and uid, they will be 1000 if you are the only user, itâs fine to be other number just 1000 to your number.
Profile
Create a profile for hardware accelerate.
incus profile copy default hwac
incus profile edit hwac
config: {}
description: Hardware acceleration
devices:
mygpu:
type: gpu
gid: '1000'
uid: '1000'
name: hwac
Create a profile for wayland socket.
incus profile copy default wayland
incus profile edit wayland
config: {}
description: Let containers use host display
devices:
waylandSocket:
type: proxy
bind: container
connect: unix:/run/user/1000/wayland-1
listen: unix:/mnt/wayland-1
mode: '0770'
security.gid: '1000'
security.uid: '1000'
gid: '1000'
uid: '1000'
name: wayland
Create a profile for pipewire socket.
incus profile copy default pipewire
incus profile edit pipewire
config: {}
description: Let containers use host sound
devices:
pipewireSocket:
type: proxy
bind: container
connect: unix:/run/user/1000/pipewire-0
listen: unix:/mnt/pipewire-0
mode: '0770'
security.gid: '1000'
security.uid: '1000'
gid: '1000'
uid: '1000'
name: pipewire
Create a profile for autostart.
incus profile copy default autostart
incus profile edit autostart
config:
boot.autostart: true
description: auto start and shutdown container
devices: {}
name: autostart
If using X11.
incus profile copy default x11
incus profile edit x11
config:
environment.DISPLAY: :0
description: x11
devices:
x11:
bind: container
connect: unix:@/tmp/.X11-unix/X0
listen: unix:@/tmp/.X11-unix/X0
security.gid: '1000'
security.uid: '1000'
type: proxy
name: x11
Now we can create gui ct. Iâm using alpine for gui ct, you can use whatever you like.
incus launch images:alpine/3.19 htpc -p default -p wayland -p pipewire -p hwac -p autostart
Container basic step
Alpine default shell is ash.
incus exec htpc ash
apk update
apk upgrade
I like bash.
apk add bash nano bash-doc bash-completion
sed -i 's/ash/bash/g' /etc/passwd
doas is a replace for sudo.
apk add doas
echo "permit persist :wheel" >> /etc/doas.d/doas.conf
wlroots doesnât like NVIDIA
Intel Video - Alpine Linux
Radeon Video - Alpine Linux
Install intel video driver.
apk add mesa-dri-gallium mesa-va-gallium linux-firmware-i915
If older than gen8:
apk add intel-media-driver
else:
apk add libva-intel-driver
Do you like me who is not a native English speaker, letâs modify alpine to suit us.
sed -i 's/#unicode="YES"/unicode="YES"/g' /etc/rc.conf
You should find font support your language. For example for east asia:
apk add musl-locales font-noto-cjk
Itâs container gui part.
apk add labwc font-dejavu font-awesome waybar dbus dbus-x11 swaybg nwg-launchers xfce4-terminal xdg-user-dirs thunar exo tumbler chromium mpv mousepad rhythmbox wayvnc copyq lxappearance wireplumber pipewire pipewire-pulse pipewire-zeroconf xarchiver openssh
If you are not a native English speaker, you need to install input method. Somehow ibus malfunction, we need to install fcitx5 in alpine edge repository. Edge - Alpine Linux
You need to install fcitx5 addons suit you language. For example fcitx5-chinese-addons.
apk add fcitx5 fcitx5-gtk3 fcitx5-configtool fcitx5-chinese-addons
LANG=zh_CN.UTF-8 apk add lang
Start service we need.
rc-update add dbus
rc-update add sshd
service sshd start
Create user. Letâs say user is lxc:
adduser -s /bin/bash -g "lxc" lxc
set you password now
adduser lxc wheel
adduser lxc video
adduser lxc input
adduser lxc audio
User lxc is now usable.
su -l lxc
LC_ALL=zh_CN.UTF-8 xdg-user-dirs-update
Donât start labwc yet. Click Win+Enter open a xfce4-terminal. Push .bashrc and .profile to gui ct.
incus file push ~/.bashrc htpc/home/lxc/
incus file push ~/.profile htpc/home/lxc/
You can close xfce4-terminal using Win+Shift+q.
If you are not a native English speaker, add this to .bashrc in gui ct.
export LANG=zh_CN.UTF-8
export GTK_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx
export QT_IM_MODULE=fcitx
Alpine doesnât provide XDG_RUNTIME_DIR, and wayland compositor(labwc) rely on it. So we need to create it every time we login. So we use .profile. Just add this:
if [ -z "$XDG_RUNTIME_DIR" ]; then
XDG_RUNTIME_DIR="/tmp/$(id -u)-runtime-dir"
mkdir -pm 0700 "$XDG_RUNTIME_DIR"
export XDG_RUNTIME_DIR
fi
if [ -z "$XDG_CONFIG_HOME" ] ; then
export XDG_CONFIG_HOME=$HOME/.config
fi
Now we have XDG_CONFIG_HOME, we can actually run labwc, donât do it, else it will be a headless wayland session.
We are going to tell labwc to use host wayland socket. Just add this in .profile:
if ! [ -S "$XDG_RUNTIME_DIR/wayland-1" ] ; then
ln -s /mnt/wayland-1 $XDG_RUNTIME_DIR/wayland-1
fi
if [ -z "$WAYLAND_DISPLAY" ] ; then
export WAYLAND_DISPLAY=wayland-1
fi
Now we can actually run labwc:
source .bashrc
source .profile
labwc
If you see a black screen with a curse then you are succeed.
Letâs start custom it.
custom labwc
mkdir -p ~/config/labwc
nano ~/config/labwc/autostart
swaybg -i $HOME/Pictures/background.jpg -m fill >/dev/null 2>&1 &
copyq --start-server enable >/dev/null 2>&1 &
waybar >/dev/null 2>&1 &
nano ~/config/labwc/environment
# This allows xdg-desktop-portal-wlr to function (e.g. for screen-recording)
XDG_CURRENT_DESKTOP=wlroots
XDG_SESSION_DESKTOP=wlroots
XDG_SESSION_TYPE=wlroots
# Force firefox to use wayland backend
MOZ_ENABLE_WAYLAND=1
# Set cursor theme.
# Find icons themes with the command below or similar:
# find /usr/share/icons/ -type d -name "cursors"
# XCURSOR_THEME=breeze_cursors
# Disable hardware cursors. Most users wouldn't want to do this, but if you
# are experiencing issues with disappearing cursors, this might fix it.
WLR_NO_HARDWARE_CURSORS=1
# For Java applications such as JetBrains/Intellij Idea, set this variable
# to avoid menus with incorrect offset and blank windows
# See https://github.com/swaywm/sway/issues/595
_JAVA_AWT_WM_NONREPARENTING=1
QT_QPA_PLATFORM=wayland-egl
QT_WAYLAND_FORCE_DPI=physical
QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
ELM_DISPLAY=wl
SDL_VIDEODRIVER=wayland
SAL_USE_VCLPLUGIN=gtk3
GTK_USE_PORTAL=0
MOZ_ENABLE_WAYLAND=1
MOZ_DBUS_REMOTE=1
XDG_SESSION_TYPE=wayland
intel_iommu=igfx_off
LIBVA_DRIVER_NAME=i965
WAYLAND_DISPLAY=wayland-0
nano ~/config/labwc/menu.xml
<?xml version="1.0" encoding="UTF-8"?>
<openbox_menu>
<menu id="client-menu">
<item label="Minimize">
<action name="Iconify" />
</item>
<item label="Maximize">
<action name="ToggleMaximize" />
</item>
<item label="Fullscreen">
<action name="ToggleFullscreen" />
</item>
<item label="Decorations">
<action name="ToggleDecorations" />
</item>
<item label="AlwaysOnTop">
<action name="ToggleAlwaysOnTop" />
</item>
<!--
Any menu with the id "workspaces" will be hidden
if there is only a single workspace available.
-->
<menu id="workspace" label="Workspace">
<item label="Move to left workspace">
<action name="SendToDesktop" to="left" />
</item>
<item label="Move to right workspace">
<action name="SendToDesktop" to="right" />
</item>
</menu>
<item label="Close">
<action name="Close" />
</item>
</menu>
<menu id="root-menu">
<item label="App launcher">
<action name="Execute" command="nwggrid" />
</item>
<item label="File browser">
<action name="Execute" command="thunar" />
</item>
<item label="Web browser">
<action name="Execute" command="chromium" />
</item>
<item label="Terminal">
<action name="Execute" command="xfce4-terminal" />
</item>
<item label="Reconfigure">
<action name="Reconfigure" />
</item>
<item label="Exit">
<action name="Exit" />
</item>
</menu>
</openbox_menu>
nano ~/config/labwc/rc.xml
<?xml version="1.0"?>
<labwc_config>
<keyboard>
<default />
<!-- Use a different terminal emulator -->
<keybind key="W-Return">
<action name="Execute" command="foot" />
</keybind>
<!--
Remove a previously defined keybind
A shorter alternative is <keybind key="W-F4" />
-->
<keybind key="W-F4">
<action name="None" />
</keybind>
</keyboard>
<mouse>
<default />
<!-- Show a custom menu on desktop right click -->
<context name="Root">
<mousebind button="Right" action="Press">
<action name="ShowMenu" menu="root-menu" />
</mousebind>
</context>
</mouse>
<focus>
<focusNew>no</focusNew>
<focusLast>no</focusLast>
<followMouse>no</followMouse>
<focusDelay>200</focusDelay>
<underMouse>no</underMouse>
<raiseOnFocus>no</raiseOnFocus>
</focus>
</labwc_config>
Custom waybar now. waybar(5) â Arch manual pages
Custom waybar
mkdir -p ~/config/waybar
all icon below will be something else because missing font-awesome.
nano ~/config/waybar/config
{
"layer": "top", // Waybar at top layer
// "position": "left", // Waybar position (top|bottom|left|right)
// "height": 38, // Waybar height (to be removed for auto height)
//"width": 1000, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules
"modules-left": ["wlr/taskbar"],
"modules-center": ["clock"],
"modules-right": ["wireplumber", "tray", "cpu", "memory", "network", "temperature"],
// Modules configuration
"clock": {
"locale": "zh_CN.UTF-8",
"format": "<span font='Noto Sans CJK SC'>{:%H:%M} </span>ï ",
"format-alt": "<span font='Noto Sans CJK SC'>{:%Y-%m-%d} </span>ïł ",
"tooltip-format": "<span size='12pt' font='Noto Sans CJK SC'><big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt></span>",
"calendar": {
"mode" : "year",
"mode-mon-col" : 3,
"weeks-pos" : "right",
"on-scroll" : 1,
"on-click-right": "mode",
"format": {
"months": "<span color='#ffead3'><b>{}</b></span>",
"days": "<span color='#ecc6d9'><b>{}</b></span>",
"weeks": "<span color='#99ffdd'><b>W{}</b></span>",
"weekdays": "<span color='#ffcc66'><b>{}</b></span>",
"today": "<span color='#ff6699'><b><u>{}</u></b></span>"
}
},
"actions": {
"on-click-right": "mode",
"on-scroll-up": "shift_up",
"on-scroll-down": "shift_down"
}
},
"cpu": {
"format": "{usage}% ï",
"tooltip": false
},
"memory": {
"format": "{}% ïŸ"
},
"temperature": {
// "thermal-zone": 2,
// "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
"critical-threshold": 80,
// "format-critical": "{temperatureC}°C {icon} ",
"format": "{temperatureC}°C {icon} ",
"format-icons": ["ï«", "ï", "ï©"]
},
"network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ï«",
"format-ethernet": "{ifname} ï ",
"tooltip-format": "{ifname} gateway {gwaddr} ïš",
"format-linked": "{ifname} (No IP) ï",
"format-disconnected": "Disconnected â ",
"format-alt": "<span font='Noto Sans CJK SC'>{ifname}: {ipaddr} /{cidr}</span> ï"
},
"wlr/taskbar": {
"format": "{icon} {name} ",
"icon-size": 18,
"tooltip-format": "{title}",
"on-click": "minimize-raise"
},
"tray": {
"icon-size": 20,
"spacing": 5
},
"wireplumber": {
"format": "{volume}% ïš",
"format-muted": "ïŠ",
"max-volume": 1.0,
"on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
"on-scroll-up": "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+",
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
}
}
nano ~/config/waybar/style.css
#waybar {
font-family: "Noto Sans CJK SC";
font-size: 18px;
}
#window {
padding: 0 1px;
}
window#waybar {
border: none;
border-radius: 0;
box-shadow: none;
text-shadow: none;
transition-duration: 0s;
color: rgba(217, 216, 216, 1);
background: rgba(180, 153, 255, 0.6);
}
window#waybar.solo {
background: rgba(35, 31, 32, 0.5);
}
window#waybar.empty {
/* display: none; */
}
/* Repeat style here to ensure properties are overwritten as there's no !important and button:hover above resets the colour */
#custom-powermenu {
color:lightcoral;
}
#temperature {
color: rgb(153,35,51);
}
#idle_inhibitor {
color: powderblue;
}
#language {
color: orchid;
}
#taskbar {
background-color: lightyellow;
border-radius: 8px;
}
#taskbar button.active {
background-color: yellow;
border-radius: 4px;
padding: 2px;
margin: 2px;
}
#taskbar button {
background-color: transparent;
border-radius: 4px;
padding: 2px;
margin: 2px;
}
#tray {
background-color: transparent;
}
#wireplumber,
#wireplumber.muted,
#clock,
#taskbar,
#mode,
#battery,
#cpu,
#memory,
#network,
#idle_inhibitor,
#custom-media {
color: rgb(157, 25, 6);
margin: 0 2px;
}
#battery {
color: lightgreen;
}
#battery.warning {
color: rgba(255, 210, 4, 1);
}
#battery.critical {
color: rgba(238, 46, 36, 1);
}
#battery.charging {
color: rgba(217, 216, 216, 1);
}
#custom-storage.warning {
color: rgba(255, 210, 4, 1);
}
#custom-storage.critical {
color: rgba(238, 46, 36, 1);
}
@keyframes blink {
to {
background-color: #ffffff;
color: black;
}
}
Now you can restart labwc. And something like this will show. Yes, you need to find font-awesome icon.
Setting GTK theme
You can custom it the way gtk does using lxappearance. But something will not update well. Here is a sh script I copyed from somewhere to proper update:
#!/bin/sh
# usage: import-gsettings
config="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-3.0/settings.ini"
if [ ! -f "$config" ]; then exit 1; fi
gnome_schema="org.gnome.desktop.interface"
gtk_theme="$(grep 'gtk-theme-name' "$config" | sed 's/.*\s*=\s*//')"
icon_theme="$(grep 'gtk-icon-theme-name' "$config" | sed 's/.*\s*=\s*//')"
cursor_theme="$(grep 'gtk-cursor-theme-name' "$config" | sed 's/.*\s*=\s*//')"
font_name="$(grep 'gtk-font-name' "$config" | sed 's/.*\s*=\s*//')"
gsettings set "$gnome_schema" gtk-theme "$gtk_theme"
gsettings set "$gnome_schema" icon-theme "$icon_theme"
gsettings set "$gnome_schema" cursor-theme "$cursor_theme"
gsettings set "$gnome_schema" font-name "$font_name"
Now the most weird part.
We have to start pipewire manually.
/usr/libexec/pipewire-launcher >/dev/null 2>&1 &
Then remove itâs socket.
rm $XDG_RUNTIME_DIR/pipewire-0
Then swap with host pipewire socket.
ln -s /mnt/pipewire-0 $XDG_RUNTIME_DIR/pipewire-0
For waybar to display right volume, kill it and start it.
killall waybar
nohup waybar >/dev/null 2>&1 &
If you install fcitx5:
nohup fcitx5 -d >/dev/null 2>&1 &
Itâs too complex to do every time login container. So a sh script:
#/bin/sh
/usr/libexec/pipewire-launcher >/dev/null 2>&1 &
sleep 3s
rm $XDG_RUNTIME_DIR/pipewire-0
ln -s /mnt/pipewire-0 $XDG_RUNTIME_DIR/pipewire-0
killall waybar
nohup waybar >/dev/null 2>&1 &
nohup fcitx5 -d >/dev/null 2>&1 &
Thatâs it. Now you can use this gui ct as normal de.