ears-tablet)Working notes for setting up an Asus T101HA (Atom x5-Z8350, 2 GB RAM / ~1.8 GB usable, eMMC) as a Mono + GTK#3 development and runtime machine.
User: ears · Hostname: ears-tablet
Target: Xubuntu 24.04 LTS (Minimal ISO).
Reasons, from the apt/Mono investigation:
gtk-sharp3 is not packaged on 25.10, and upgrading to 26.04 would not
bring it back.gtk-sharp3 is packaged on 24.04 (noble), supported to 2029.Mono + mcs + GTK# is the correct toolchain for this hardware — the .NET SDK
(~800 MB) and Avalonia are far heavier.
Before reinstalling, back up: *.cs, *.glade, SSH keys, browser profile.
lsblk # is /home a separate partition? df -h / free -h mkdir -p ~/backup && cp ~/*.cs ~/*.glade ~/backup/
If /home is its own partition, it can be preserved during install (assign
/home without format). Use the same username to keep permissions clean.
In Windows: Settings → System → About → System type
Ventoy includes IA32 UEFI support, so try it first. If it won't boot, fall back
to Rufus and copy a 32-bit bootia32.efi (GRUB) into /EFI/BOOT on the stick.
From Linux, writing directly:
sudo dd if=xubuntu-24.04-minimal-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync
Verify the target with
lsblkfirst.ddto the wrong disk destroys it silently.
Verify the ISO: sha256sum against the checksum on the download page.
Choose Install, not Try — the live session eats RAM you don't have. Don't open Firefox or anything else during install. Let it create swap.
The installer writes a 64-bit grubx64.efi the firmware can't read. Boot the
live session, chroot in, and:
grub-install --target=i386-efi --efi-directory=/boot/efi
This is not optional on IA32 firmware.
| Component | Notes |
|---|---|
| Wi-Fi | RTL8723BS SDIO — finicky |
| Audio | ESS ES8316 codec, needs UCM configs |
| Touchscreen | Works, but rotation needs manual matrix (§6) |
| Auto-rotation | Needs iio-sensor-proxy + custom handler |
A recent 6.x kernel fixes most of these.
If the user account lacks admin rights:
usermod -aG sudo ears groups ears # confirm 'sudo' appears hostnamectl set-hostname ears-tablet localectl set-locale LANG=en_US.UTF-8 # optional, cosmetic exit # then 'resume'
usermod -aG sudo is the one that isn't optional — without it you can log in
but can't run any admin command.
zram-tools (simple)sudo apt install zram-tools
Edit /etc/default/zramswap:
PERCENT=60 ALGO=zstd
systemd-zram-generator (current, preferred on 24.04+)sudo apt install systemd-zram-generator sudo tee /etc/systemd/zram-generator.conf > /dev/null <<'EOF' [zram0] zram-size = min(ram / 2, 4096) compression-algorithm = zstd swap-priority = 100 EOF sudo systemctl daemon-reload sudo systemctl start systemd-zram-setup@zram0.service swapon --show # expect /dev/zram0 zramctl
The package does nothing without a config file containing at least one section.
Swapping to zram is cheap, so raise it so the kernel actually uses it:
echo 'vm.swappiness=100' | sudo tee /etc/sysctl.d/99-zram.conf
Reboot. Verdict: worth it at 2–4 GB, marginal at 8 GB, skip above 16 GB. zram compresses RAM — it does nothing for low disk space.
Since 24.04, archive config lives in /etc/apt/sources.list.d/ubuntu.sources
(deb822 format). /etc/apt/sources.list being empty is normal, not a symptom.
Most apt advice online predates this and edits a file that does nothing.
sudo tee /etc/apt/sources.list.d/ubuntu.sources > /dev/null <<'EOF' Types: deb URIs: http://archive.ubuntu.com/ubuntu/ Suites: questing questing-updates questing-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: http://security.ubuntu.com/ubuntu/ Suites: questing-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg EOF sudo apt update
Do not drop the Signed-By: line — without it apt falls back to the legacy
trusted-keyring and you get confusing "not signed" errors later.
ubuntu.sources.curtin.orig is the installer's pristine backup. apt ignores
.orig files. Useful as a reference, don't copy blindly.
An EOL release only moves to old-releases.ubuntu.com when the migration
actually runs — which can lag the EOL date by weeks. Pointing there too early
gives four clean 404s. Test before switching:
for p in questing questing-updates questing-backports questing-security; do echo -n "$p: " curl -sI "http://old-releases.ubuntu.com/ubuntu/dists/$p/Release" | head -1 done curl -sI http://archive.ubuntu.com/ubuntu/dists/questing/Release | head -1
200 = pocket exists. 404 = drop it or use the other host.
All read-only, cheapest first:
sudo dpkg --audit # clean = no output at all sudo apt-get check # verifies the dependency tree dpkg -l | grep -v '^ii' | grep -v '^rc' apt-mark showhold # held packages are silently skipped on upgrade
dpkg status codes (desired state, then actual):
| Code | Meaning |
|---|---|
ii | Healthy |
rc | Removed, config left — harmless |
iU | Unpacked, never configured |
iF | Half-configured |
iH | Half-installed |
iW / it | Waiting on / pending triggers |
*R* | Reinstall required → apt install --reinstall <name> |
Then repair:
sudo dpkg --configure -a sudo apt --fix-broken install sudo apt full-upgrade
# WRONG — killall receives 'sudo', 'rm', paths etc. as process names sudo killall apt apt-get dpkg 2>/dev/null sudo rm /var/lib/apt/lists/lock ... # DANGEROUS — rm -rf also targets files named sudo, apt, clean, update in $PWD sudo rm -rf /var/lib/apt/lists/* sudo apt clean sudo apt update
These must be separate lines. Also: killing dpkg mid-transaction is what
creates the broken state. Check first with ps aux | grep -E 'apt|dpkg'.
Don't remove universe — it's an Ubuntu component, not a PPA, and
gtk-sharp3 lives there. And never run
add-apt-repository --remove ppa:repository-name/ppa literally; that's a
placeholder from a copied guide.
full-upgrade checklistfull-upgrade, not upgrade — it permits removals where deps changeddpkg recreates the broken statesudo apt autoremove — but answer n if anything with efi appearsdo-release-upgrade so the newest kernel is running[ -f /var/run/reboot-required ] && cat /var/run/reboot-required.pkgs sudo needrestart # interactive; shows what's running old libs
Kernel / initramfs / systemd / glibc → reboot. Everything else → restart the service.
sudo add-apt-repository universe sudo apt update sudo apt install mono-complete gtk-sharp3
| Package | Contents | Size |
|---|---|---|
mono-runtime | JIT + mscorlib only | ~15 MB |
mono-devel | Compiler + the BCL you need | ~200–400 MB |
mono-complete | Runtime + mcs + entire class library + F#/VB | ~300–500 MB |
Avoid mono-runtime. The BCL is split across dozens of
libmono-system-*-cil packages; hello-world runs, but anything touching LINQ,
XML, HTTP, or System.IO.Ports (the COM-port combo box in homepage.glade)
throws Could not load file or assembly. On a tight disk, mono-devel saves
~100 MB over mono-complete and still covers everything this project needs.
sudo apt install glade # visual designer — skip on this machine sudo apt install libgdiplus # only if using System.Drawing sudo apt install libcanberra-gtk3-module # silences a harmless console warning
mono --version mcs --version pkg-config --list-all | grep gtk-sharp # want gtk-sharp-3.0 apt policy gtk-sharp3
cat > gtkcheck.cs <<'EOF'
using System;
using Gtk;
class GtkCheck {
static void Main() {
Application.Init();
var win = new Window("Hello GTK#3");
win.SetDefaultSize(300, 200);
win.DeleteEvent += (o, args) => Application.Quit();
var button = new Button("Click me");
button.Clicked += (o, args) => Console.WriteLine("Clicked!");
win.Add(button);
win.ShowAll();
Application.Run();
}
}
EOF
mcs -pkg:gtk-sharp-3.0 gtkcheck.cs
mono gtkcheck.exe
Do the throwaway test first — it isolates "is gtk-sharp3 working" from "does my app compile."
Then the real thing:
mcs -pkg:gtk-sharp-3.0 program.cs MainWindow.cs ArduinoConnection.cs -out:test.exe mono test.exe
(ArduinoConnection.cs is needed because MainWindow calls
_arduino.Disconnect(); omitting it gives CS0246.)
$DISPLAY fiddling — Xfce is itself GTK3, so the app picks up the
system theme and the window just appears.ssh -X.-r: DLLs, homepage.glade) must sit next to the .exe, or
you get FileNotFoundException.homepage.glade targets GTK 3.24, which 22.04 and 24.04 both ship.gtk-sharp2 is gone from 24.04 onward — use gtk-sharp3.X11 does not rotate touch input when you rotate the display. The touch device needs its own coordinate transformation matrix.
xrandr # output name — DSI-1, or None-1 on this unit xinput list --name-only # look for Silead / GSL / SIS, NOT 'Touchpad'
On this tablet the output reported as
None-1(xrandr's fallback when the connector type isn't identified — it can change between kernels) and the touchscreen asSIS0457:00 0457:11ED.
| Rotation | Matrix |
|---|---|
| normal | 1 0 0 0 1 0 0 0 1 |
| right (90° CW) | 0 1 0 -1 0 1 0 0 1 |
| left (90° CCW) | 0 -1 1 1 0 0 0 0 1 |
| inverted (180°) | -1 0 1 0 -1 1 0 0 1 |
xrandr --output None-1 --rotate right xinput set-prop "SIS0457:00 0457:11ED" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1
nano ~/rotate.sh
#!/bin/bash
sleep 3
OUT=$(xrandr | awk '/ connected/{print $1; exit}')
xrandr --output "$OUT" --rotate right
TS=$(xinput list --name-only | grep -iE 'silead|gsl|touchscreen|SIS' | grep -vi 'touchpad' | head -1)
[ -n "$TS" ] && xinput set-prop "$TS" "Coordinate Transformation Matrix" 0 1 0 -1 0 1 0 0 1
xset s off
xset -dpms
chmod +x ~/rotate.sh ~/rotate.sh # test before automating
Then Settings → Session and Startup → Application Autostart → Add, command
/home/ears/rotate.sh.
Gotcha: grepping bare
touchmatches the trackpad and rotates the pointer instead. Usetouchscreenand excludetouchpad. To undo:TP=$(xinput list --name-only | grep -i -m1 touchpad) xinput set-prop "$TP" "Coordinate Transformation Matrix" 1 0 0 0 1 0 0 0 1Or just log out — the matrix isn't saved anywhere.
The sleep 3 matters; the touchscreen isn't registered the instant the session
starts. Raise to 5 if touch still comes out wrong.
Try Settings → Display first. Its rotation dropdown often persists on its
own, leaving the script to handle only the xinput part.
sudo nano /etc/lightdm/lightdm.conf
[Seat:*] display-setup-script=/usr/bin/xrandr --output None-1 --rotate right
sudo nano /etc/X11/xorg.conf.d/10-monitor.conf
Section "Monitor"
Identifier "DSI-1"
Option "Rotate" "right"
EndSection
Whole machine including the text console — add to /etc/default/grub:
fbcon=rotate:1 plus video=DSI-1:panel_orientation=right_side_up, then
sudo update-grub.
The desktop paints the wallpaper onto a surface sized for the old geometry and
doesn't repaint after xrandr. On Xfce:
xfdesktop --reload
Also set the image style to scaled — spanned and zoom misbehave badly
when the aspect ratio flips. Rotating before the session starts avoids this
entirely.
Xfce has no built-in handler. Confirm the sensor works before writing anything:
sudo apt install -y iio-sensor-proxy monitor-sensor # tilt the tablet; orientation should print
If nothing prints, this unit needs a kernel quirk — not worth chasing.
monitor-sensor | while read -r line; do
case "$line" in
*normal*) R=normal; M="1 0 0 0 1 0 0 0 1" ;;
*right-up*) R=right; M="0 1 0 -1 0 1 0 0 1" ;;
*left-up*) R=left; M="0 -1 1 1 0 0 0 0 1" ;;
*bottom-up*) R=inverted; M="-1 0 1 0 -1 1 0 0 1" ;;
*) continue ;;
esac
xrandr --output DSI-1 --rotate "$R"
xinput set-prop "SIS0457:00 0457:11ED" "Coordinate Transformation Matrix" $M
done
Most community auto-rotate scripts only call
xrandrand leave touch behind. If a script doesn't containxinput set-prop, it won't rotate touch.
sudo apt install alsa-utils # this is what provides aplay aplay --version aplay -l # list playback devices aplay /usr/share/sounds/alsa/Front_Center.wav speaker-test -c 2 -t wav -l 1
PipeWire/PulseAudio layer:
pactl info # "Server Name" tells you which pactl list short sinks paplay /usr/share/sounds/alsa/Front_Center.wav
Neither config file exists by default — create whichever you prefer:
nano ~/.asoundrc # per-user; wins over the system file # or sudo nano /etc/asound.conf # system-wide, incl. root and systemd services
defaults.pcm.card 1 defaults.ctl.card 1
ctl is what alsamixer reads, pcm is what aplay reads — set both. No
reload needed; it's read per-process.
With plughw conversion behaviour as the default:
pcm.!default {
type plug
slave.pcm "hw:1,0"
}
ctl.!default {
type hw
card 1
}
Verify — -v prints the resolved device:
aplay -D default -v /usr/share/sounds/alsa/Front_Center.wav 2>&1 | head -20
cat /proc/asound/modules sudo nano /etc/modprobe.d/alsa-card-order.conf
options snd_soc_sst_bytcr_rt5640 index=0 options snd_hda_intel index=1
sudo update-initramfs -u && sudo reboot
~/.asoundrc only governs programs
talking to ALSA directly (aplay, alsamixer). For desktop apps use
pactl set-default-sink or pavucontrol.sudo alsactl store 1aplay -D default file.wav, not -D hw:0,0sudo usermod -aG audio $USER, then re-loginaplay handles WAV/AU/RAW only — use mpg123 or ffplay for MP3/FLACsudo nano /usr/local/bin/myscript.sh sudo chmod +x /usr/local/bin/myscript.sh
Start with #!/bin/bash and use absolute paths — there's almost no
environment at boot.
sudo nano /etc/systemd/system/myscript.service
[Unit] Description=My boot script After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/myscript.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable --now myscript.service systemctl status myscript.service journalctl -u myscript.service
Variations:
Type=simple, drop RemainAfterExitBefore=display-manager.service in [Unit],
WantedBy=display-manager.service in [Install]sudo crontab -e, then
@reboot /usr/local/bin/myscript.sh (no logging, no ordering)After=/Wants= if networking isn't needed — it'll run earlierA root systemd service cannot rotate the display — no
DISPLAY, noXAUTHORITY. Use the config-level methods in §6 instead. Same for audio: a root service has no audio session, so usesystemctl --useror target the hardware device directly.
Tailscale is not required. It creates a private network across the internet — only needed to reach the tablet from outside your home. For a tablet and PC on the same Wi-Fi it's just another background daemon on a 2 GB machine.
Lightest option — SSH:
sudo apt install -y openssh-server
Connect from Windows with WinSCP or scp in PowerShell. No configuration
beyond the above.
Samba is only worth it if you want the tablet's folders to appear in Windows Explorer as a normal network drive.
# state free -h; df -h /; lsblk; swapon --show cat /etc/os-release . /etc/os-release; echo $VERSION_CODENAME # apt health sudo dpkg --audit; sudo apt-get check; apt-mark showhold ls /etc/apt/sources.list.d/ cat /etc/apt/sources.list.d/ubuntu.sources # display xrandr; xinput list --name-only ~/rotate.sh; xfdesktop --reload # audio aplay -l; pactl list short sinks aplay /usr/share/sounds/alsa/Front_Center.wav sudo alsactl store 1 # build mcs -pkg:gtk-sharp-3.0 program.cs MainWindow.cs ArduinoConnection.cs -out:test.exe mono test.exe