Skip to content

Releases: Tropaion/Tobii_Linux

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 02:16

TobiiLinux v0.4.0 — head tracking that holds up, and opentrack without a wrapper

A feature release for the game-output side. Head tracking survives a blink, a
glance away no longer costs you the whole calibration, there is a control for a
tracker that is not quite square to you, and starting opentrack is now enough on
its own — no launch option, no wrapper.

Nothing in the driver, the protocol or the USB layer changed.

Unofficial. Not affiliated with, endorsed by, or supported by Tobii.
Use at your own risk.

One dropped eye no longer costs you the frame

The Eye Tracker 5 loses sight of one eye often — measured here at roughly five
per-eye dropouts a second — and until now every one of those frames produced no
head pose at all. That, not latency, is what the "lag" in head tracking was.

The missing eye is now rebuilt from the last measured distance between your
eyes, so the pose keeps coming from the eye that is still there. Two things
about it are deliberate. The head centre does not lurch sideways toward the
surviving eye, which is what feeding one eye straight in would do. And rotation
is held at its last measurement rather than guessed: only your position keeps
following the eye the tracker can still see. The reconstruction expires after
300 ms, so a long outage gives you no pose rather than a confident invention.

Alongside it, a sample that jumps further in one frame than a head can move is
now held rather than averaged in. The limit is 150 mm between frames, which is
about 5 m/s — several times faster than a seated head moves, so it rejects a
teleport and not a lunge.

A recentre, for a tracker that is not square to you

If your in-game view sits permanently off to one side, hold the posture that
should count as straight ahead and take a rotation reference:

tobii headpose --recenter

or the Recentre view button on the hub's Head tracking for games card.

It measures for a second and takes the median of what it saw. If your head moved
more than 8 degrees across that second, or the tracker measured too little of
it, it refuses and says so, leaving whatever reference you had in place — a
reference caught mid-turn is worse than none.

Pitch is deliberately untouched: that zero already comes from
tobii headpose --calibrate-pitch, and a second reference would fight it.

opentrack: just start it

Running opentrack is now enough. The hub watches the address game output sends
to, and treats a program bound there as the request it needs — no launch
option, no wrapper.

Measured on a hub with no window open: 0 USB file descriptors with nothing
listening, 1 within four seconds of a socket binding 127.0.0.1:4242, and 0
again once it closed.

Two honest caveats. A bound socket is not the same as a game running, so an
opentrack left open with no game keeps the tracker lit; tobii games set wake_for_opentrack false turns the watch off. And only this machine's sockets
are visible, so sending to another machine's opentrack still needs the wrapper.

The virtual joystick and the Wine bridge also still need tobii game --, since
the hub watches the opentrack address and nothing else.

One bad corner no longer fails the whole calibration

A calibration group whose points the tracker could not collect used to end the
run and send you back to the first dot. On a wide panel that is usually one
corner where your eyes clip out of range. Such a group is now re-shown, up to
three attempts, and only then gives up.

What the review found before you did

Everything above was reviewed adversarially before release: sixteen reviewers
over the code, each of their findings sent to a separate skeptic, the survivors
applied in file-disjoint groups with a test for each that fails when the fix is
undone, and then the fixes themselves checked again. Six things were found that
way rather than by use, all fixed here:

  • The calibration retry above did not actually work for its main case. A
    rejection was read as a level that nothing cleared, so a re-shown group was
    immediately charged again for the very rejection that caused the re-show — a
    single rejection still failed the run. Its own tests passed throughout.
  • tobii headpose reported "one eye 100%" for the rest of a session after the
    first blink. The two-eye frames were never counted, so the ratio only ever had
    reconstructions in its denominator.
  • The recentre could be accepted when nothing was composing frames to perform
    it, leaving the hub saying "measuring — hold still" for ever.
  • The settle window counted a held rotation as a fresh measurement. One
    measurement could fill the window and read as perfect stillness — worst with
    the head-pose model installed, where a held rotation is re-offered for a whole
    second.
  • A calibration whose device hung near a group deadline could reach an
    acknowledgement it could not place, and the flow asserted rather than
    tolerating it: a panic that would have taken the hub down mid-calibration.
  • The hub's opentrack port watch could count one of its own sockets, which would
    have latched the tracker on for good.

Known limitations

  • None of this has been run against a tracker by anyone but its author, and the
    head-tracking changes are unit-tested only.
  • The numbers above are chosen, not fitted: no recording in this repository
    contains a tracked eye, so 150 mm, 300 ms, 8 degrees and the ten-rotation
    floor come from geometry and judgement rather than a measured distribution.
  • The port watch was verified with a test socket binding the port opentrack and
    X-Plane document, not with either program itself.
  • A recentre reference is not shown anywhere after it is taken, so the only way
    to tell it worked is that the view sits straight.
  • Carried over: the tray has only ever been seen on KDE Plasma, and
    sudo ./install.sh --system has not been run end to end under real sudo.

The full list of what is untested is at
https://github.com/Tropaion/Tobii_Linux/blob/main/docs/wiki/Quality-and-Risks.md
— sections 11.3d and 11.3e for this release.

Upgrading

tobii update replaces the two binaries and that is the whole upgrade. Nothing
new to install, no new rule, and your settings and calibration are untouched.

The new game-output settings are off-by-default in the sense that matters:
wake_for_opentrack is on, but it does nothing until game output itself is
enabled and has an opentrack address, so a hub that was not sending to a game
behaves exactly as it did before.

Reporting a problem

tobii debug prints the report an issue asks for, and the hub's cogwheel can
copy or save it. For anything about head tracking into a game, say which route
you use — tobii headpose on its own, or the hub — and whether the head-pose
model is installed.

Credits

The FreeTrack and TrackIR interfaces are public ABIs; opentrack was read for
interface facts and the files that use them say so and say which fact. No
opentrack code was copied. The StatusNotifierItem interface is KDE's published
D-Bus specification. No Tobii code was copied: the ET5's USB protocol was mapped
from this project's own USB captures, cross-checked against the third-party
tobiifree project, with op names and enum orderings read from a decompile of
Tobii's software.

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 11 Sep 22:15

TobiiLinux v0.3.1 — the tracker turns off again, and installs you can undo

A fix release. Five problems were reported against v0.3.0; all five are
addressed here, and each fix below says what was measured and what was not.

No driver, protocol, USB, head-pose or game-output behaviour changed. Those
crates only take the names of the files they write from one shared list now,
which tobii uninstall --purge reads too.

Unofficial. Not affiliated with, endorsed by, or supported by Tobii.
Use at your own risk.

After a calibration, the tracker stayed on

Reported as "the tracker suddenly no longer turns off when the hub is minimised
or not in focus". It did — until you had run a calibration or display setup.
From then on the Eye Tracker 5 stayed claimed for the rest of the session,
whatever the hub did: measured on hardware, the USB device stayed open with the
claim "calibration" after the calibration window had closed.

Two faults, both there since v0.1.0. The claim was released when the window was
destroyed, which GTK 4 does only once the last reference to it is gone, not
when it closes. And both flows held references to their own window, so that
never happened. The claim is now released when the window closes, and the
windows no longer keep themselves alive.

A test clicks the real buttons in the hub and checks that the claim is held
while the flow is open and gone after it closes — including with the window
deliberately kept alive, so it fails if the release ever depends on the window
being freed again.

The update button offers only what can work

Reported: "Update failed: /usr/bin cannot be written to … Install it by hand, or
re-run with the permission to write there." That hub was a v0.1.0 whose package
had been removed while it kept running. Both halves of the advice were dead
ends, and v0.3.0 would have offered the same failing Update to any copy in a
folder you cannot write.

The hub now decides before it shows the button:

  • A copy you can replace gets Update, as before.
  • A copy a package manager owns gets Download of that package, as before — for
    pacman now the prebuilt Arch package (below) when the release has one.
  • A copy in a folder only an administrator can change gets Download of the
    release archive, and the one command that installs it for every user:
    sudo ./install.sh --system <that folder>.
  • A copy in your own folder whose files are root's — what sudo ./install.sh
    into your home directory leaves — gets Download too, with a plain
    ./install.sh <that folder>, no sudo. An update in place cannot replace
    those files: its backup step hard-links the old file, and the kernel refuses
    that for a file you do not own.
  • A folder of yours that you cannot write gets the one command that fixes it,
    chmod u+w <that folder>, and no download. A folder inside your home that
    another account owns — sudo ./install.sh ~/.local/bin can leave one — gets
    sudo chown of that one folder, with your user and group ids. The button
    quits the hub, because the banner is worked out when the app starts and
    closing its window only hides it: run the command, then start the app again.
  • A copy that was replaced or removed while it was running gets Quit. Nothing
    is left at its path to update, and starting the app again would only hand
    over to the same old process. When that only shows up as you click Update,
    the button turns into Quit then.

tobii update --install refuses the same cases with the same advice.
sudo tobii update --install on an install of your own is told to run again
without sudo, instead of being sent through an install by hand.

The Download folder has to be one nobody else can change, because the command
printed at the end is run on those files later, perhaps with sudo: not writable
by other users or by a group you share, and the same for every folder above it.
The folder chooser no longer starts in a folder that would be refused. On the
development machine ~/Downloads is writable by a service's group, so there it
starts in the home folder. The decision, and any failure, is written to the log
that tobii debug quotes.

install.sh no longer installs into /root

sudo ./install.sh printed "Installing into /root/.local/bin" — under sudo your
home directory is root's, so the whole install went where nobody logs in. It
now refuses to run as root and prints the two commands that are meant:
./install.sh for you, or sudo ./install.sh --system for every user
(/usr/local/bin, menu entry and icon in /usr/local/share).
scripts/build.sh --install --system does the same from source, and
build.sh refuses sudo too.

Also new in the installer:

  • It records where it installed, for tobii uninstall.
  • A start-at-login entry that still runs a program which no longer exists is
    repaired to the copy just installed. One that runs another copy that does
    exist is left alone and named — which copy starts at login is your choice.
  • A hub still running from a replaced copy is named with its pid, because the
    hub is single-instance: every launch goes to that old process until it quits.
  • The menu entry names the program by its absolute path, quoted, so a folder
    with a space or a % in its name works. A folder containing ", `, $
    or \ gets no menu entry, and the installer says so.
  • --system installs only where only root can write. A folder inside a home
    directory, one another account owns, or one a group or everyone can write is
    refused, and the refusal names the folder. --system also sets its own
    umask, so the menu entry it writes for every user is readable by every user.
  • A menu entry or icon that cannot be written no longer stops the install once
    the binaries are in place. A folder it cannot create at all still does.
  • A mistyped option is refused. A mistyped --no-udev used to run the one step
    it was meant to skip. scripts/build.sh --install <folder> now reads a
    relative folder from where you ran it.

Uninstalling

tobii uninstall --dry-run     # prints what it would do; changes nothing
tobii uninstall               # asks, then does it

It finds an install through the installer's record and, for installs made
before this release, through the menu entry, the start-at-login entry and
~/.local/bin. It stops running copies first, through the hub's own Quit and
only with your consent beyond that, and removes known file names only.
--purge also removes settings and calibration, --udev the device rule, and
sudo tobii uninstall --system a --system install. If tobii is gone
already, the release archive's ./uninstall.sh runs its own copy.

What it will not touch, and says so: a copy your package manager owns (it prints
the manager's own command), a copy another user owns, anything in a folder
someone else can write, a Cargo build directory, an unpacked release archive,
and copies cargo install put there. Files you made yourself in the settings
folder — a calibration backup, say — are listed, never deleted. A menu or
start-at-login entry whose Exec line launchers could read in two ways is kept,
and is not used to find anything. As root, a folder the install record lists is
checked like any other: one that someone other than root could change is left
for its owner to remove.

A prebuilt package for Arch

On Arch, CachyOS, Manjaro and EndeavourOS there is now nothing to compile:

curl -fLO https://github.com/Tropaion/Tobii_Linux/releases/download/v0.3.1/tobii-linux-bin-0.3.1-1-x86_64.pkg.tar.zst
curl -fLO https://github.com/Tropaion/Tobii_Linux/releases/download/v0.3.1/SHA256SUMS
sha256sum -c --ignore-missing SHA256SUMS
sudo pacman -U ./tobii-linux-bin-0.3.1-1-x86_64.pkg.tar.zst

Download it first: pacman -U with the URL refuses an unsigned package on
Arch's stock settings, while a file on disk is accepted. The sha256sum line
proves the file arrived intact; it says nothing about who built it.

The package replaces the source-built tobii-linux; answer yes when pacman
offers to remove it. Its dependencies are read from the binaries themselves.
The source PKGBUILD is still published, under the same name, because a v0.3.0
hub's Download looks for it.

The tray icon

After a first install the tray could show a generic placeholder: Plasma looks for
icons only in folders that existed when it started. The hub now hands the tray
its own copy of the icon. Plasma reads it, but whether that fixes the
first-install case has not been confirmed. What is confirmed: after
systemctl --user restart plasma-plasmashell.service, or your next login, the
real icon shows — in the tray and in the application menu.

Also in this release

  • Switching Start at login on from a copy that had been updated while it ran
    wrote a path ending in "(deleted)": an entry that showed as on and started
    nothing. It now writes the path the new copy is at, and refuses, saying why,
    when the copy was removed rather than replaced.

  • The hub has a quit action on the session bus, which runs its normal
    shutdown. tobii uninstall and the Quit banner use it, and so can you:

    gdbus call --session --dest com.tobiilinux.Configuration \
      --object-path /com/tobiilinux/Configuration \
      --method org.freedesktop.Application.ActivateAction quit '[]' '{}'
    
  • A quit from outside while a calibration or display setup is open closes that
    window first, so it gives the tracker back like any other close. What's new
    can be opened again after an update attempt.

  • The installer no longer creates an icon cache where there was none. GTK
    trusts a cache over the folder, so one created in ~/.local/share/icons
    hid every icon another program added there later.

  • A relative XDG_CONFIG_HOME, XDG_DATA_HOME or `XDG_STATE_...

Read more

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 03:09

TobiiLinux v0.3.0 — the hub, rebuilt

This is a user-interface release. The hub is shorter, it goes to the status area
instead of the taskbar, its text size is adjustable, and a copy installed by your
package manager can now fetch its own update instead of just being told it can't
have one.

Nothing below the GUI changed. No driver, protocol, USB, head-pose or
game-output code is touched in this release — everything v0.2.0 said about head
tracking into games still stands exactly as it did, untouched and equally
untested by a real game.

Unofficial. Not affiliated with, endorsed by, or supported by Tobii.
Use at your own risk.

The window is 374 pixels shorter

Measured on the same machine, same screen, opening at the default text size:

v0.2.0 1040 x 1087
v0.3.0 1189 x 713

The live data — trackbox, readout, sensor view — is now one card spanning the
top, with six setting cards in three columns beneath it. Before, one tall column
set the height of the whole window and the other ran out halfway down.

The layout still has three column counts and drops to two, then one, as the
window narrows. Be aware that on an ordinary floating desktop you probably
cannot reach those: the window's minimum width and the three-column threshold
come from the same measurement, so the narrower layouts need a tiling compositor
or a screen smaller than the hub.

Pressing X now goes to the status area — where your desktop has one

v0.2.0's notes said "closing the hub window minimises it instead of quitting".
As of this release that is only half true, and which half you get depends on
your desktop.

If something on your session bus owns org.kde.StatusNotifierWatcher — KDE
Plasma, and the bars and panels that implement the same interface — the window
is hidden, and a tray icon is your way back. If nothing does, which includes
stock GNOME, the window is minimised exactly as before and Alt-Tab or the
overview is the way back.

Either way, launching the app again raises the hub you already have, and Quit in
the cogwheel still exits for real.

The tray icon deliberately has no menu. Left-, right- and middle-click all just
raise the hub; there is no right-click Quit. If you find that surprising, so
will the next person — it is listed under Known limitations rather than hidden.

Fixed along the way: in v0.2.0, Quit did not actually quit a hub that had been
started at login. --background leaked a GApplication hold and nothing ever
called app.quit(), so the window closed and the process stayed running with no
window and no way back to it.

Text size, 80% to 160%

In the cogwheel, beside the other settings that are about the program rather
than the tracker. Minus, the current percentage, plus. It applies instantly and
is remembered in ~/.config/tobii-linux/text_scale.

It scales the text in every window this program draws, including the fullscreen
setup and calibration flows. The tracker, the games and the gaze overlay are
unaffected.

Two honest caveats. At 160% the hub's own minimum size reaches 1395 x 857, which
is bigger than a 1366 x 768 laptop screen can show — and the control that undoes
it sits in a popover attached to that oversized window. If you get stuck there,
delete ~/.config/tobii-linux/text_scale and restart the hub. And only text
follows the setting: every fixed pixel dimension stays put, so making the text
smaller does not make the window fit a screen it did not fit before.

Download, for copies a package manager owns

If dpkg, rpm or pacman installed your copy, the update banner has never been
able to do anything for you — replacing a packaged file behind the package
manager's back is how a package database comes to describe files that are no
longer there, so it refused, and that was the end of it.

Now the button says Download. It asks where to put the file, fetches the
artifact that matches your package manager — the .deb, the .rpm, or the PKGBUILD
and its install hook together — into a version-named folder there, checks it
against SHA256SUMS, and prints the one command that installs it.

It does not install anything. That is still your package manager's job, and the
command to hand it is on screen.

This is the hub only. tobii update on the command line still refuses and tells
you why.

Read this part: nobody has ever clicked this button against a real release. It
cannot be done from a development build — the path is reachable only when a
package manager owns the running binary AND a newer release exists. The code is
unit-tested and its asset matching was checked against the real v0.2.0 release
assets, but v0.3.0 is the release that first makes the button reachable, and its
first users are its first test. Same standing as "no game has consumed any of
it" was in v0.2.0.

Also in this release

  • The window follows its own content. Raising the text size or a banner
    appearing now grows the window instead of clipping the last card off the
    bottom. It stops doing this once you resize the window by hand, which is
    deliberate and is also listed under Known limitations.
  • The sensor view no longer changes size when the tracker connects. The
    disconnected placeholder is drawn in the same letterboxed, rounded rectangle a
    real frame occupies, so there is no jump.
  • The layout breakpoints are measured from the widgets rather than computed.
    The old arithmetic assumed dropping a column frees its width plus a gutter; it
    frees 63 pixels less than that, so at the default text size the hub switched
    to two columns 63 pixels before two columns fit.
  • tobii debug now reports the text scale, because "the window opens too big"
    and "the last card is cut off" are not answerable without it.
  • tobii-gtk --accuracy no longer puts a second, identical icon in the status
    area. It runs as a separate process, so it was publishing its own.
  • Closing the hub to the tray now stops its 30 Hz tick instead of running it to
    reach a guard. The CPU saved is below what /proc can measure; what it actually
    removes is roughly 324,000 timer wakeups over a three-hour session.

Upgrading

tobii update replaces binaries and nothing else — and this time that is
everything there is. No new udev rule, no new asset, nothing to install by hand.
If you are coming from v0.1.0 rather than v0.2.0, read v0.2.0's "Upgrading" note
about the udev rule first, because that one does still apply.

The one exception: a copy run straight out of a build tree has no icon installed,
so its tray icon is whatever placeholder your theme uses for an unknown
application. scripts/build.sh --install, or any of the packages, installs the
real one.

Known limitations

  • The tray has only ever been seen on KDE Plasma. waybar, xfce4-panel, LXQt and
    GNOME's AppIndicator extension implement the same interface and are believed
    to work; none has been tried.
  • The hub decides to hide itself based on whether anything owns the watcher bus
    name, not on whether its icon was accepted. A host that refuses the item, or a
    panel set to hide unknown icons, leaves the window hidden with no icon
    visible. Launching the app again brings it back.
  • No tray menu: right- and middle-click raise the hub, they do not offer Quit.
  • On stock GNOME there is no status area and no taskbar, so "minimised" means
    the overview and Alt-Tab.
  • Only the pacman branch of the package-ownership query has run against a real
    package database. The dpkg and rpm branches are unit-tested only — and they
    are what decides whether a Debian or Fedora user is offered Update or
    Download.
  • Re-downloading the same version into the same folder and failing part-way
    deletes the copy that was already there.
  • Changing the text size mid-session does not re-measure the column
    breakpoints; only the window height is re-fitted.
  • The fullscreen setup and calibration flows follow the text-size setting and
    have not been looked at at either end of its range.
  • The PKGBUILD the Download button hands pacman users builds from GitHub's
    generated source tarball, which pins no digest (sha256sums=('SKIP')). The
    PKGBUILD itself is checksum-verified before it is written; what it downloads
    afterwards is not. Pre-existing, but v0.3.0 is the first release that steers
    anyone to that file from inside the program.

How this was tested

  • The two window sizes above were measured by building v0.2.0 in a worktree and
    opening both hubs on the same machine and screen, not estimated from the
    layout.
  • The tray was verified against Plasma's live kded6 watcher: after the hub
    starts, its item appears in the watcher's RegisteredStatusNotifierItems, and
    reading the ToolTip property back over D-Bus returns the right string.
  • --accuracy was checked to publish no item, and the ordinary hub exactly one.
  • The three layout thresholds were measured at four text scales; at every one,
    the width at which the layout switches and the width that layout needs are the
    same number.
  • minimize() was measured to leave a GTK4 window mapped, which is why the tick
    is keyed on map/unmap and a minimised hub still ticks.
  • 821 tests pass, 8 ignored, none failing. Clippy clean at -D warnings and
    rustfmt clean across the main workspace, which is what CI gates; both
    workspaces build --locked. The bridge workspace is not linted by CI and has
    7 clippy warnings of its own, unchanged by this release.

Three adversarial passes were run over this work and every finding from all
three was adjudicated: 32 review findings (27 confirmed and fixed, 5 refuted),
26 cleanup findings across four lenses (15 applied, 3 skipped with reasons
recorded in the commit), and 85 release-readiness findings of which 76 survived
refutation. Several of the fixes above — the two-column breakpoint, Quit in
backgroun...

Read more

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 16:08

TobiiLinux v0.2.0 — head tracking into games

Games. v0.1.0 could send head pose to opentrack, and only to opentrack. This
release adds two routes that need nothing else installed, and fixes a bug that
made one axis useless in every output the program has.

This is a testing release. The tracking paths below are verified end to
end on hardware — through SDL, through joydev, through DirectInput under a
real Proton prefix — but no game has consumed any of it yet, and no real
head has driven the axes. If something feels wrong, it probably is; see
Reporting a problem.

Unofficial. Not affiliated with, endorsed by, or supported by Tobii.
Use at your own risk.

New: a virtual joystick, needing nothing else

Most games have never heard of head tracking, but nearly all of them can bind a
joystick axis. So game output now presents one, on /dev/uinput:

Axis Carries Full scale
X, Y, Z head displacement from where you sit ±500 mm
RX, RY, RZ yaw, pitch, roll ±180°, ±90°, ±180°
Throttle, Rudder gaze on screen, left→right and top→bottom the whole screen

No opentrack, no Wine. Because it is an ordinary evdev joystick it is read by
SDL, by the legacy /dev/input/js* interface, and by Wine's winebus — so
Proton games see it as a DirectInput joystick too. The last two axes are
gaze, which no other head tracker offers.

Turn it on with the hub's Head tracking for games switch, or:

tobii games set enabled true
tobii game -- %command%        # Steam: paste into Launch Options

If the axes feel weak, this is the knob. The number is the head angle that
reaches full deflection:

tobii games set joystick_yaw_full_deg 45     # default 70

New: the Wine/Proton bridge needs nothing running

tobii bridge games                     # what is installed, and what has a prefix
tobii bridge install --steam elite     # by name, or by app id

The client DLL the game loads receives the tracking itself, in a background
thread inside the game's own process. There is no second program to leave
running. That is not a convenience: a Proton game runs under its own wineserver,
so an external tobii bridge run created its shared memory in a different
session and the game never saw it — for a Steam title the old design could not
have worked at all.

install --steam finds the prefix from Steam's own library files and writes the
registry with the Proton build that prefix records, not whatever wine is
on your $PATH — a foreign wine would run wineboot -u and upgrade the prefix
out from under the game.

Fixed: one translation axis was dead in every output

Z carried the head's distance from the sensor — 650–700 mm for anyone
sitting normally — against a ±500 mm scale. It was pinned hard at maximum, for
every user, in both the joystick and the TrackIR/FreeTrack output that
shipped in v0.1.0. In a game that reads as a camera permanently leaned all the
way in. Translation is displacement from where you sit now, taken when the
tracker picks you up.

Also in this release

  • X-Plane 11/12 works with no extra sink. The plugin Linux users actually
    run (amyinorbit/headtrack) reads exactly what our opentrack output already
    emits, on the port it already uses. Just turn game output on.
  • tobii game -- <command> holds the tracker for a game's lifetime, and says so
    when game output is switched off rather than lighting the tracker silently.
  • Closing the hub window minimises it instead of quitting, so the tracker
    stays available to a running game.
  • Another program can take the device from the hub and hand it back.
  • tobii games [set KEY VALUE] for every game-output setting.
  • tobii debug reports /dev/uinput, separating the three ways it fails —
    they are indistinguishable from inside a game.
  • The packaged udev rule now also grants /dev/uinput, as one clearly-marked
    line the file explains how to remove.

Upgrading — read this one

tobii update replaces binaries and nothing else, so it will not deliver
the new udev rule. If the virtual joystick reports a permission error, install
the rule by hand:

sudo cp assets/60-tobii.rules /etc/udev/rules.d/
sudo udevadm control --reload
sudo udevadm trigger --subsystem-match=usb --subsystem-match=misc

then log out and back in — the grant is a logind ACL applied at session start.
tobii debug says installed without a warning once it has taken. Distribution
packages (deb, rpm, PKGBUILD) do this for you.

If you set bridge_port to something other than the default, launch through
tobii game: it passes the port to the Wine side, and nothing else can.

Known limitations

  • Some games' "look" axis is a rate control, not a position: the camera
    keeps turning while the axis is deflected. The bind takes, the axis moves in
    the test display, and the view still spins away from you. Elite Dangerous has
    an explicit Headlook Axis Mode: Direct precisely because its default is
    incremental. Where only a rate axis is offered, the joystick cannot drive the
    view however it is tuned.
  • Steam Input can take the device away, silently, from a Steam-launched
    game. Fix per game: Properties → Controller → Disable Steam Input.
  • The bridge is 64-bit only. A 32-bit game asks for freetrackclient.dll
    without the 64 and finds nothing while install reports success. Falcon
    BMS, IL-2 1946 and the FSX generation are 32-bit.
  • TrackIR still needs a signed client DLL. NP_GetSignature is answered
    from NaturalPoint's own signature data, which we do not ship, so
    tobii bridge install points TrackIR at an installed opentrack client — and
    that one does need tobii bridge run. FreeTrack has no signature check and
    works with our own DLL.
  • XInput does not see the virtual joystick — measured under Wine 11.17.
    Its two-stick layout has nowhere to put eight axes.
  • Flatpak Steam is half-supported. The install finds its prefix, but the
    launch option runs inside the sandbox where our binary is not on PATH.
    flatpak-spawn --host tobii game -- %command% is the shape to try.
  • [UNKNOWN] the per-axis signs of the TrackIR encoding, and whether a game
    ignores a frame whose wPFrameSignature did not change.
  • The gaze axes sit on Throttle and Rudder. A flight sim that auto-binds a
    throttle could pick one up; no sim has been observed doing so.

How this was tested

Everything below was measured on hardware, not inferred:

  • SDL2 2.32 enumerates the device with 8 axes and 2 buttons. A known pose
    survives the whole chain exactly: composed yaw +35° of the 70° default arrives
    as 16384 — half deflection — pitch −17.5° as −16385, gaze at the
    right-hand screen edge as 32767.
  • joydev (/dev/input/js*) reports every axis at exactly 0 at rest.
  • Wine 11.17 DirectInput8 enumerates it as DI8DEVTYPE_JOYSTICK; XInput
    reports zero controllers in all four slots, with and without it present.
  • udev classification was established by building the device four ways and
    reading /run/udev/data/. Without an EV_KEY capability the kernel calls it
    an accelerometer and hands it to iio-sensor-proxy — the service that rotates
    laptop screens.
  • The bridge, on a real Elite Dangerous Proton prefix, with nothing else
    running.
    The game's own load path (LoadLibrary on the registry directory,
    then GetProcAddress) resolves all five FreeTrack exports, and a pose sent
    from Linux as yaw 7.5°, pitch −3.25°, roll 1.5°, (11, 22, 33) mm reads back
    through FTGetData as yaw=0.1309, pitch=-0.0567, roll=0.0262 radians and
    pos=(11.0, 22.0, 33.0), with DataID advancing. With nothing sending it
    reports no data rather than a tracker frozen at centre.

793 tests, zero clippy warnings, both workspaces building.

Two full adversarial review rounds were run over this change and every finding
from both was adjudicated
— 55 findings, 22 confirmed and fixed. Several were
defects in behaviour that shipped in v0.1.0.

The full list, including what is untested and what is merely believed to work,
is in Quality-and-Risks — see 11.3a Game
output
. That page is written to be uncomfortable to read.

Reporting a problem

tobii debug prints the report an issue asks for, and the hub's cogwheel can
copy or save it. It names the state of the udev rule, /dev/uinput, the display
area and the calibration. If a game sees nothing, say which game, which route
(joystick, opentrack, or the bridge), and paste that report.

Credits

The FreeTrack and TrackIR interfaces are public ABIs; opentrack was read for
interface facts — which axes, which classification bits, which packet shapes —
and the files that use them say so and say which fact. No opentrack code was
copied. The clean-room claim covers the ET5's USB protocol, which was
derived from captured traffic and nothing else.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 20:13

The first release. A native Linux runtime and GTK4 GUI for the Tobii Eye
Tracker 5
, written in Rust, with no Tobii software installed — the USB
protocol was reverse-engineered clean-room.

Unofficial. Not affiliated with, endorsed by, or supported by Tobii.
Use at your own risk.

What works

Eye tracking

  • Gaze streaming over USB: 2D gaze point, per-eye validity, eye position in the
    trackbox, eye origins in millimetres, pupil size.
  • Follow-the-dot calibration — 7 points in three groups, computed and applied on
    the device after each group, then persisted and re-applied on every connect.
    Measured accuracy on a 49" 32:9 panel: 12.1 mm mean error within the
    device's usable ±28° — about 0.92° at a 750 mm viewing distance.
  • Guided display setup, seeded from your monitor's EDID. On a curved panel the
    device is told the EDID arc width (it only accepts a flat plane) and the
    per-user calibration absorbs the curve.
  • Select which eyes to detect: both, left only, or right only.

Head tracking

  • 5 DOF with no download: position, yaw and roll from the two eye origins.
  • 6 DOF with an optional model: adds pitch, which two eye origins cannot
    express. Read from the tracker's infrared camera at ~12.4 ms a frame. The
    model is not shipped — its weights are non-commercial-only, so the program
    shows the terms and fetches it only if you agree.
  • Output to games over opentrack's UDP protocol.

The app

  • A GTK4 hub with a live instrument panel: the trackbox with a graticule, your
    eyes as dots, the head drawn around them turning as you turn, a readout of
    position, distance, yaw, pitch and roll, and the infrared sensor view.
  • Preview my gaze — a translucent click-through dot following your gaze.
  • An accuracy diagnostic: a 39-target sweep reporting error per target, per
    angle band and per eye.
  • A desktop entry, and optional start-at-login.
  • A cogwheel in the header for the settings that are not about tracking —
    start at login, check for updates, and save or copy the diagnostics report —
    and tobii debug on the command line, so a bug report is one paste.

The tracker is only on when something needs it. Its infrared illuminators
are lit for as long as a USB session is open, so the session exists only while
the hub has focus, the gaze overlay is shown, or a flow is running — and closes
three seconds after the last of those lets go. A consequence worth knowing:
while the hub is unfocused it holds no session, so tobii headpose can claim
the device for a game without closing the hub.

Installing

Every release ships an archive and three packages. Which you want depends on
one question: do you want the built-in updater, or do you want your system to
own this?

Install Updates via
tobii-linux-<version>-<triple>.tar.gz tar -xzf it, then ./install.sh the built-in updater
tobii-linux_<version>_amd64.deb sudo apt install ./tobii-linux_*.deb apt
tobii-linux-<version>-1.<arch>.rpm sudo dnf install ./tobii-linux-*.rpm dnf
PKGBUILD + tobii-linux.install both into one directory, then makepkg -si pacman

All four install the same three things: the two binaries, the application-menu
entry, and the udev rule that makes the tracker usable without root. All four
also delete /etc/udev/rules.d/99-tobii.rules if you have one from before this
release — it says MODE="0666" and, because 99 sorts after 60, it would
override the new rule and leave the tracker world-readable. The
archive's install.sh puts the binaries in ~/.local/bin (pass a directory for
somewhere else) and asks before using sudo for the rule; the packages install
into /usr/bin and place the rule as part of the install.

The built-in updater deliberately refuses to touch a package-managed install
— it asks dpkg, rpm and pacman whether they own the binary, and if one does it
tells you to update with that instead. Overwriting a package-managed file would
leave the package database describing a file that is no longer there, and the
next upgrade would revert the update anyway.

So: to try the built-in updater, install the tar.gz.

Or from source, which works everywhere and builds against your own GTK:

git clone https://github.com/Tropaion/Tobii_Linux.git && cd Tobii_Linux
scripts/build.sh --install --udev

scripts/build.sh checks its dependencies first and names anything missing with
the command to install it for your distribution. Per-distro instructions are in
the README. Re-plug the tracker afterwards so
the udev rule takes effect.

Prebuilt binaries and packages in this release are built in a Debian 13
container, so they need glibc 2.41 or newer — and the .deb and .rpm
declare that, so your package manager refuses rather than installing something
that cannot start. On an older distribution, build from source; the PKGBUILD
does this by design.

Updating

The hub checks for a new release when it opens and can show the changelog and
install it. The check can be switched off, in Settings or with
TOBII_NO_UPDATE_CHECK=1.

Please read this once. The checksums published with a release are fetched
from that same release over the same connection, so they prove the download
arrived intact and nothing about who produced it. There is no signature.
Installing an update trusts this project's GitHub releases exactly as much as
downloading a binary from the releases page and running it by hand. That is a
normal amount of trust for a program you already run, but it is not the
guarantee a checksum is often assumed to give.

What the updater does protect against: a corrupted or truncated download; an
archive whose members are symlinks; and — the one that matters most — a release
built against newer system libraries than your machine has. The new binaries are
run once before they are installed, so that failure is refused while the
working ones are still in place, and any failure rolls every binary back.

Reporting a problem

tobii debug prints everything an issue needs in one paste — versions, the
distro and kernel, library versions, whether the tracker is on the bus, what is
configured, and the tail of the log. The hub does the same from the cogwheel
in its header: a save button that asks where to put it, and a copy button.

It is meant to be read before you send it: no calibration data, your monitor id
hashed, and no username, home path or hostname. The hash is salted per
install
— an unsalted one is not a redaction but an encoding, and the
maintainer's own monitor id was recovered from its digest in 0.07 seconds on one
core. Two reports from the same machine still carry the same id, which is the
only thing that hash was ever for.

Issues use a form that asks for it. If you would rather not paste diagnostics,
Discussions is linked from the same page and asks for nothing.

What the pre-release review changed

This release was reviewed in five rounds, and the last one found things worth
naming here because they are the kind a user is entitled to know were wrong:

  • The monitor hash was reversible. A brute force over 100,000 candidates
    recovered the maintainer's own id from its 8-character digest in 0.07s. It is
    salted per install now.
  • The report leaked the home path whenever $HOME was not the install
    prefix — sudo tobii debug printed it in full, under a footer saying it was
    redacted. It folds the invoking user's home, and anything shaped like a home
    directory, and truncates whatever is left.
  • The udev rule never worked as designed. It was named 99-tobii.rules, and
    systemd's 73-seat-late.rules is what turns TAG+="uaccess" into an ACL — so
    the tag was set twenty-six files too late and MODE="0666" was doing all the
    work, handing every local process access to an infrared camera. It is
    60-tobii.rules with mode 0660 now, and the installer removes a leftover copy
    of the old one, which would otherwise still win on mode.
  • The updater read a failed ownership query as "nobody owns this file", and
    the query had no timeout. Both are fixed; an unanswerable query now refuses.
  • Neither package declared the glibc floor the notes promise.
  • The archive had no installer and no udev rule, and these notes told you to
    unpack it into ~/.local/bin, which produces a directory rather than binaries.

Known limitations

  • The head-pose model's focal length is assumed (DEFAULT_FOCAL_PX = 355),
    which feeds a perspective correction worth 12–16° of pitch. The real value can
    be computed from data already on the wire; it is not yet wired up.
  • This release path has never run before. The repository had no tags until
    now, so this is the first execution of the release workflow and the first
    time the updater's download-and-install path meets a real release. The .deb
    has been unpacked and read field by field and the PKGBUILD parses under real
    makepkg, but nobody has installed any of these packages on a clean machine
    of the distribution it targets
    , and the .rpm is built for the first time by
    this release — most developer machines have no rpmbuild, so its spec was
    written where it could not be read back. CI now opens both finished packages
    and fails if they do not declare the glibc floor, which is the only check that
    runs where they are actually built.
  • The head-pose model's yaw and roll signs are confirmed on hardware; pitch is
    not comparable to anything.
    Pitch has no geometric counterpart to check
    against, only a zero you measure once with
    tobii headpose --calibrate-pitch.
  • Glyph clipping at fractional display scale. Tops of tall glyphs can appear
    shaved. CSS, the GSK...
Read more