Thymio 2 Device Manager provides a minimal user interface to control the Thymio Device Manager (TDM) backend. It adds a menu with About and Quit or Exit entries as a status item on macOS or a tray item on Linux; Windows provides an Exit entry. The TDM is launched as a subprocess, and the launcher only terminates the subprocess it started.
The Thymio Device Manager backend doesn't require any modification.
- macOS with Xcode Command Line Tools or Xcode installed. This provides
make,swiftc,codesign,ditto,hdiutil,xcrun,stapler, andspctl. - Python 3.9+ for validating and copying the supplied backend.
- A built macOS
thymio-device-managerexecutable atmac/thymio-device-manager, or supplied usingTDM_EXECUTABLE. The Makefile copies this file into the generated.appbundle. - For signed releases outside the Mac App Store, an Apple Developer account, a
Developer ID Applicationcertificate in the keychain, and a notarytool keychain profile.
Install the Xcode Command Line Tools with:
xcode-select --install- .NET 8 SDK or a newer .NET SDK that can target
net8.0-windows. - Network access on first restore/build so NuGet packages and Windows targeting/runtime packs can be downloaded when needed.
- A Windows x64
thymio-device-manager.exeatwin/thymio-device-manager.exe, or supplied usingTdmExecutable. Keep any DLLs required by that backend beside it; the build copies them too. - Windows is required to run the generated launcher, even when cross-building it from macOS or Linux.
- The upstream backend requires the Bonjour service and applicable Thymio USB drivers. Bundling
dnssd.dllsupplies the Bonjour client library, not the service.
- GNU Make.
- Python 3.9+.
- A C compiler such as
gcc. pkg-config.- GTK 3 and Ayatana AppIndicator development headers and libraries, including GLib/GIO.
- A Linux x64
thymio-device-managerexecutable atlinux/thymio-device-manager, or supplied usingTDM_EXECUTABLE, with executable permissions. - A graphical desktop session. AppIndicator support enables the optional tray icon.
Common distro package commands:
# Debian/Ubuntu
sudo apt install build-essential python3 pkg-config libgtk-3-dev libayatana-appindicator3-dev
# Fedora
sudo dnf install gcc make python3 pkgconf-pkg-config gtk3-devel libayatana-appindicator-gtk3-devel
# Arch Linux
sudo pacman -S base-devel python pkgconf gtk3 libayatana-appindicatorOrdinary builds use the executable you supply. They never download TDM, consult GitHub releases, or require Apple credentials. Existing default locations still work:
make -C mac
make -C linux
dotnet build win/thymio-2-device-manager.csproj -c ReleaseTo test another backend without replacing the checked-in executable:
make -C mac TDM_EXECUTABLE="/absolute/path/my backend/thymio-device-manager"
make -C linux TDM_EXECUTABLE="/absolute/path/my backend/thymio-device-manager"
dotnet build win/thymio-2-device-manager.csproj -c Release \
-p:TdmExecutable="/absolute/path/my backend/thymio-device-manager.exe"Run only the command for your platform. Relative backend paths are resolved from
the respective mac, linux, or win directory. The source can have any
filename: builds copy it under the canonical name the launcher expects.
Windows also copies neighboring *.dll files. Keep each backend's dependencies
in its own source directory; use a fresh build directory or dotnet clean when
switching between backends with different DLL sets.
Missing files, incompatible executable formats, and missing Unix executable permissions cause an error instead of silently substituting another backend. The supplied binary is copied unchanged; Mac release signing changes only the bundled copy. Normal Mac builds compile the launcher for the host architecture and ad-hoc sign the launcher without Developer ID credentials. When the supplied backend is signed, the app bundle is also ad-hoc signed; an unsigned backend is preserved and the local app bundle is left unsealed. Choose a backend compatible with that Mac (or an Intel backend where Rosetta is available).
The same overrides work with make -C mac dmg, make -C linux deb,
make -C linux deb-podman, and dotnet publish. For example, create a local
Mac disk image without notarization:
make -C mac dmg TDM_EXECUTABLE="/absolute/path/to/thymio-device-manager"For a universal Mac launcher, add UNIVERSAL=1; this requires a universal backend
containing both Intel and Apple Silicon code. Local compilation still requires
the installed platform SDKs/libraries, and a first .NET restore needs NuGet access.
VERSION.txt supplies the default wrapper version. APP_VERSION and APP_BUILD
can override Mac/Linux metadata; -p:AppVersion=1.2.3 overrides Windows metadata.
TDM_VERSION is used only by the explicit release downloader, never by ordinary
local builds.
The application is implemented in Swift. As a command-line program, it can be compiled with the swiftc compiler. It expects a command thymio-device-manager in the same directory. This can be bundled as a standard self-contained Mac application in a .app directory.
At startup, the application launches its own thymio-device-manager subprocess. It does not kill existing thymio-device-manager processes or remove lock files that may belong to another instance.
If you distribute the app outside the Mac App Store, the warning:
Apple could not verify "...app" is free of malware
usually means the app was copied into a .dmg without the full Apple release flow. To avoid that warning, you need all of the following:
- Sign the bundled executable code with a
Developer ID Applicationcertificate. - Enable the hardened runtime when signing.
- Submit the app or disk image to Apple's notary service with
xcrun notarytool. - Staple the notarization ticket to the final
.appand.dmg.
The mac/Makefile includes helper targets for this flow. Generated files are written under mac/build/.
First, store your notary credentials in the keychain once:
xcrun notarytool store-credentials tdm-notary \
--apple-id "<apple-id>" \
--team-id "<team-id>" \
--password "<app-specific-password>"Then build, sign, notarize, and staple the app and DMG:
make -C mac release \
APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)" \
NOTARY_PROFILE="tdm-notary"This target signs mac/build/Thymio 2 Device Manager.app, notarizes and staples it, creates mac/build/thymio-2-device-manager.dmg, signs the DMG, notarizes it, and staples the final disk image.
Add UNIVERSAL=1 and a universal TDM_EXECUTABLE to produce the same architecture
coverage as CI. For App Store Connect API authentication instead of Apple ID,
create the local profile with:
xcrun notarytool store-credentials tdm-notary \
--key /path/to/AuthKey.p8 --key-id YOUR_KEY_ID --issuer YOUR_ISSUER_IDOmit --issuer for an individual API key. NOTARY_KEYCHAIN can select an explicit
keychain containing the profile. Signing and notarization run only through the
explicit signing targets or release, never through a normal local build.
You can also run the steps individually:
make -C mac sign-app APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)"
make -C mac notarize-app APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)" NOTARY_PROFILE="tdm-notary"
make -C mac staple-app APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)" NOTARY_PROFILE="tdm-notary"
make -C mac sign-dmg APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)"
make -C mac notarize-dmg APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)" NOTARY_PROFILE="tdm-notary"
make -C mac staple-dmg APP_SIGN_IDENTITY="Developer ID Application: Your Company (TEAMID)" NOTARY_PROFILE="tdm-notary"To verify locally:
make -C mac verify-app
make -C mac verify-dmgExpected successful checks include:
spctl: accepted
source=Notarized Developer ID
If notarization reports The binary is not signed with a valid Developer ID certificate., check the certificate you passed in APP_SIGN_IDENTITY. It must be a Developer ID Application: certificate, not Apple Development, Mac App Distribution, or Developer ID Installer.
To list the code-signing identities available on the machine:
security find-identity -v -p codesigningTo inspect the actual authority and timestamp attached to each executable inside the app:
codesign -dvv "mac/build/Thymio 2 Device Manager.app/Contents/MacOS/thymio-2-device-manager"
codesign -dvv "mac/build/Thymio 2 Device Manager.app/Contents/MacOS/thymio-device-manager"The application is implemented in C# as a WinForms app targeting the modern .NET SDK.
The Linux launcher is implemented in C with GTK 3 and Ayatana AppIndicator. It opens a small status window and provides a tray menu with Show Status, About, and Quit actions. The window reports backend startup failures and unexpected exits. Launching the application again presents the existing window without starting another backend.
When an AppIndicator tray is available, closing the status window keeps the manager in the tray. Without a tray, the window stays available and closing it stops the manager. Quit always stops the subprocess started by this launcher.
At startup, the Linux launcher starts its own thymio-device-manager subprocess. It does not kill existing thymio-device-manager processes or remove lock files that may belong to another instance.
With the Windows prerequisites installed, run:
dotnet build win/thymio-2-device-manager.csproj -c ReleaseThis produces thymio-2-device-manager.exe under win/build/bin/Release/net8.0-windows/, with the display name Thymio 2 Device Manager.
To publish a single-file Windows x64 build from macOS, Linux, or Windows with the .NET 8 SDK or a newer SDK that can target net8.0-windows, first place the Windows TDM executable and its DLLs at win/thymio-device-manager.exe, or pass -p:TdmExecutable="/path/to/backend.exe". A macOS or Linux binary cannot be used in its place.
From the repository root, run:
dotnet publish win/thymio-2-device-manager.csproj \
-c Release \
-r win-x64 \
--self-contained true \
-p:PublishSingleFile=true \
-p:IncludeAllContentForSelfExtract=true \
-p:DebugType=embeddedDistribute the resulting executable:
win/build/bin/Release/net8.0-windows/win-x64/publish/thymio-2-device-manager.exe
This bundles the launcher, the .NET runtime, thymio-device-manager.exe, and its neighboring DLLs into one file. Users do not need to install .NET or keep a separate TDM executable beside the launcher. The bundled files are automatically extracted to disk at startup, and the launcher starts TDM from the extraction directory.
The Windows TDM executable must be present before building or publishing;
validation fails if it is missing or is not a Windows x64 executable. Bonjour
service and USB drivers still need to be installed separately. Test the published
executable on Windows before distribution. The diagnostic command
thymio-2-device-manager.exe --check-backend checks discovery, extraction, and DLL loading by
running the backend's --help command without opening the tray application.
It returns zero on success (TDM itself returns one for --help).
For a regular dotnet build, the project instead copies win/thymio-device-manager.exe next to the launcher, where it must remain.
The Windows project uses EnableWindowsTargeting, so dotnet build and dotnet publish can target Windows from macOS or Linux with the current .NET SDK.
The resulting executable is still a Windows program and must be run on Windows.
With the Linux prerequisites installed, run:
make -C linuxThis produces the launcher executable under linux/build/.
The launcher expects thymio-device-manager in the same directory as thymio-2-device-manager.
The Makefile requires the supplied backend and copies it into linux/build/.
On some Linux desktop environments, AppIndicator support depends on the desktop shell configuration or an installed tray extension. The status window works without a tray, including on Wayland.
Release packages target Ubuntu 26.04 on x86-64. Older Ubuntu and Debian
releases are not promised to work with the upstream backend. From Arch Linux or
Manjaro, use the Podman target to compile and package inside Ubuntu 26.04 LTS while
keeping the resulting .deb in your local checkout.
Install the host prerequisites on Arch Linux or Manjaro:
sudo pacman -S --needed podman make pythonWith rootless Podman configured for your user and the backend executable at
linux/thymio-device-manager, run from the repository root without sudo:
make -C linux deb-podman \
APP_VERSION=1.0.0 APP_BUILD=1 \
DEB_MAINTAINER='Mobsya <mobsya@mobsya.org>'The result is linux/build/thymio-2-device-manager_1.0.0-1_amd64.deb, owned by
your user. No GTK development libraries or Debian packaging tools are required
on the host. The first build downloads Ubuntu 26.04 LTS and installs the build
dependencies into the image localhost/thymio-deb-builder:ubuntu-26.04.
It uses the Ubuntu 26.04-based buildpack-deps:resolute-curl image, which includes
CA certificates, and downloads Ubuntu packages over HTTPS from the Init7 mirror
in Switzerland. Subsequent builds reuse those image layers. To choose another
mirror, add UBUNTU_MIRROR=https://archive.ubuntu.com/ubuntu to the make command.
The container mounts the source and backend read-only and builds as your user
using --userns=keep-id. Intermediate container outputs go under
linux/build/podman/; the final package is placed directly in linux/build/.
The packaging step runs without network access. The BUILD_DIR and
TDM_EXECUTABLE overrides also work, including a backend outside the checkout.
To refresh the cached Ubuntu image and build dependencies, run:
podman build --platform=linux/amd64 --pull=always --no-cache \
--tag localhost/thymio-deb-builder:ubuntu-26.04 \
--file linux/packaging/Containerfile linux/packagingIf using a custom mirror, also pass --build-arg UBUNTU_MIRROR=<mirror-url>
when refreshing the image.
Then run make -C linux deb-podman again with the release metadata above.
Alternatively, use an updated Ubuntu 26.04 LTS amd64 machine or VM. The native packaging target uses the Debian/Ubuntu package database to determine runtime dependencies. Building on a newer release can introduce newer library requirements. Validate every release on Ubuntu 26.04 and on any older distribution you intend to support; dependency detection alone is not a compatibility test.
Install the build and packaging prerequisites:
sudo apt update
sudo apt install build-essential pkg-config libgtk-3-dev libayatana-appindicator3-dev dpkg-dev python3 \
desktop-file-utils libavahi-client3 libavahi-common3 avahi-daemon libstdc++6Place the Linux x86-64 thymio-device-manager executable at
linux/thymio-device-manager, with its executable bit set. Packaging requires
this file and copies it unchanged. Both the launcher and the backend must have
all their required shared libraries and symbols available on the build machine.
The upstream backend requires Ubuntu 26.04-era glibc and C++ runtime libraries;
older distributions and ARM builds are outside the release package's supported targets.
Build without sudo, supplying the release maintainer's name and email:
make -C linux deb \
APP_VERSION=1.0.0 APP_BUILD=1 \
DEB_MAINTAINER='Mobsya <mobsya@mobsya.org>'This produces linux/build/thymio-2-device-manager_1.0.0-1_amd64.deb.
APP_VERSION defaults to VERSION.txt and APP_BUILD to 1; DEB_MAINTAINER is
required. Use a non-empty Debian revision for APP_BUILD, such as 1 or 2.
The launcher is rebuilt each time to keep its About dialog synchronized with
these values. Runtime dependencies are generated from both executables using
dpkg-shlibdeps, and include explicit dependencies on udev and avahi-daemon. Packaging fails
if dependency analysis finds missing libraries or unresolved symbols.
Copy the .deb to the user's machine and install it with APT so dependencies
are installed automatically:
sudo apt install ./thymio-2-device-manager_1.0.0-1_amd64.debOpen Thymio 2 Device Manager from the application menu, or run
thymio-2-device-manager from a terminal as your normal desktop user. The package
does not start the application automatically or install a background service.
A graphical desktop session is required; tray support is optional.
The launcher and backend are installed together under
/usr/lib/thymio-2-device-manager/. The command in /usr/bin/ is a symlink to the
launcher, which continues to find the backend beside its own executable.
If the application appears to do nothing, make sure you have the current package: older launchers used only the legacy X11 tray API and could be invisible on Wayland. The current launcher always opens a status window. On Ubuntu GNOME, enable the Ubuntu AppIndicators extension in the Extensions application if you also want the tray icon. No extension is required to use the status window.
For startup diagnostics, run thymio-2-device-manager in a terminal as your
desktop user. Backend output goes to that terminal; startup errors and exit
status also appear in the status window. When replacing an older invisible
launcher, log out and back in before launching the updated version so the old
process does not handle the new launch request.
The package owns /usr/lib/udev/rules.d/70-thymio-device-manager.rules:
# Thymio II and wireless dongles: allow the active local desktop user USB access.
SUBSYSTEM=="usb", ATTR{idVendor}=="0617", ATTR{idProduct}=="000a", TAG+="uaccess"
SUBSYSTEM=="usb", ATTR{idVendor}=="0617", ATTR{idProduct}=="000c", TAG+="uaccess"
The 70- prefix ensures these tags are present before 73-seat-late.rules
applies access permissions. Access is granted to the active local desktop user
through the system's udev/logind session management; it does not grant access to
every user or to an SSH-only session.
Installation and upgrades reload the rules and request change events only for
the two matching USB devices. If udev is unavailable during installation, the
package can still be installed; the rules apply when udev starts. Reconnect the
robot or dongle if access is not refreshed immediately, and make sure you are
using an active local desktop session. Administrator overrides with the same
filename under /etc/udev/rules.d/ take precedence and are preserved.
Remove the package with:
sudo apt remove thymio-2-device-manager
# To also purge any package configuration state:
sudo apt purge thymio-2-device-managerRemoval deletes the packaged rules and reloads udev. Reconnect previously connected devices to refresh their permissions after removal.
The launcher regression tests use a private D-Bus session and virtual display. On a Debian/Ubuntu development machine with the build prerequisites installed:
sudo apt install dbus-daemon xvfb xauth
make -C linux testThey exercise the visible status window without a tray, AppIndicator registration
and tray loss, repeated activation, About, startup failures, unexpected exits,
and graceful/forced Quit using disposable backend fixtures under linux/build/.
Run the packaging regression tests from the repository root:
python3 -B -m unittest discover -s linux/packaging -p 'test_*.py'These tests check package staging, validation failures, and maintainer scripts using isolated substitutes for Debian tools and udev. They do not install a package or modify USB permissions. Inspect the real artifact separately:
dpkg-deb --info linux/build/thymio-2-device-manager_1.0.0-1_amd64.deb
dpkg-deb --contents linux/build/thymio-2-device-manager_1.0.0-1_amd64.debBefore distributing a release, install it on Ubuntu 26.04, upgrade
it with a higher APP_BUILD, then remove and purge it. Check the application-menu
entry, About version, and Quit behavior. On desktops with hardware, verify that
both 0617:000a and 0617:000c are usable without sudo, including when connected
before installation. Verify package installation in an offline environment where
udev is not running as well.
The Build apps and draft release workflow runs when a v* wrapper tag is pushed,
or manually from the Actions page. Manual runs on a branch produce Actions
artifacts, including an ad-hoc Mac image. Tag runs require Apple signing secrets
and create a draft GitHub release only after all platform checks pass.
Manually selecting an existing release tag uses the same signed release flow.
The version files have separate purposes:
| File | Purpose |
|---|---|
VERSION.txt |
Wrapper version, numeric major.minor.patch; tag must be v<version> |
TDM_VERSION |
Pinned upstream release version, without the leading v |
The initial backend pin is 1.0.0. The upstream release must be published,
with these assets, before the workflow can succeed:
thymio-device-manager-1.0.0-macos-universal.tar.gz
thymio-device-manager-1.0.0-windows-x64.zip
thymio-device-manager-1.0.0-linux-x64.tar.gz
SHA256SUMS
Downloads come from Mobsya/thymio-device-manager releases. Missing assets, checksum mismatches, incompatible binaries, unsafe archive entries, and missing upstream notices fail the build. CI never falls back to the checked-in backend executables for a release package.
To explicitly fetch a backend for local use, choose a new staging directory:
python3 scripts/fetch_tdm.py --platform macos-universal --output .build/tdm-macos
make -C mac UNIVERSAL=1 \
TDM_EXECUTABLE="$PWD/.build/tdm-macos/bin/thymio-device-manager" \
TDM_METADATA_DIR="$PWD/.build/tdm-macos/metadata"Use linux-x64 or windows-x64 for the other platforms. The downloader refuses
to replace an existing output directory. Each extracted package includes a
metadata directory with upstream notices, version, provenance, and the verified
archive checksum. Mac/Linux packaging accepts TDM_METADATA_DIR; Windows
accepts -p:TdmMetadataDir=.... CI includes these files in every release package.
Configure these Actions secrets in the wrapper repository; never commit them:
| Secret | Value |
|---|---|
MACOS_CERTIFICATE_P12 |
Base64-encoded Developer ID Application .p12, including its private key |
MACOS_CERTIFICATE_PASSWORD |
Password protecting the .p12 |
APPLE_API_KEY_P8 |
Contents of the App Store Connect .p8 private key |
APPLE_API_KEY_ID |
API key ID |
APPLE_API_ISSUER_ID |
Issuer ID for a team API key; leave unset for an individual key |
Set the Actions variable MACOS_SIGN_IDENTITY to the complete certificate
identity, such as Developer ID Application: Mobsya (TEAMID).
The workflow imports the certificate into a temporary keychain, signs both
executables and the app, notarizes/staples the app and DMG, and removes temporary
credentials even if a build fails. No Apple ID password or provisioning profile
is needed for this flow. A tag build never silently substitutes an unsigned Mac
package when credentials are missing.
-
Publish the desired upstream TDM release and set
TDM_VERSIONto its version. -
Set
VERSION.txtto the wrapper version and commit the changes. -
Push the matching wrapper tag, for example:
git tag v1.0.0 git push origin v1.0.0
-
Review the resulting draft's DMG, Windows EXE, Debian package, checksums, and recorded wrapper/backend versions. Test robot detection and Quit on real hardware before publishing the draft.
Rerunning the same tag can update an existing draft. The workflow refuses to
overwrite a published release; create a new wrapper version for subsequent
changes. Upstream updates are explicit changes to TDM_VERSION, not automatic
tracking of the latest release. Windows outputs are unsigned; Bonjour service
and USB drivers remain prerequisites.
Run the platform-independent checks locally:
python3 -B -m unittest discover -s scripts/tests -p 'test_*.py'
python3 -B -m unittest discover -s linux/packaging -p 'test_*.py'CI also builds with the default backend and renamed external backend paths, checks switching binaries and rejecting invalid paths, runs Linux GUI tests under Xvfb, inspects the Debian payload and dependencies, and tests Windows single-file extraction in a clean directory. It mounts and tests the final Mac image on both Intel and ARM runners. Hosted runners cannot check physical USB hardware.