Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,21 @@ XDG Autostart entry for the GUI. Login start hides the initial window only when
Hide to tray is selected and a tray host is actually available; otherwise the
window is shown. This switch never changes the background runtime service.

## What's new in 2.5.3

- Panorama `391a:1011`, Panorama SE `391a:1021` and Panorama WB `391a:1031`
no longer drop off USB on systems where they used to disappear until the
power supply was switched off. The runtime keeps a bulk IN transfer pending
between requests, like the official bridge, and re-arms it as soon as it
ends. A reporter who had the drops ran the display for hours without a
disconnect (#28).
- The runtime creates its data and cache directories owner-only. With the
0002 umask common on Ubuntu and Linux Mint they used to become
group-writable; after the first media upload the runtime refused them and
the display session no longer started. Directories created by earlier
versions are not changed automatically: the status and the log name the
directory and the `chmod go-w` command that fixes it (#32).

## What's new in 2.5.2

- Panorama WB (`391a:1031`) is supported with the same feature set as the
Expand Down Expand Up @@ -548,7 +563,7 @@ runtime remote, and a working USB portal backend are required. Stop any other
TRYX runtime before starting this build.

```fish
flatpak install --user ./tryx-panorama-manager-2.5.2-experimental-x86_64.flatpak
flatpak install --user ./tryx-panorama-manager-2.5.3-experimental-x86_64.flatpak
flatpak run io.github.dxvsi.tryx_panorama_manager//experimental
```

Expand Down Expand Up @@ -580,13 +595,13 @@ Install a downloaded package with the package manager for your distribution:
# Fedora. Enable RPM Fusion Free first because media conversion requires the
# full ffmpeg package with the libx264 encoder.
set tryx_fedora_release (rpm -E %fedora)
sudo dnf install --allowerasing ./tryx-panorama-manager-2.5.2-1.fc$tryx_fedora_release.x86_64.rpm
sudo dnf install --allowerasing ./tryx-panorama-manager-2.5.3-1.fc$tryx_fedora_release.x86_64.rpm

# Ubuntu 24.04 or Linux Mint 22
sudo apt install ./tryx-panorama-manager_2.5.2-1_amd64.deb
sudo apt install ./tryx-panorama-manager_2.5.3-1_amd64.deb

# Arch Linux
sudo pacman -U ./tryx-panorama-manager-2.5.2-1-x86_64.pkg.tar.zst
sudo pacman -U ./tryx-panorama-manager-2.5.3-1-x86_64.pkg.tar.zst
```

These commands use the distribution package manager to resolve and download
Expand Down Expand Up @@ -696,6 +711,44 @@ sudo dnf install -y android-tools unzip e2fsprogs ffmpeg mesa-demos
- Supported printer-class devices use `391a:1011` for Panorama, `391a:1021` for Panorama SE / PASE, `391a:1031` for Panorama WB, and `391a:2011` for Turris 620; direct libusb access uses `/dev/bus/usb/*/*` and requires the `lp` group or a seat ACL from `TAG+="uaccess"`
- Fedora's generic printer rule must not start CUPS `configure-printer` for this vendor protocol. The qmake install target places an early access rule and a late printer-suppression rule in `/usr/lib/udev/rules.d`; do not create same-named overrides in `/etc/udev/rules.d`, because they would shadow packaged updates.

### Display drops off USB and only returns after a power-off

Some Panorama `391a:1011`, Panorama SE `391a:1021` and Panorama WB
`391a:1031` displays disappear from the bus during a session and come back
only after the power supply is switched off. Reports come from AMD 800-series
chipset USB controllers on Linux and Windows. The firmware fails when the host
stops polling the device between exchanges; the official bridge keeps a bulk
IN pending at all times and survives for days. Since 2.5.3 the runtime does
the same for the Panorama family.

If a display still drops, a kernel parameter that disables USB link power
management for these devices is a known workaround:

```text
usbcore.quirks=391a:1011:k,391a:1021:k,391a:1031:k
```

Add it to the kernel command line of your boot loader, reboot, and switch the
power supply off once so the display recovers. Details and measurements are in
issue #28.

### The display session never starts after the first media upload

Before 2.5.3 the runtime created its directories with permissions from the
umask. With a 0002 umask they became group-writable, and the runtime then
refused them, so the display stayed on "Waiting for Device". Since 2.5.3 the
status and the service log name the affected directory with the command that
fixes it, for example:

```text
chmod go-w '/home/user/.cache/DXVSI/TRYX Panorama Runtime/prepared-media'
```

Run the command for each directory the log names, without `-R`, then restart
the service with `systemctl --user restart tryx-panorama.service`. Do not
delete the directories: they can hold an interrupted upload that the runtime
still has to finish.

## Firmware Updates

Firmware updates are initiated from the firmware panel in Quick Settings, but
Expand Down Expand Up @@ -727,7 +780,7 @@ verification. A new flash cannot be started from an unidentified Loader-only
device; the runtime must first identify a firmware-capable TRYX product before
authorizing the transition into Loader mode.

After updating to the new KANALI firmware, the cooler no longer exposes ADB by default. It appears as `391a:1021 RK PASE` with a bidirectional printer interface. The app generates C++ types from three minimal, project-owned schemas under `protocol/wire-v1`; recovered vendor descriptor sources are not a build or release dependency. The production path does not read or write `/dev/usb/lp*`: it claims the `07/01/02` interface through usbfs, temporarily detaches `usblp`, arms one bulk IN before each request, never re-arms that endpoint while the matching bulk OUT is still active, drains optional periodic responses to a complete frame boundary after OUT, and releases the interface on shutdown.
After updating to the new KANALI firmware, the cooler no longer exposes ADB by default. It appears as `391a:1021 RK PASE` with a bidirectional printer interface. The app generates C++ types from three minimal, project-owned schemas under `protocol/wire-v1`; recovered vendor descriptor sources are not a build or release dependency. The production path does not read or write `/dev/usb/lp*`: it claims the `07/01/02` interface through usbfs, temporarily detaches `usblp`, arms one bulk IN before each request and, on the Panorama family, leaves one bulk IN pending between requests as the official bridge does (an idle link made these displays drop off the bus until power was removed, see issue #28), never re-arms that endpoint while the matching bulk OUT is still active, drains optional periodic responses to a complete frame boundary after OUT, and releases the interface on shutdown.

Turris 620 exposes the supported `391a:2011` printer-class identity and runs on
the same configuration pipeline as PASE with a Turris product profile. Media is
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.5.2
2.5.3
8 changes: 8 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
tryx-panorama-manager (2.5.3-1) noble; urgency=medium

* Keep a bulk IN pending between requests on the Panorama, Panorama SE and Panorama WB and re-arm it as soon as it ends, as the official bridge does, so the display no longer drops off USB until a power-off.
* Create the runtime's data and cache directories owner-only regardless of the umask, so a 0002 umask no longer stops the display session from starting after the first media upload.
* When stored retry media cannot be used, report that the display session cannot start and name the directory with the command that fixes it, instead of a validation that never finishes.

-- DXVSI <dxvsi123@gmail.com> Wed, 07 Oct 2026 10:00:00 +0000

tryx-panorama-manager (2.5.2-1) noble; urgency=medium

* Add Panorama WB (391a:1031) support with the Panorama feature set, confirmed on real hardware.
Expand Down
2 changes: 1 addition & 1 deletion packaging/arch/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# PKGBUILD metadata and directory variables are consumed or provided by makepkg.
# shellcheck disable=SC2034,SC2154
pkgname=tryx-panorama-manager
pkgver=2.5.2
pkgver=2.5.3
pkgrel=1
pkgdesc='Control compatible TRYX cooler displays on Linux'
arch=('x86_64')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
<url type="bugtracker">https://github.com/DXVSI/Tryx-Linux-GUI/issues</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="2.5.3" type="stable" date="2026-10-07">
<description>
<ul>
<li>Keep a bulk IN pending between requests on the Panorama, Panorama SE and Panorama WB and re-arm it as soon as it ends, as the official bridge does, so the display no longer drops off USB until a power-off.</li>
<li>Create the runtime's data and cache directories owner-only regardless of the umask, so a 0002 umask no longer stops the display session from starting after the first media upload.</li>
<li>When stored retry media cannot be used, report that the display session cannot start and name the directory with the command that fixes it, instead of a validation that never finishes.</li>
</ul>
</description>
</release>
<release version="2.5.2" type="stable" date="2026-09-29">
<description>
<ul>
Expand Down
7 changes: 6 additions & 1 deletion packaging/rpm/tryx-panorama-manager.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Name: tryx-panorama-manager
Version: 2.5.2
Version: 2.5.3
Release: 1%{?dist}
Summary: Linux manager for supported TRYX cooler displays

Expand Down Expand Up @@ -120,6 +120,11 @@ udevadm verify --resolve-names=never \
%{_mandir}/man1/tryx.1*

%changelog
* Wed Oct 07 2026 DXVSI <DXVSI@users.noreply.github.com> - 2.5.3-1
- Keep a bulk IN pending between requests on the Panorama, Panorama SE and Panorama WB and re-arm it as soon as it ends, as the official bridge does, so the display no longer drops off USB until a power-off
- Create the runtime's data and cache directories owner-only regardless of the umask, so a 0002 umask no longer stops the display session from starting after the first media upload
- When stored retry media cannot be used, report that the display session cannot start and name the directory with the command that fixes it, instead of a validation that never finishes

* Tue Sep 29 2026 DXVSI <DXVSI@users.noreply.github.com> - 2.5.2-1
- Add Panorama WB (391a:1031) support with the Panorama feature set, confirmed on real hardware
- Show GPU names with their vendor on the hardware badge, for example NVIDIA GeForce RTX 4090 on the NVIDIA colours, also inside the Flatpak
Expand Down
2 changes: 1 addition & 1 deletion packaging/tryx-panorama-manager.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH TRYX-PANORAMA-MANAGER 1 "2026-09-29" "TRYX Panorama Manager 2.5.2" "User Commands"
.TH TRYX-PANORAMA-MANAGER 1 "2026-10-07" "TRYX Panorama Manager 2.5.3" "User Commands"
.SH NAME
tryx-panorama-manager \- control compatible TRYX cooler displays
.SH SYNOPSIS
Expand Down
2 changes: 1 addition & 1 deletion packaging/tryx.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH TRYX 1 "2026-09-29" "TRYX Panorama Manager 2.5.2" "User Commands"
.TH TRYX 1 "2026-10-07" "TRYX Panorama Manager 2.5.3" "User Commands"
.SH NAME
tryx \- inspect and manage the local TRYX runtime from a terminal
.SH SYNOPSIS
Expand Down
3 changes: 2 additions & 1 deletion src/deleteintentstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "devicemanagermessages.h"
#include "printermediaidentity.h"
#include "privatedirectorypath.h"

#include <QDir>
#include <QFile>
Expand Down Expand Up @@ -424,7 +425,7 @@ bool ensureDirectory(const QString &path, QString *errorMessage) {
systemErrorText(tryx::DeviceManagerMessages::tr("Cannot inspect the delete intent directory"),
errorNumber));
}
if (!QDir().mkpath(directory)) {
if (!tryx::makePrivateDirectoryPath(directory)) {
return setError(
errorMessage,
tryx::DeviceManagerMessages::tr("Cannot create the delete intent directory"));
Expand Down
12 changes: 11 additions & 1 deletion src/devicemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "mediatransform.h"
#include "paseoverlayconfig.h"
#include "pasemetricsconfigstore.h"
#include "privatedirectorypath.h"
#include "privateruntimepaths.h"
#include "printermediafileintegrity.h"
#include "printermediaidentity.h"
Expand Down Expand Up @@ -2241,7 +2242,13 @@ void DeviceManager::loadRuntimePresentationPreferences() {
presentationPreferences_ = result.preferences;
presentationPreferences_.revision = 1;
if (!result.detail.isEmpty()) {
qWarning().noquote() << result.detail;
QString detail = result.detail;
if (const QString problem = tryx::privateDirectoryProblem(
runtimePresentationPreferencesStore_->directory());
!problem.isEmpty()) {
detail += QStringLiteral(" (%1)").arg(problem);
}
qWarning().noquote() << detail;
}
}
const TryxRuntimePresentationPreferencesV1 loaded =
Expand Down Expand Up @@ -2946,6 +2953,9 @@ PrinterSessionController::Callbacks DeviceManager::sessionCallbacks() {
callbacks.retryCacheValidationPending = [this]() {
return operationCoordinator_.retryCacheValidationPending();
};
callbacks.retryCacheStartupFailureDetail = [this]() {
return operationCoordinator_.retryCacheStartupFailureDetail();
};
callbacks.hasUnresolvedRetryOutcomeForFirmware = [this]() {
return operationCoordinator_.hasUnresolvedRetryOutcomeForFirmware();
};
Expand Down
4 changes: 3 additions & 1 deletion src/firmwarerecoveryjournal.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "firmwarerecoveryjournal.h"

#include "privatedirectorypath.h"

#include <QDir>
#include <QFile>
#include <QFileInfo>
Expand Down Expand Up @@ -173,7 +175,7 @@ bool openVerifiedDirectory(
const QString parentPath =
QFileInfo(directoryPath)
.absolutePath();
if (!QDir().mkpath(parentPath)) {
if (!tryx::makePrivateDirectoryPath(parentPath)) {
return setError(
errorMessage,
QStringLiteral(
Expand Down
3 changes: 2 additions & 1 deletion src/mediacatalogstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "devicemanagermessages.h"
#include "printermediafileintegrity.h"
#include "printermediaidentity.h"
#include "privatedirectorypath.h"

#include <QCryptographicHash>
#include <QDir>
Expand Down Expand Up @@ -202,7 +203,7 @@ bool MediaCatalogStore::ensureDirectories(QString *errorMessage) const {
}
return false;
}
if (!info.exists() && !QDir().mkpath(path)) {
if (!info.exists() && !tryx::makePrivateDirectoryPath(path)) {
if (errorMessage) {
*errorMessage = tryx::DeviceManagerMessages::tr(
"Cannot create the media catalog directory");
Expand Down
3 changes: 2 additions & 1 deletion src/pasemetricsconfigstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "configurationformatbackup.h"
#include "devicemanagermessages.h"
#include "paseoverlayconfig.h"
#include "privatedirectorypath.h"

#include <QDir>
#include <QFile>
Expand Down Expand Up @@ -252,7 +253,7 @@ bool PaseMetricsConfigStore::ensureDirectory(
}
return false;
}
if (!info.exists() && !QDir().mkpath(directory_)) {
if (!info.exists() && !tryx::makePrivateDirectoryPath(directory_)) {
if (errorMessage) {
*errorMessage = tryx::DeviceManagerMessages::tr(
"Cannot create the PASE metrics configuration directory");
Expand Down
3 changes: 2 additions & 1 deletion src/printermediapreparer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "printermediafileintegrity.h"
#include "printermediaidentity.h"
#include "printerprotocol.h"
#include "privatedirectorypath.h"
#include "turrismediaformat.h"

#include <panorama/media.hpp>
Expand Down Expand Up @@ -106,7 +107,7 @@ QString printerTempPath(const QString &fileName) {
const QString directory =
QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
.filePath(QStringLiteral("prepared-media"));
QDir().mkpath(directory);
tryx::makePrivateDirectoryPath(directory);
return QDir(directory).filePath(fileName);
}

Expand Down
18 changes: 17 additions & 1 deletion src/printeroperationcoordinator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "printermediaidentity.h"
#include "mediatransform.h"
#include "paseoverlayconfig.h"
#include "privatedirectorypath.h"
#include "privateruntimepaths.h"
#include "runtimeapplyrequestcodec.h"

Expand Down Expand Up @@ -314,6 +315,10 @@ bool PrinterOperationCoordinator::retryCacheValidationPending() const {
!retryCacheLoadComplete_;
}

QString PrinterOperationCoordinator::retryCacheStartupFailureDetail() const {
return retryCacheStartupFailure_ ? retryCacheFailureDetail_ : QString();
}

PrinterOperationCoordinator::RuntimeDowngradeAssessment
PrinterOperationCoordinator::runtimeDowngradeAssessment() const {
RuntimeDowngradeAssessment assessment;
Expand Down Expand Up @@ -1528,6 +1533,11 @@ void PrinterOperationCoordinator::loadRetryCache(
retryCacheFailureDetail_ = loaded.detail.isEmpty()
? tryx::DeviceManagerMessages::tr("Retry-cache state is invalid or unsafe")
: loaded.detail;
if (const QString problem =
tryx::privateDirectoryProblem(retryCacheDirectory());
!problem.isEmpty()) {
retryCacheFailureDetail_ += QStringLiteral(" (%1)").arg(problem);
}
qWarning().noquote()
<< tryx::DeviceManagerMessages::tr("Retry-cache startup remains fail-closed: %1")
.arg(retryCacheFailureDetail_);
Expand Down Expand Up @@ -5237,9 +5247,15 @@ void PrinterOperationCoordinator::loadDeleteIntent() {
pendingDeleteIntent_.reset();
pendingDeleteOperationId_ =
QStringLiteral("invalid-delete-intent");
QString detail = loaded.detail;
if (const QString problem = tryx::privateDirectoryProblem(
QFileInfo(deleteIntentPath()).absolutePath());
!problem.isEmpty()) {
detail += QStringLiteral(" (%1)").arg(problem);
}
qWarning().noquote()
<< "Delete intent was not accepted; deletes remain blocked:"
<< loaded.detail;
<< detail;
return;
}
const tryx::DeleteIntentRecord &intent = loaded.record;
Expand Down
1 change: 1 addition & 0 deletions src/printeroperationcoordinator.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class PrinterOperationCoordinator final : public QObject {
bool hasPendingDeleteRecovery() const;
bool hasPendingReplaceRecovery() const;
bool retryCacheValidationPending() const;
QString retryCacheStartupFailureDetail() const;
RuntimeDowngradeAssessment runtimeDowngradeAssessment() const;
SupportState supportState() const;
void initializeDeviceMediaOutbox();
Expand Down
1 change: 1 addition & 0 deletions src/printerproductprofile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ PrinterProductProfile paseFamilyProfile(quint16 productId, bool firmwareFlash) {
profile.overlayLayout = PrinterOverlayLayoutKind::PaseDualArea2240;
profile.orientationModel = PrinterDisplayOrientationModel::RotationFields;
profile.fileTransferTrackId = 0;
profile.keepBulkInPending = true;
profile.mediaUploadSupported = true;
profile.mediaCatalogSupported = true;
profile.mediaPullSupported = true;
Expand Down
5 changes: 5 additions & 0 deletions src/printerproductprofile.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ struct PrinterProductProfile {
// 0 keeps the full transaction timeout. Turris firmware never
// acknowledges these writes and the official app does not wait for them.
int readbackConfirmedWriteAckWindowMs = 0;
// Keep one bulk IN transfer pending between requests, as the official
// bridge does. With the IN armed only around each request, the idle link
// between exchanges makes Panorama-family firmware drop off the bus until
// power is removed (issue #28).
bool keepBulkInPending = false;
// Written into UserConfiguration when the device reports no such section.
QString defaultPowerOnMedia;
QString defaultStandbyMedia;
Expand Down
5 changes: 5 additions & 0 deletions src/printerprotocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ bool PrinterProtocol::validateMediaPullPathForTesting(const QByteArray &rawPath,
return validateMediaPullPath(rawPath, mediaName);
}

PrinterProtocol::IdleInputTestResult PrinterProtocol::runIdleInputScenarioForTesting(
const QList<DuplexTestEvent> &events, int cycles, int idleMs) {
return UsbPrinterTransport::runIdleInputScenarioForTesting(events, cycles, idleMs);
}

PrinterProtocol::DuplexTestResult PrinterProtocol::runDuplexTransportScenarioForTesting(
const QList<DuplexTestEvent> &events, const QByteArray &request, int writeTimeoutMs,
int readTimeoutMs) {
Expand Down
Loading