Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

TisVlay brings TV Display control for Linux, including: power, volume, backlight, AV mute, input switching plus more, with per-device profile automation via desktop session integration (idle/lock, boot/suspend, remote login/streaming, etc), and in reverse, letting the TV lock, sleep or wake the PC and its remote drive the desktop, while supporting various vendor interface protocols for multi-display setups, mitigating energy usage and OLED panel burn-in or projector bulb burn-out.

CI Pipeline Coverage Latest release License

✨ Features

  • 📺 PC desktop monitor control
    • TV power on/off with PC, Screen on/off with lock screen
    • Easily adjust backlight, volume, active input, and more
  • 🤖 Automation via system/user events
    • System boot, reboot, shutdown, suspend, wake, DPMS
    • User lock/unlock, idle, stream connect/disconnect
  • 🎮 TV console remote control
    • PC sleep/wake/lock with TV power state or TV’s remote
    • Navigate PC via TV remote keypress (via HDMI CEC)
  • 🧰 Flexible profile customization
    • System-wide and per-user profiles for per-device behavior
    • Unified CLI and SDK for further scripting and integrations
  • 🧩 Modular & vendor agnostic
    • Supports HDMI CEC, Network IP Control (LG WebOS SSAP)
    • Generalized for further interfaces, protocols, and transports
  • 🩺 Self-healing connectivity
    • Interface status monitoring for automatic reconnect and retry
    • Per-NIC kernel carrier detection with route-aware failover
  • 📡 Input-aware automation gating
    • Suppress automation when TV already in use via another input
    • Prevent PC from interrupting movie sessions or console gaming
  • 🖱️ Graphical User Interfaces
    • GUI app for device pairing, profile editing, and user setting
    • System tray for quick access controls and device status
  • 🛋️ Couch mode for the big screen
    • Drive the GUI from a TV remote or gamepad, no mouse needed
    • Scaled-up 10-foot layout, automatic under Plasma Bigscreen

📸 Screenshots

GUI and system tray track user DE themes and accent color settings.

TVs — devices, status, profiles (dark theme)Controls — picture, audio, input (light theme)
Discovery — local device detectionPairing — auto populate configuration
System profiles — machine lifecycle eventsUser profiles — session events and idle timers
Settings — desktop notification preferencesAbout — components, their status and versions
Tray menu — per-TV quick control settingsTray tooltip — at a glance or scroll to adjust

🏛️ Architecture

TisVlay is comprised of a collection of cooperating components:

graph LR

subgraph User
    Gui["tisvlay-gui<br/><i>user settings</i>"]
    Cli["tisvlay<br/><i>CLI</i>"]
    Session["tisvlay-session<br/><i>desktop events</i>"]
    Tray["tisvlay-tray<br/><i>system tray</i>"]
end

subgraph System
    Daemon["tisvlay-daemon<br/><i>system daemon</i>"]
    Greeter["tisvlay-greeter<br/><i>login greeter</i>"]
end

subgraph Devices
    TV1["CEC TV"]
    TV2["LG TV"]
end

Session -->|socket| Daemon
Tray -->|socket| Daemon
Gui -->|socket| Daemon
Cli -->|socket| Daemon
Daemon -.->|HDMI-CEC| TV1
Daemon -.->|WebSocket / WoL| TV2
ComponentDescription
tisvlayCLI for direct TisVlay control and configuration
tisvlay-daemonSystem profile actor and device interface driver
tisvlay-sessionUser profile actor and session event integration
tisvlay-traySystem tray for quick access and desktop notifications
tisvlay-guiDevice management, profile editor, settings panel
tisvlay-greeterLogin greeter idle detection absent of active session

Services ship as tisvlay CLI subcommands; tisvlay-gui as separate binary.

📖 Where to go next

Installation

Most Linux users can install a pre-built package below. Choose the instructions for your distribution.

Distro packages (.deb / .rpm / .pkg.tar.zst / Nix)

Pre-built packages are published on the releases page for each tagged version, for x86_64 and arm64 (aarch64). The commands below pick the one that matches your machine. Each package is signed; to check one before you install it, see Security & Supply Chain.

export VERSION=0.6.0
export BASE=https://github.com/TisVlay/TisVlay/releases/download/v${VERSION}

Debian / Ubuntu

ARCH=$(dpkg --print-architecture)   # amd64 or arm64
wget ${BASE}/tisvlay_${VERSION}-1_${ARCH}.deb
sudo apt install ./tisvlay_${VERSION}-1_${ARCH}.deb

Arch / CachyOS

wget ${BASE}/tisvlay-${VERSION}-1-$(uname -m).pkg.tar.zst
sudo pacman -U ./tisvlay-${VERSION}-1-$(uname -m).pkg.tar.zst

Fedora / RHEL

wget ${BASE}/tisvlay-${VERSION}-1.$(uname -m).rpm
sudo dnf install ./tisvlay-${VERSION}-1.$(uname -m).rpm

Bazzite / Silverblue

These immutable systems apply package changes after a reboot.

sudo rpm-ostree install ./tisvlay-${VERSION}-1.$(uname -m).rpm

NixOS / Source Install

NixOS module

The repository provides a NixOS module that wires up the daemon, user services, and CLI together. With flakes, add the repository as an input:

# flake.nix
{
  inputs.tisvlay.url = "github:TisVlay/TisVlay";

  outputs = { nixpkgs, tisvlay, ... }: {
    nixosConfigurations.your-host = nixpkgs.lib.nixosSystem {
      modules = [
        tisvlay.nixosModules.default
        {
          services.tisvlay.enable = true;
          services.tisvlay.users = [ "yourname" ];
        }
      ];
    };
  };
}
Without flakes

Without flakes, import a fetched copy of the repository (or one pinned with npins or niv); it evaluates to the same outputs the flake has:

# configuration.nix
let
  tisvlay = import (builtins.fetchTarball "https://github.com/TisVlay/TisVlay/archive/main.tar.gz");
in
{
  imports = [ tisvlay.nixosModules.default ];
  services.tisvlay.enable = true;
  services.tisvlay.users = [ "yourname" ];
}
Prebuilt binaries (optional)

By default the module builds tisvlay against your system’s nixpkgs, so it compiles locally on every install and update. Each release is also built by CI and published to the public tisvlay.cachix.org binary cache, for x86_64 and aarch64 Linux. To use those builds instead, pin the repository to a release tag, trust the cache, and point the module at the repository’s own package. With flakes:

# flake.nix
{
  inputs.tisvlay.url = "github:TisVlay/TisVlay/vX.Y.Z"; # a release tag
  # No `inputs.tisvlay.inputs.nixpkgs.follows`: it would change the build
  # and miss the cache.

  outputs = { nixpkgs, tisvlay, ... }: {
    nixosConfigurations.your-host = nixpkgs.lib.nixosSystem {
      modules = [
        tisvlay.nixosModules.default
        ({ pkgs, ... }: {
          nix.settings.substituters = [ "https://tisvlay.cachix.org" ];
          nix.settings.trusted-public-keys = [
            "tisvlay.cachix.org-1:pa9AnLlywwXfm4cwUzRD1afQh2VbGTbYF6a9iQgzb/g="
          ];
          services.tisvlay.enable = true;
          services.tisvlay.package =
            tisvlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
        })
      ];
    };
  };
}
Without flakes
# configuration.nix
{ pkgs, ... }:
let
  # A release tag.
  tisvlay = import (builtins.fetchTarball "https://github.com/TisVlay/TisVlay/archive/vX.Y.Z.tar.gz");
in
{
  imports = [ tisvlay.nixosModules.default ];
  nix.settings.substituters = [ "https://tisvlay.cachix.org" ];
  nix.settings.trusted-public-keys = [
    "tisvlay.cachix.org-1:pa9AnLlywwXfm4cwUzRD1afQh2VbGTbYF6a9iQgzb/g="
  ];
  services.tisvlay.enable = true;
  services.tisvlay.package = tisvlay.packages.${pkgs.stdenv.hostPlatform.system}.default;
}

The nix.settings lines take effect once a switch activates them, so pass the cache on the command line for the first switch, or it builds locally (without flakes, drop --flake):

sudo nixos-rebuild switch --flake .#your-host \
  --option extra-substituters https://tisvlay.cachix.org \
  --option extra-trusted-public-keys tisvlay.cachix.org-1:pa9AnLlywwXfm4cwUzRD1afQh2VbGTbYF6a9iQgzb/g=

Later switches pick the cache up from nix.settings alone.

services.tisvlay.enable runs tisvlay-daemon (as the tisvlay user), the tisvlay-session and tisvlay-tray user services, and the tisvlay CLI in $PATH. The module’s options, all under services.tisvlay:

OptionWhat it does
usersThe durable access grant: tisvlay group membership. List every account that should control TVs from SSH, scripts, or while not at the console. The console user has access anyway, but listing your user means access never depends on being the active seat.
seatAccessThe presence grant for the active seat’s graphical user (on by default). Set false to restrict the socket to users and the greeter.
settingsDeclarative TVs and profiles, rendered to a seed file (see Configuration Files). Re-applied on every start for TVs not already in the daemon’s store, so runtime edits and pairing still persist.
cec.enable, cec.pulse8.enableDevice access for the HDMI-CEC backend; the second also builds the in-tree Pulse-Eight USB-CEC driver.
greeter.enableThe login-screen idle client (see Automation). On automatically when plasma-login-manager or GDM is the display manager.
openFirewall, bindPortOpen the pinned discovery port in the host firewall (see Firewall & Discovery).
sunshinePortWhere to find Sunshine if you moved it off its default port (see Streaming).
logLevel, extraArgsThe services’ log level, and extra tisvlay-daemon arguments for anything the module doesn’t expose.
user, group, uid, gidThe daemon’s service account. Fix uid and gid only when they must match across hosts.

Types and defaults are in each option’s description in module.nix.

cargo from source
git clone https://github.com/TisVlay/TisVlay tisvlay
cd tisvlay
cargo build --release

Binaries land in target/release/. Building from Cargo does not install the systemd units — copy those from packaging/ if you want the daemon and session services managed by systemd, and create the tisvlay group yourself.

A recent stable Rust toolchain is required — at least the rust-version declared in the workspace Cargo.toml; install it from https://rust-lang.org/tools/install/.

After installation

After a fresh install, the system daemon starts automatically. The session and tray start at your next graphical login; to start them now:

systemctl --user start tisvlay-session tisvlay-tray

Packages also install shell completion for the tisvlay CLI (bash, zsh, fish); see the Command Line page. Bash needs the bash-completion loader (recommended by the Debian and Fedora packages).

Access

At the desktop, no extra setup is needed: the daemon grants access to the user at the active seat.

Set up access for SSH, scripts, or headless use

For SSH or headless use, grant your account persistent access, then run:

tisvlay enable-access

This adds your account to the tisvlay group through polkit. The group change takes effect at a fresh login; because some desktop services keep old group membership after logout, reboot if access is still denied.

Optional Additions

HDMI-CEC backend (optional)

The HDMI-CEC backend needs a CEC adapter that appears as a /dev/cecN character device. Most GPU HDMI ports don’t wire the CEC pin. Two known-working sources:

  • A USB CEC adapter (e.g. Pulse-Eight), using the kernel pulse8-cec driver. Pulse-Eight adapters also need inputattach --pulse8-cec to bind the USB serial device and create /dev/cecN; some distributions provide this setup already. See the kernel’s USB dongle instructions.
  • An active DisplayPort→HDMI adapter on an amdgpu or i915 output — the GPU driver tunnels DP-AUX CEC. Nvidia GPUs expose no CEC, so use a USB adapter there.

The daemon’s sandbox hides /dev by default, so it needs device access granted once:

  • NixOS: set services.tisvlay.cec.enable = true for a GPU adapter. For a Pulse-Eight USB adapter, set services.tisvlay.cec.pulse8.enable = true; it also enables device access and attaches the driver.

  • Distro packages: let the CEC devices through the sandbox with a drop-in (sudo systemctl edit tisvlay-daemon):

    [Service]
    PrivateDevices=false
    DeviceAllow=char-cec rw
    

    then give the tisvlay group read/write on the nodes with a udev rule, and restart the daemon:

    echo 'KERNEL=="cec[0-9]*", GROUP="tisvlay", MODE="0660"' | sudo tee /etc/udev/rules.d/70-tisvlay-cec.rules
    sudo udevadm control --reload && sudo udevadm trigger --subsystem-match=cec
    sudo systemctl restart tisvlay-daemon
    

For a Pulse-Eight adapter, this grants the daemon access to /dev/cecN once it exists; the host still needs the pulse8-cec driver and inputattach setup described above.

For the TV acting on your PC, the packages and the NixOS module also install a polkit rule letting the daemon put the PC to sleep, a service that applies the TV-wakes-the-PC setting around each sleep, and a keymap keeping the TV remote’s power keys off the desktop. A source build installs none of them: sleeping the PC and the wake setting don’t work there, and turning on Remote keys reach the desktop lets the remote’s power key reach the desktop, which usually sleeps the PC. The files and where they go are in packaging/README.md.

The backend depends on the LGPL-licensed linux-cec crate.

No pairing prompt or credentials are involved — a CEC TV is added by picking its adapter; see Adding a TV.

Quick Start

This walks you from a fresh install to a TV that blanks when you lock your desktop, using only the TisVlay app. It uses an LG webOS TV on your network as the example; an HDMI-CEC TV goes through the same wizard (see Adding a TV).

1. Open TisVlay

Launch TisVlay from your application menu. In the bottom-left corner of the window, the Daemon, Session, and Tray indicators should all be green. If one isn’t, see Troubleshooting.

If a banner offers Enable TV control, click it and then log out and back in. That grants your account access to the TisVlay service; see Access.

2. Add your TV

Make sure the TV is on and on the same network as this computer, then:

  1. On the TVs page, click the + button above the TV list.
  2. Wait for your TV to appear in the list, select it, and click Next.
  3. Check the name on the Configure page, then click Pair.
  4. When the prompt appears on the TV, accept it with the remote.

The wizard ends with a “your TV is ready” message; click Close. Adding a TV covers each step, plus what to do if your TV isn’t found.

3. Try the controls

Select your TV in the list. The detail view has live controls:

  • Power on / Power off and the Managed switch (under STATUS).
  • Backlight and Screen (under PICTURE). Turning Screen off blanks the panel while the TV stays powered, so it resumes instantly and is safe for OLED panels.
  • Volume and Mute (under AUDIO).
  • Current input (under INPUT).

Which controls appear depends on what the TV supports; an HDMI-CEC TV, for example, only offers power and screen.

4. Use the tray icon

TisVlay also adds a tray icon. Click it for a menu with one submenu per TV (power, input, backlight, screen, volume, mute), and a Settings entry that opens the app. Hover over the icon for a summary of each TV, or scroll over it to adjust volume or backlight.

On GNOME, the tray icon needs an extension; see GUI & Tray.

5. Let it automate

A newly added TV is Managed, which means it follows your desktop. With the stock profiles:

  • Lock your screen and the TV blanks.
  • Touch the mouse at the lock screen and the TV lights up before you type your password.
  • Suspend or shut down and the TV powers off; resume or boot and it powers on.

Try it: lock your desktop, wait a moment, then wake it. To change what happens, or to blank the TV after a period of inactivity, edit the profiles on the Profiles page; see Automation. To keep a TV out of automation entirely, turn its Managed switch off.

Prefer the command line?

Everything above also works from a terminal; see the CLI Reference. For example:

tisvlay pair "Living Room" --vendor lg --params '{"ip":"192.168.1.100"}'
tisvlay list                    # shows each TV's id
tisvlay screen <id> off

Adding a TV

Adding a TV connects it to TisVlay. What that involves depends on the kind of TV: an LG webOS TV on your network shows a prompt that you accept with its remote, while an HDMI-CEC TV needs no prompt at all (below).

Add a TV with the wizard

Open TisVlay, go to TVs, and click the + button above the TV list. The wizard has four steps: Discover, Configure, Pair, and Done.

1. Discover

TisVlay scans your network and lists each TV as it answers. The chip on each row shows how it was found: SSDP for a network TV, CEC for a TV on one of this computer’s HDMI-CEC adapters. Select your TV and click Next.

If your TV isn’t listed, make sure it’s on (some TVs only answer while awake), then use Don’t see your TV? Add it manually: choose the Vendor, type the TV’s address (its IP for LG, or its device such as /dev/cec0 for HDMI-CEC), and click Continue. If scanning never finds anything but manual entry works, see Firewall & Discovery.

A row marked No access is a CEC adapter the daemon can see but not open; see Installation.

2. Configure

Check the details TisVlay filled in:

  • Friendly name is how the TV is shown in the app, the tray, and the command line.
  • IP and MAC are the TV’s network address and its hardware address. A green Auto-detected badge means TisVlay found the value itself. The MAC is what lets TisVlay wake the TV from standby; see The Wake-on-LAN MAC.
  • Initial system profile decides what happens at boot, shutdown, and sleep. You can change it later.
  • Managed by TisVlay lets automation act on this TV. Untick it to add the TV for manual control only.

Click Pair.

3. Pair

For an LG TV, a prompt appears on the TV: accept it with the remote. The wizard waits and shows its progress. If the TV rejects the prompt or it times out, use Retry pairing or Back to discovery. If no prompt appears, see Troubleshooting.

4. Done

The wizard confirms the TV is ready and shows which MAC it ended up with. Click Close; the new TV is selected on the TVs page.

The Wake-on-LAN MAC

To wake an LG TV from standby, TisVlay needs the MAC address of the network connection the TV is actually using. A TV has separate wired and Wi-Fi addresses, and only the one it’s connected through will work. When pairing completes, TisVlay asks the TV for it, and the last step tells you what happened:

  • MAC detected from the TV means the field was blank, so the TV’s MAC was saved and waking from standby will work.
  • MAC updated means the TV’s MAC was adopted in place of the one you had.
  • The TV reports … but … was entered means the MAC you typed differs from the TV’s (often the other interface). Choose Use reported to switch to the TV’s, or Keep mine to keep yours.

A MAC you entered is never overwritten without your choice. Asking the TV is the reliable way to get a MAC for a TV on another subnet or VLAN, where your computer can’t see it. This works for LG TVs paired by IPv4 address; for anything else, enter the MAC yourself (it’s in the TV’s network settings). Wake-on-LAN also has requirements on the TV’s side; see The TV won’t power on.

HDMI-CEC: no pairing needed

HDMI-CEC has no credentials and no on-screen prompt. Any device on the CEC bus can be controlled, so adding a CEC TV just means choosing which adapter it’s on:

  1. In the wizard’s Discover step, select the adapter (for example /dev/cec0); TisVlay asks the TV for its name and model and fills them in.
  2. Click Next, then Pair.

The TV must be on the CEC bus (powered on or in standby) and on the input the adapter is plugged into. See Installation for adapter requirements, and Requirements for what CEC can and can’t control.

Re-pairing a TV

Pairing credentials can expire or be revoked by the TV. Select the TV on the TVs page, scroll to DANGER ZONE, and click Re-pair. TisVlay runs the handshake again and keeps the TV’s name, profiles, and other settings. Re-pairing also reads the TV’s MAC, so it’s the way to give an already-added TV a MAC it’s missing.

Removing a TV

In the same DANGER ZONE, Remove TV deletes the TV and its settings from TisVlay. You’d need to add it again to use it.

Pairing requirements

  • The TV is powered on and reachable on the network. Answering ping isn’t enough: a TV in standby can keep its network up while refusing control connections.
  • Newer LG firmware requires a secure (wss://) connection with a self-signed certificate. TisVlay handles this automatically.
Pair from the command line
tisvlay pair "<name>" --vendor lg --params '{"ip":"192.168.1.100"}'
  • "<name>" is the friendly name, shown in tisvlay list and the app.
  • --vendor lg selects the backend. tisvlay vendor lists the registered backends, and tisvlay vendor <vendor> shows a backend’s pairing fields.
  • --params is the backend’s pairing fields as a JSON object. For LG: ip (required) and mac (optional; wake_id is accepted as an alias). Leave mac out and tisvlay reads it from the TV.

Accept the prompt on the TV. On success the command prints the TV’s UUID, the id you pass to every other command (tisvlay list shows it too). Pairing by IP address works whether or not discovery can see the TV.

The command reports the MAC on a mac: line:

  • mac: detected AA:BB:CC:DD:EE:FF (saved): you didn’t supply a MAC, so the TV’s was stored.
  • mac: AA:BB:CC:DD:EE:FF (matches the TV): the MAC you supplied is right.
  • mac: TV reports …, configured …; to use it: tisvlay config <id> '{"mac":"…"}': yours differs and was kept; run the printed command to switch to the TV’s.

To pair a TV for manual control only, add --unmanaged (or later run tisvlay managed <id> off). To re-pair, run tisvlay repair <id>.

GUI & Tray

TisVlay has two desktop frontends: a settings window for setup and configuration, and a tray icon for quick day-to-day control. Both talk to the same background service, so a change in one shows up live in the other (and in the command line).

The TisVlay window

Launch TisVlay from your application menu. Launching it again focuses the existing window. The window follows your desktop’s light or dark theme and accent color.

The sidebar has four pages (TVs, Profiles, Settings, About) and, at the bottom:

  • Reload, which re-reads the configuration files from disk. You only need it if you edit them by hand; see Hand-editing and reloading. If a file can’t be applied, a banner above the page names the file and the problem until you dismiss it or a later Reload succeeds.
  • Three status dots for the Daemon, Session, and Tray services. A filled dot means the service is running: green if it works with this window, amber if it’s on a slightly different patch release (still compatible), red if it can’t be used. A hollow dot means it isn’t running.

Two banners can appear above any page:

  • Enable TV control: your account can’t reach the service yet. Click the button, then log out and back in; see Access.
  • Version mismatch: a service is on a different release than the window, usually right after an upgrade. See Troubleshooting.

Tips. An ⓘ button reveals a longer explanation of the option beside it (click it, or press OK on it), and icon-only buttons show a short tip when you hover, or when the focus ring rests on them. Alt+← and Alt+→ (or your mouse’s back and forward buttons) step through the pages you’ve visited, like a browser. They’re inactive while the pairing wizard or a TV edit form is open. The whole window also works without a mouse: see From a remote or gamepad.

TVs

The list on the left shows each TV with a green dot when it’s connected, and whether it’s Managed, Automated (managed, and currently being acted on), or Unmanaged. The header counts how many are connected, and the + button opens the wizard to add a TV. Select a TV to see its details:

GroupWhat it has
STATUSPower on / Power off, with the current state (including transitional states like Warming up…), and the Managed switch.
PROFILESThe System and User profile this TV uses, with a pencil that jumps to that profile in the editor. See Automation.
PICTUREBacklight and Screen (on/off). Turning the screen off blanks the panel while the TV stays powered.
AUDIOVolume and Mute.
INPUTThe Current input (changing it switches the live input) and the Primary input, which is the one your PC is on. Automation uses the primary input to avoid interrupting you when the TV is showing something else.
CONFIGThe TV’s name and connection details (for an LG TV, its IP and MAC). Click the pencil to edit, then Save or Cancel. Clearing an optional field puts it back to its default.
INFORead-only details: TV ID, Make, Model, Serial, Connector, and anything else the TV reports. Each value has a copy button.
DANGER ZONERe-pair to refresh the TV’s credentials, and Remove TV. See Adding a TV.

Groups and controls the TV can’t use are hidden: an HDMI-CEC TV has no backlight, volume, mute, or input controls, for example. If a TV is off the network and TisVlay has no MAC address for it, Power on is disabled and says why; see The Wake-on-LAN MAC.

When the TV list is empty, it says why: Daemon offline, TVs hidden: version mismatch, Loading (the service just started or came back, and its TVs are still arriving), or No TVs configured.

Profiles

Create, rename, duplicate, and delete profiles, and edit what each event does, in one place. See Automation.

Settings

The Settings page controls the tray’s desktop notifications and has a Send test notification button to check that your desktop’s notification system works. If the tray service isn’t running, the switches are dimmed and a banner says so. Its COUCH MODE group sets how the window behaves on a TV across the room; see From a remote or gamepad.

About

The version, the status and version of each component (daemon, session, tray, GUI, and CLI), and links to the release notes, third-party notices, and the issue tracker. Diagnostics copies a report to your clipboard; paste it into bug reports. Each component with a configuration file has a Config button that opens it in your file manager.

From a remote or gamepad

The window can be driven without a mouse: from a keyboard, or from a TV remote or gamepad under a TV shell that turns their buttons into keys. Plasma Bigscreen does, and so does a TV that forwards its own remote’s keys to the desktop (The remote’s keys). An accent-colored ring marks the control the keys act on. It appears with the first key press and hides again when you use the mouse.

KeyWhat it does
↑ ↓Move along the sidebar, down a list, or between the rows of a page. The sidebar switches pages as you move, and a list shows the TV or profile under the ring.
← →Move along a row, from its control to the buttons beside it, and between the sidebar, the list and the page. On a slider they change the value.
OK / EnterActivate: enter a page from the sidebar, open the TV or profile under the ring, press a button, flip a switch, open a choice, start typing in a field.
Back / EscOne level out: close a help bubble, stop editing, leave the page for its list and the list for the sidebar. In the pairing wizard it’s Back, or Cancel; while editing a TV’s config it’s Cancel, asking first if you changed something. On the sidebar it returns from a jump you made with a pencil or a Used by link.
MenuHelp for the row under the ring, the same as its ⓘ.
Tab / Shift+TabThe next or previous control; in couch mode, the next or previous page.

Under Plasma Bigscreen, a gamepad’s D-pad, Cross and Circle are the arrows, OK and Back, Square is Menu, and the shoulder buttons are Tab and Shift+Tab; a TV remote’s arrows, OK and Back are the arrows, OK and Back. On any desktop, a TV that forwards its remote’s keys (Remote keys reach the desktop, see TV Controls the PC) gives the same arrows, OK and Back, and its Info key is Menu. Help is also on each row’s ⓘ, at the end of the row.

Couch mode. On a TV across the room the window opens maximized and scaled up, and the keys are arranged for a remote: a text field, slider or number waits for OK before it takes the arrows, so moving between rows never changes a value by accident (OK or Back finishes), Tab and Shift+Tab step between the pages, and Back on the sidebar closes the window. It turns on by itself under Plasma Bigscreen, the TV shell it recognizes. To use it on an ordinary desktop driving a TV, or to keep it off, set Settings → COUCH MODE → Couch mode to On or Off (Follow the session is the default), and adjust Scale, a percentage on top of your display’s own scale: larger for a farther couch or a smaller screen. Changes apply at once and are kept for the next launch.

Tray applet (tisvlay-tray)

The tray applet starts with your graphical session. Click its icon for a menu with one submenu per TV, and a Settings entry that opens the window. The icon is in color while the service is running and at least one TV is connected, and a flat monochrome glyph otherwise. Each TV’s own menu entry also has an icon showing whether it’s connected and automated, connected but not automated, or disconnected.

GNOME users: vanilla GNOME (Fedora and others) ships no system-tray host, so the icon won’t appear until you install the AppIndicator extension (gnome-shell-extension-appindicator; the .deb and .rpm suggest it, and Ubuntu bundles its own). Without it the tray still runs: notifications work and the window is unaffected. Only the icon and menu are missing. KDE Plasma needs nothing.

A TV that is on and reachable offers:

  • Power Off
  • Profiles, with User and System submenus to pick each profile
  • Unmanage (or Manage)
  • Input, if the TV reports its inputs
  • Backlight, showing the current value, with Backlight Up (+10) and Backlight Down (-10)
  • Screen Off (or Screen On)
  • Volume, showing the current value, with Volume Up (+10), Volume Down (-10), and Mute (or Unmute)

Controls a TV doesn’t support are left out. A TV that is off or unreachable, including an HDMI-CEC TV in standby, offers only Power On (Wake), Profiles, and Manage/Unmanage. If the service isn’t running the menu says Daemon unavailable, with no TVs added it says No TVs configured, and right after an upgrade it may say Version mismatch and name both releases: see Troubleshooting.

Hover over the icon for a summary of each TV.

Scroll over the icon to change volume or backlight; each notch moves it by 1, where the menu’s Up and Down move it by 10. The wheel adjusts whichever control you last used from the menu. Before you’ve used either, it adjusts backlight if any powered-on TV offers it, and volume otherwise. It acts on the TV you last used from the menu, or else the first powered-on TV that supports that control. A ► in the tooltip marks the current target. Middle-click cycles through the powered-on TVs that support it. If no TV can be adjusted, scrolling does nothing.

While a stream has any TV armed, the menu shows a read-only Stream active line, so you can tell at a glance that TV wakes are being suppressed. See Streaming.

Notifications

The tray shows desktop notifications for changes that happen while it’s running (not for the state it finds when it starts). Each category can be switched on or off under Settings → NOTIFY ME ABOUT, and all are on by default:

SettingNotifies you when
TV connectiona TV connects or disconnects
Pairing outcomepairing or re-pairing completes or fails
Wake outcomea wake from standby starts, succeeds, or fails
Daemon offlinethe TisVlay service goes offline or comes back, or was upgraded to a release the tray can’t talk to until you log in again
Starting the app from a terminal

The window is the tisvlay-gui command, and the tray applet runs as the tisvlay-tray user service:

tisvlay-gui
systemctl --user status tisvlay-tray

With the window closed, TISVLAY_COUCH_MODE=on tisvlay-gui (or off) opens it in or out of couch mode for this launch, whatever the setting says. While a window is open, a second launch only brings it to the front.

Automation

TisVlay can react to what your computer is doing: blank the TV when you lock your screen, light it when you come back, power it off when the machine sleeps. You decide what happens by editing profiles, which map events (lock, unlock, idle, sleep, …) to actions (screen off, screen on, power off, power on, or nothing).

This page covers what happens out of the box, how to change it, and the rules that decide which TVs react. For the TV acting on your PC, see TV Controls PC.

What happens by default

Out of the box, a managed TV follows your desktop:

When you…The TV…
Lock your screenblanks (stays powered, so it resumes instantly)
Touch the mouse or keyboard at the lock screenlights up, before you’ve typed your password
Unlockis already lit, and switches back to your PC’s input
Suspend the machinepowers off
Resume the machinepowers on, and switches back to your PC’s input
Shut downpowers off
Bootpowers on

The TV blanking or waking along with your monitor (DPMS) is on too. Inactivity timers (blank the TV after N minutes idle) are off until you set them.

Wake events never fire for a TV while a stream has it armed, so streaming in from elsewhere doesn’t light an empty room.

Changing what happens

Open the Profiles page. There are two lists on the left:

  • System profiles control what the machine does: boot, shutdown, reboot, sleep, wake, and your display blanking (DPMS). They also cover the login screen.
  • User profiles control what you do in your desktop session: lock, unlock, idle, and streams. Each user account has its own.

Every list starts with a default profile (marked ✓), which every TV uses until you assign something else. It can’t be renamed or deleted.

To change a behavior, select a profile and change the dropdown beside the event, for example On session lock from screen_off to none. Changes apply immediately.

The actions are:

ActionEffect
nonedo nothing
screen_offblank the panel; the TV stays powered
screen_onunblank
power_offput the TV in standby
power_onwake the TV from standby

Next to each dropdown are two small icon toggles:

  • Land on primary input (the remote icon): after this action, switch the TV to your PC’s input. This needs the TV’s Primary input set on its page, and a TV that can switch inputs (not HDMI-CEC).
  • Suppressed / fires while streaming (the network icon): whether this event still happens while a stream has the TV armed. See Streaming.

The idle events have two timers, Idle timeout (locked) and Idle timeout (unlocked), each in seconds, where 0 turns the timer off. The first applies at the lock screen, the second while you’re logged in and active. The On idle timeout and On idle resume actions say what happens when either timer fires and when activity returns.

Creating, duplicating, and assigning profiles

  • Click + beside System profiles or User profiles to create a profile. Duplicate one with the copy icon, and rename or delete it with the icons in its header. The header’s Used by list links to the TVs that use it.
  • A profile only needs to hold what’s different. A field you haven’t changed is inheriting (it follows the default profile); a field you’ve changed is set, and has an accent bar on its left edge and a reset icon. Click reset to hand the field back to the default, or use the reset icon beside User profile or System profile to do it for every field.
  • To use a profile, open the TV on the TVs page and pick it under PROFILES (System and User). The pencil beside each dropdown jumps to that profile in the editor. The tray menu also has a Profiles submenu for each TV.

Two ideas that cover most setups

  • A TV that ignores your desktop (a gaming TV, say): create a user profile with every event set to none, and assign it to that TV. Or leave the profile alone and set the system profile’s Automate when setting so it only automates while the TV is showing your PC (see below).
  • Different behavior for different people: user profiles belong to each account, so your account can blank aggressively while a shared account does nothing, on the same TV.

The events

Your desktop session (user profiles)

EventFires when
On session lockyou lock your screen
On lock wakeyou touch the mouse or keyboard at the lock screen
On session unlockyou unlock
On idle timeouteither idle timer runs out
On idle resumeactivity returns after an unlocked idle
Stream start / enda stream starts or ends
On local returnyou first touch the PC after a stream ended
  • Lock wake is there so the TV is lit before you type your password; the later unlock then has nothing left to do. (Wayland sessions only.)
  • Idle is independent of your desktop’s own screen-locking and blanking timers.
  • Multiple users: only the foreground session on a seat acts. With fast user switching, a backgrounded user’s lock and idle events are ignored until they’re active again, so one account can’t blank or wake the TV under whoever is using the machine.

The machine (system profiles)

On startup, on shutdown, on reboot, on sleep, and on wake are handled by the system service, with no one logged in. On DPMS off and On DPMS on mirror your host display’s power state onto the TV that shares its connector, so the TV follows your monitor blanking even before you log in. When a session is running, the lock and idle events can fire for the same blank; that overlap is harmless.

Login-screen idle

An unattended login screen never blanks by itself. The optional TisVlay greeter client closes that gap, using the system profile’s Idle timeout with On idle timeout and On idle resume. Blanking stays off until you set a non-zero timeout. GDM and plasma-login-manager are supported; the distro packages enable it for both, and on NixOS it turns on by itself for either. See Installation. It also lights the panel once at startup, so the login screen isn’t left dark after a logout.

Which TVs react

For an event to do anything to a TV, three things must agree:

  1. The TV’s Managed switch (on its page, under STATUS) is on. An unmanaged TV is never automated, and it doesn’t act on your PC either (TV Controls PC).
  2. The system profile’s Automate when setting allows it: always, on_primary (only while the TV is showing your PC’s input), or off_primary (only while it’s showing something else).
  3. No stream is suppressing the event (see below).

Manual actions from the app, the tray, or the command line always run, regardless.

ManagedAutomate whenTV on its primary input?Automated?
noanyanyno
yesalwaysanyyes
yeson_primaryyesyes
yeson_primarynono
yeson_primaryunknown (TV off)yes
yesoff_primaryyesno
yesoff_primarynoyes

Streams

A separate gate applies on top: while a stream has a TV armed, wake actions are suppressed unless the event’s stream toggle is set to fire. It’s independent of Automate when. The rules are in Streaming.

How profiles resolve

Resolution is per field, not per profile. For each field, TisVlay looks along a chain and uses the first value that’s set:

the TV's assigned profile  →  the default profile  →  built-in defaults

So a profile only spells out what it changes, and it keeps tracking the default for everything else. If you later edit the default, every field that isn’t set follows along.

User profiles resolve along the same chain, with your user profiles in place of the system ones.

To see exactly what a TV will do, use the command line’s tisvlay system-profile <tv> --effective (and user-profile for your user profile); the app shows set versus inheriting per field.

Making sure it’s running

Lock and idle events need the per-user session service. The sidebar’s Session indicator should be green. If an event fires but the TV does nothing, see Automation does nothing. Actions are verified and retried, and a boot-time or resume-time wake waits for the network; see Networking & Reliability.

Command-line equivalents
tisvlay profile system list                  # system profiles, with ids
tisvlay profile system create "guest" --body '{"on_startup":"none","on_wake":"none"}'
tisvlay system-profile <tv-id> <profile-id>  # assign; --clear unassigns, no id prints it
tisvlay profile user list                    # user profiles
tisvlay user-profile <tv-id> <profile-id>
tisvlay system-profile <tv-id> --effective   # one TV's fully resolved mapping
tisvlay managed <tv-id> off                  # stop automating a TV

tisvlay profile system defaults and tisvlay profile user defaults print the stock mappings. The full schema, with every field and its stock default, is in Configuration; the verbs are in the CLI Reference.

TV Controls PC

Automation runs from your PC to the TV. These settings run the other way: the TV turning off can lock your session or put the PC to sleep, and with an HDMI-CEC adapter the TV’s remote and the TV turning on can act on your PC too. Out of the box none of them acts, except that TisVlay keeps the TV from waking a sleeping PC (below).

What works where

SettingWhat it reacts toHDMI-CECLG over the network
On TV power offthe TV turned off by its remote, its menu or its own timeryesyes, while TisVlay is connected to it
On remote power keythe TV’s “power off this device” for your PC (on LG, in the SIMPLINK menu)yesno
Remote keys reach the desktopthe remote’s other keyswith an adapter that supports itno
On TV power onthe TV turning on while your PC sleepswith a USB adapter that can wake the PCno

An LG that TisVlay isn’t connected to, such as one turned back on with its own remote after TisVlay powered it off, isn’t watched, so its next power-off goes unseen.

Each TV’s page in the app has a FROM THE TV section showing which of these that TV supports. A setting a TV doesn’t support does nothing for it.

Setting them up

Open the Profiles page; the settings are in each profile’s FROM THE TV section. Where a setting lives depends on what it acts on:

  • The machine, in the system profile: putting the PC to sleep (On TV power off and On remote power key: suspend or sleep), waking it (On TV power on), and the remote’s keys. These work with nobody logged in.
  • Your session, in your user profile: locking it (On TV power off and On remote power key: lock). Each user sets their own.

suspend always suspends to RAM. sleep uses your machine’s configured sleep, which may hibernate, and needs systemd 256 or newer. Setting both a lock and a sleep does both. The stock values are in Configuration Files.

When they act

  • Only for a managed TV. Turning a TV’s Managed switch off stops it acting on your PC as well as TisVlay acting on it.
  • Never for TisVlay’s own power-offs. When your PC sleeps and powers the TV off, that doesn’t count as the TV turning off.
  • Never while a stream has the TV armed, so someone pressing the remote in the room doesn’t sleep the PC under a streaming user.
  • The TV turning off follows Automate when. With on_primary, a TV turned off while showing another input leaves your PC alone. The remote’s power key doesn’t: it’s aimed at your PC.
  • A wake press doesn’t count. A TV switching to your PC may send it a power-key press to wake it; TisVlay ignores a press unless the TV is already showing your PC.
  • Each TV acts alone. With two TVs, either one turning off acts, even while the other still shows your PC.
  • Nothing unlocks. Turning the TV on never unlocks your session.

The TV waking your PC

This needs a USB CEC adapter that can wake the PC (a Pulse-Eight can; the TV’s page shows Wakes this PC), and a PC that wakes from USB devices, which is a firmware setting on some machines.

  • wake: turning the TV on to your PC’s input wakes the PC, as does switching the TV to your PC’s input, or SIMPLINK’s power key for it. Turning the TV on to another input doesn’t.
  • none: TisVlay keeps the PC asleep. A Pulse-Eight would wake it regardless, so TisVlay disconnects the adapter for the sleep and reconnects it after, and the TV shows no device for your PC in the meantime.

A Pulse-Eight wakes the PC only with its own autonomous mode on, as it ships. If wake does nothing, check it with libcec 8 or newer: stop the adapter’s inputattach service, run cec-client on the adapter’s serial port, and enter am to read the setting or am 1 to turn it on. To keep the TV from ever waking the PC, whatever the profile says, am 0 turns it off.

An unmanaged TV’s adapter is left exactly as it is.

The remote’s keys

With Remote keys reach the desktop on, the TV remote’s arrows, OK, back and media keys arrive as keyboard keys, and your desktop decides what they do. The remote’s power keys never reach the desktop, so the remote can’t put the PC to sleep that way; On remote power key is the setting for that. The packages install what makes that so; a source build needs one extra step (Installation).

Streaming

When you connect to this computer from elsewhere (game streaming with Sunshine and Moonlight, RustDesk, or an RDP client), the stream unlocks the desktop and feeds it virtual keyboard and mouse input. Your desktop treats that exactly like someone sitting down, so without help TisVlay would power on the physical TV in an empty room.

Stream awareness prevents that. While a stream is active, TisVlay marks the affected TVs as armed: it holds back the actions that would turn them on, while still letting blanks and power-offs through. When you walk back to the machine, the TV wakes on your first touch of the mouse or keyboard.

You don’t need to set anything up for the common cases. Just connect.

What it does when armed

  • Wake actions are held back. Unlocking, touching the lock screen, activity resuming from idle, and your display waking (DPMS) don’t turn the TV on while it’s armed.
  • Blanks and power-offs still happen. Locking, idle blanking, sleep, and display blanking go through, so a TV that’s on still darkens for the empty room.
  • Starting and ending have their own actions. When a stream arms the TV, the profile’s On stream start action runs (out of the box it blanks the panel). When it ends, On stream end runs (out of the box it blanks too, since the room is still empty).
  • Your return wakes it. After a stream ends, your first genuine touch of the PC’s mouse or keyboard runs On local return (out of the box it lights the screen and switches back to your PC’s input). This covers the case where your desktop never locked or idled while you were away, so no unlock event would otherwise relight the TV.

You can override the hold-back per event. In the Profiles editor, each event row has a network icon: when it shows Suppressed while streaming, the event is held back while armed; click it so it shows Fires while streaming to let the event run anyway. See Automation.

What counts as a stream

SourceArms the TVNotes
Sunshine / Moonlightautomaticallyno setup; TisVlay detects it
RustDesk, RDPautomaticallydetected through the desktop’s screen-sharing system
Any other screen capture (OBS, a browser tab share, …)only if you turn on Screen capture armsTisVlay can’t tell who’s capturing, so it’s opt-in
Your own script or toolwhen it tells TisVlaysee Advanced

Software KVMs such as lan-mouse never arm, because they run while you’re physically present. A plain SSH login doesn’t arm either; only a graphical remote desktop does.

Screen capture arms is a switch on the STREAMING card of a user profile, in the Profiles page. Turn it on if you want any screen recording to hold back TV wakes. Leave it off (the default) and only the recognized streaming apps arm.

Seeing it work

While any TV is armed, the tray menu shows a read-only Stream active line. That’s the quickest way to tell that TV wakes are being held back on purpose; see GUI & Tray.

Advanced: Sunshine and manual control

Everything here is optional. Sunshine works without it, and the command line is only needed to arm TisVlay from your own scripts.

Sunshine integration

TisVlay detects Sunshine by watching Sunshine’s own status endpoint on this computer, which is why no configuration is needed. That endpoint sits on Sunshine’s base port, at its default unless you’ve moved it (the default is under --sunshine-port in the command-line reference). If you’ve moved Sunshine to another port, tell TisVlay where to look, or it will poll whatever now answers on the default port:

  • NixOS: services.tisvlay.sunshinePort = 48989; (Sunshine’s configured port).
  • Other distros: add --sunshine-port 48989 to the session service’s command line with systemctl --user edit --full tisvlay-session, then systemctl --user restart tisvlay-session.

To arm the TV the instant Sunshine launches, a second or two ahead of the status check, add a prep-command hook to Sunshine’s global_prep_cmd setting:

{
  "global_prep_cmd": [
    {
      "do": "tisvlay stream start --source sunshine",
      "undo": "tisvlay stream stop --source sunshine",
      "elevated": false
    }
  ]
}

The hook is purely a latency optimization. If you run an unpackaged build, give the hook the absolute path to tisvlay and pass the --socket and --user-socket flags explicitly, since Sunshine runs hooks outside your shell environment.

Manual control

For a remote tool TisVlay doesn’t recognize, arm and disarm it yourself:

tisvlay stream start --source myapp   # arm
tisvlay stream stop  --source myapp   # disarm
tisvlay stream status                 # show live sources + armed TVs

These talk to your user session service, so add --user-socket if you’ve moved it from the default. tisvlay watch shows the stream state live, alongside everything else.

Troubleshooting

Start with the bottom-left corner of the TisVlay window. The Daemon, Session, and Tray dots should all be filled and green. If one isn’t, that service isn’t running or can’t be used, and the sections below explain what to do. For anything you’ll report as a bug, open About and click Diagnostics to copy a report to your clipboard.

Discovery finds no TVs

If the wizard’s scan comes up empty but you know the TV is on, the cause is almost always a firewall on this computer dropping the TV’s replies. See Firewall & Discovery. In the meantime, use Don’t see your TV? Add it manually in the wizard and type the TV’s address; manual entry works without discovery. See Adding a TV.

The TV won’t power on

An HDMI-CEC TV wakes over the cable. Check that it’s on the adapter’s input and that the service can reach the adapter (Installation). TisVlay re-sends the wake for a few seconds; if the TV still reports standby, the text beside Power on says Wake failed.

A network TV wakes by Wake-on-LAN. On the TV’s page, the text beside Power on shows how far the wake got (Sending wake-up…, Connecting…, Waiting for the network…, or Wake failed). Wake-on-LAN needs all of these:

  • A MAC address stored for the TV. If Power on is disabled and says Wake-on-LAN unavailable — no MAC configured, click Re-pair under DANGER ZONE (it reads the MAC from the TV), or open CONFIG, click the pencil, and enter it yourself. See The Wake-on-LAN MAC.
  • The right MAC. A TV has separate wired and Wi-Fi MACs, and only the interface it’s connected through wakes it. If pairing reported a different MAC than the one you entered, the last wizard step offers Use reported; for an existing TV, re-pairing reports it again.
  • Wake over the network enabled on the TV. On LG, that’s “TV On via Wi-Fi” (or “Mobile TV On”) in its network settings.
  • The TV and this computer on the same network. Wake-on-LAN packets are broadcasts and don’t cross routers, unless your router relays them onto the TV’s subnet or VLAN. See TVs on an Isolated Network.

A woken TV takes several seconds to boot before it accepts commands, longer from deep standby; TisVlay keeps retrying. TVs may ignore Wake-on-LAN during a pixel-refresh cycle, or for a moment after being switched off with their remote; TisVlay keeps re-sending for a few seconds, then reports Wake failed if the TV still reads off.

From a terminal
tisvlay config <id>        # shows the stored MAC
tisvlay repair <id>        # re-pairs and reads the MAC from the TV
tisvlay config <id> '{"mac":"AA:BB:CC:DD:EE:FF"}'

Pairing prompt never appears

This applies to TVs that show an on-screen prompt (LG); HDMI-CEC has none.

  • Confirm the TV is on and reachable. A TV in standby can keep its network up while refusing control connections, so check the screen, not just a ping.
  • In the wizard, Retry pairing tries again, and Back to discovery lets you pick a different entry.
  • Newer LG firmware requires a secure (wss://) connection with a self-signed certificate. TisVlay handles this, but a proxy or TLS-inspecting firewall can interfere.
  • If a TV that was paired before now fails, use Re-pair under DANGER ZONE on its page rather than adding it again. Its credentials may have expired.

A TV shows Disconnected

The service reconnects automatically, but a network TV drops its connection when it goes to standby. Click Power on on the TV’s page to wake it, then try your action again. A TV’s Connected or Disconnected badge is at the top of its page.

From a terminal

If a command fails with “not connected”, power the TV on first (tisvlay power <id> on) and retry. tisvlay reconnect <id> asks a downed TV to redial, and tisvlay list shows each TV’s connection state.

Inspecting the daemon

In the app, the sidebar dots show whether each service is running and usable, the About page lists each component with its version, and each service’s Config button opens its configuration file in your file manager.

For logs and live state, use a terminal:

journalctl -u tisvlay-daemon -f           # daemon logs
journalctl --user -u tisvlay-session -f   # session service logs (per-user)
tisvlay list                              # every TV's current state
tisvlay watch                             # live state changes

If even tisvlay status fails, the CLI tells you which of two problems you have:

  • “You don’t have TV control access yet”: your account isn’t allowed to reach the service. The app shows an Enable TV control banner for the same problem. Click it, or run tisvlay enable-access; see Access. You’ll mostly see this over SSH or from a service account.
  • “daemon unreachable”: the service itself is down or the socket path is wrong. Check systemctl status tisvlay-daemon and journalctl -u tisvlay-daemon.

Version mismatch

The window shows a Version mismatch banner, or a command fails with a version error: a service and the app or command line talking to it come from different releases, so they refuse to talk rather than misread each other. Releases that differ only in the patch number still work together (the sidebar dot goes amber instead of red).

It usually follows an upgrade, before the running services restarted. Restart the one the message names, then reopen the window:

sudo systemctl restart tisvlay-daemon
systemctl --user restart tisvlay-session tisvlay-tray

tisvlay status then reports the daemon’s release and whether it’s compatible. If the CLI is the odd one out, the tisvlay on your PATH comes from another install.

Until the session service matches the daemon again, your user profile’s actions pause rather than act on a daemon it can’t talk to. Logging out and back in restarts it on the new release.

A hand edit didn’t take

This only applies if you edited a configuration file by hand. After clicking Reload in the sidebar (or running tisvlay reload):

  • A banner appears naming a file and a problem. The message gives the line and column of a parse error. Nothing from that file was applied: fix it and click Reload again.
  • A <file>.edited file appeared. You edited a state file, but a service wrote it before you reloaded, so your edit was moved aside rather than overwritten. Redo the edit in the current file and reload. For the window’s gui.toml, it means the file couldn’t be read when the window opened and the window started with its defaults; fix the copy set aside and put it back.

See Hand-editing and reloading.

No tray icon on GNOME

Vanilla GNOME has no system-tray host. See GUI & Tray for the extension that adds one.

Automation does nothing

Work through these in the app:

  1. The TV must be managed. On its page, the Managed switch under STATUS must be on. (The TV list shows Unmanaged in amber if it isn’t.)
  2. The Session dot must be green. Lock and idle events come from the per-user session service.
  3. Check what the TV’s profiles say. The PROFILES group on its page shows which system and user profile it uses; use the pencil to see what each event does. A system profile’s Automate when setting can limit automation to when the TV is on your PC’s input; see Which TVs react.
  4. Did you stream in remotely? Wake actions are held back on purpose while a stream has the TV armed. The tray shows Stream active; see Streaming.
  5. Is another account the active session? With fast user switching, a backgrounded session’s automation is suppressed until you’re in the foreground again.
From a terminal
tisvlay managed <id> on
tisvlay system-profile <id> --effective   # the TV's resolved system mapping
tisvlay user-profile <id> --effective     # the same for your user profile
systemctl --user status tisvlay-session

The PC sleeps or locks when the TV turns off

That’s On TV power off in the TV’s system profile (sleep) or your user profile (lock); On remote power key does the same for the TV’s “power off this device”. Set them to none, or turn the TV’s Managed switch off. The TV turning off on its own timer (an energy-saving or no-signal auto-off) counts as it turning off. See TV Controls PC.

The TV wakes the PC, or doesn’t

  • It wakes the PC when it shouldn’t: check On TV power on is none in the TV’s system profile and the TV is managed: an unmanaged TV’s adapter is left as it is.
  • It doesn’t wake the PC: check On TV power on is wake, the TV’s page shows Wakes this PC, and the TV comes on to your PC’s input. For a Pulse-Eight, its autonomous mode must be on.

Both are covered in The TV waking your PC.

Isolating tisvlay from the TV (mock backend)

When you can’t tell whether a problem is in TisVlay or in the TV or network, take the TV out of the equation. TisVlay includes a Mock (testing) vendor: an in-memory fake TV that pairs, connects, and answers every command with no hardware or network traffic.

In the app, click + on the TVs page, use Don’t see your TV? Add it manually, choose the vendor Mock (testing), enter any address such as mock://test, and finish the wizard. Then use it like a real TV: its controls, the tray submenu, and automation all work.

  • The mock works but your TV doesn’t: TisVlay itself is healthy, and the problem is on the TV or the network. Work through the sections above (Wake-on-LAN settings, firewall, pairing).
  • The mock misbehaves too: that’s a TisVlay bug. Please report it, with the Diagnostics report from the About page and the daemon logs (see Inspecting the daemon).

When you’re done, Remove TV under DANGER ZONE deletes it.

From a terminal
id=$(tisvlay pair "mock-test" --vendor mock \
    --params '{"address":"mock://test","pairing":"accept"}')
tisvlay volume "$id" 50
tisvlay volume "$id"              # reads it back: 50
tisvlay remove "$id"

Firewall & Discovery

When you click + to add a TV, the pairing wizard scans your network. Network backends find TVs by multicast discovery: TisVlay sends a search to a multicast group and listens for replies. The LG backend uses SSDP (the same protocol DLNA and UPnP use), which is what this page’s firewall rules cover. HDMI-CEC TVs aren’t discovered over the network at all; the computer’s CEC adapters are probed instead.

Discovery is a convenience. You can always add a TV by typing its address under Don’t see your TV? Add it manually, with no firewall changes; see Adding a TV.

The Linux gotcha

LG TVs reply to SSDP from an ephemeral source port rather than the standard port 1900. On Linux, a default-deny firewall therefore drops the reply (the kernel has no conntrack entry that matches it), so discovery silently finds nothing, even though the TV is reachable and you can add it by address.

Symptom: the wizard’s scan finds no network TVs (or tisvlay discover returns none), but adding the TV by address works.

Open the pinned source port

TisVlay binds its SSDP source socket to a fixed port, 1901 by default, so a single firewall rule lets the replies through instead of opening the whole ephemeral range. You don’t need to configure the port; just allow inbound UDP on it:

# Ubuntu / Debian (ufw)
sudo ufw allow 1901/udp

# Fedora (firewalld)
sudo firewall-cmd --permanent --add-port=1901/udp
sudo firewall-cmd --reload

# Generic iptables
sudo iptables -I INPUT -p udp --dport 1901 -j ACCEPT

On NixOS the module opens it for you; its bindPort already defaults to 1901:

services.tisvlay.openFirewall = true;
# or, by hand:
networking.firewall.allowedUDPPorts = [ 1901 ];

To use a different port, or an OS-chosen ephemeral one (which can’t be narrowed to a single firewall rule), pass tisvlay service daemon --ssdp-port <port> (0 = ephemeral). On NixOS, set the module’s bindPort instead.

Note: a --sport 1900 rule does not work. The TV’s reply comes from an ephemeral port, so you must match on the pinned destination port instead.

Alternative: allow UDP from your LAN

If you’d rather not pin a port, allow UDP from your local subnet:

# ufw
sudo ufw allow from 192.168.1.0/24 proto udp

# Generic iptables
sudo iptables -I INPUT -p udp -s 192.168.1.0/24 -j ACCEPT

# firewalld (rich rule)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" protocol value="udp" accept'
sudo firewall-cmd --reload

On NixOS, insert the rule into the firewall’s own chain, ahead of its final drop, and accept through its own target:

networking.firewall.extraCommands = ''
  iptables -I nixos-fw -p udp -s 192.168.1.0/24 -j nixos-fw-accept
'';

Ways to discover and add a TV

  • The pairing wizard scans live: TVs appear as they answer, and the host’s CEC adapters are listed alongside the network scan.
  • Manual entry is the fallback when discovery can’t reach the TV. Type its address; for an LG TV the wizard fills in the MAC address (used for Wake-on-LAN) from your computer’s neighbor cache and marks it with an Auto-detected badge, which clears if you edit the value. A TV on another subnet or VLAN isn’t visible to that cache; see The Wake-on-LAN MAC and, for discovery across a VLAN, TVs on an Isolated Network.
From a terminal
  • tisvlay discover runs a bounded scan; found TVs stream back as they answer.
  • tisvlay discover --watch keeps scanning, streaming findings until you press ctrl-c.
  • tisvlay pair adds a TV by address; see Adding a TV.

Networking & Reliability

Most of the time you never think about this chapter — it’s the machinery that makes power and screen automation land reliably across suspends, reboots, cable pulls, and Wi-Fi drops, without you scripting the difference. This page explains what it does, so the behavior isn’t surprising.

Network-aware connections

tisvlay tracks network state per TV, straight from the kernel (netlink) — no NetworkManager dependency:

  • Cable pulls and software disconnects are noticed in milliseconds, not on a timeout.
  • Each TV is scoped to the interface that actually routes to it. A Wi-Fi drop won’t disconnect a TV reachable over Ethernet, and vice-versa; failover between Wi-Fi and wired reconnects on its own. This holds through VLANs, VPNs, and bridges.
  • A TV that goes down stays down until asked or until its network route returns — tisvlay doesn’t hammer a TV that’s genuinely gone, but it does resume the moment the path is back.

Wake-on-LAN only when the network is up

Wake-on-LAN is a one-shot magic packet with a limited retry budget, and a boot or resume is exactly when the network is least ready — the NIC may still be negotiating. tisvlay therefore holds a wake until a route to the TV actually exists, instead of burning its retries firing into an interface that hasn’t come up yet. When the route appears, the wake resumes automatically.

The upshot: “power the TV on when I resume the machine” works even if your NIC takes several seconds to come up, or if you resume onto Wi-Fi. You don’t need to add delays or retries of your own.

Reliable screen and power actions

Screen commands are verify-and-retry, not fire-and-forget:

  • After sending screen_on/screen_off, tisvlay confirms the panel actually changed and retries if it didn’t.
  • A screen_on that finds the TV in standby escalates to a full power-on (Wake-on-LAN for network TVs, a bus wake for CEC) — capped at one wake, so a TV that reports itself dark can’t trigger an endless wake loop.
  • The desired screen state is latched, so it survives a TV reconnect or the login-screen→session handoff: if the connection drops mid-command, the reconnect re-asserts what you asked for rather than losing it.

Together these mean automation reaches the state you asked for from any starting point — TV off, TV on the wrong input, network still coming up — without per-case scripting.

When something’s off

If a wake or screen action doesn’t land, look at the text beside Power on on the TV’s page, which shows how far the wake got, and walk through Troubleshooting → The TV won’t power on. To watch connection and power state change live from a terminal, use tisvlay watch.

TVs on an Isolated Network

A smart TV is a computer that phones home: telemetry, ads, viewing data. A common answer is to put it on its own network — an IoT VLAN that the router keeps away from the internet and from the rest of your LAN. tisvlay works fine with that setup. It controls the TV entirely over your local network and never needs the TV to reach the internet. But a VLAN boundary breaks the parts of tisvlay that rely on broadcast and multicast, so this page covers what to set up so they keep working.

This applies to TVs controlled over the network. A TV driven over HDMI-CEC talks to tisvlay through the HDMI cable, so which network it’s on doesn’t matter.

What crosses the boundary, and what doesn’t

FeatureTrafficCrosses a router?What to do
Control (power, screen, input, …)A unicast connection from tisvlay to the TVYes, it’s ordinary routed trafficAllow it in the router’s firewall
DiscoveryMulticast search and repliesNoReflect it, or add the TV by IP address
Wake from standbyWake-on-LAN broadcastNoReflect it
MAC auto-fill when you type an IPThe host’s ARP cacheNo — it only ever sees the routerRead from the TV at pairing, where the backend supports it; otherwise enter it by hand

The two “reflect it” rows are the ones that need help. A reflector on the router copies those specific broadcast and multicast packets from one network to the other, without opening anything else up.

What each backend uses

The protocols and ports behind those rows depend on the TV’s backend:

BackendControlDiscoveryWakeMAC read from the TV
LG webOSTCP 3001 (TLS); 3000 on older firmwareSSDPWake-on-LANYes
HDMI-CECHDMI cableProbes the local CEC adaptersOver the HDMI cableNot needed

Router firewall rules

Your router needs to allow the tisvlay host to open connections to the TV, while still keeping the TV from reaching the internet or opening connections back into your LAN:

  • Allow new connections from the tisvlay host (or your LAN) to the TV on its backend’s control port (see the table above), plus the return traffic of those connections (established/related).
  • Block the IoT network from the internet, and from opening new connections into the LAN. That’s the point of the VLAN, and tisvlay doesn’t need either.

Pairing and every control command are local: the TV never needs internet access for tisvlay to work. (It does for firmware updates and streaming apps — that trade-off is yours.)

Reflecting discovery and wake with netflector

netflector is an open-source reflector built for exactly this split: a wired LAN on one side, a Wi-Fi or IoT VLAN on the other. It runs on the router itself or on any machine attached to both networks. It’s available for Linux, macOS, and FreeBSD, as a multi-arch Docker image (which also runs as a MikroTik RouterOS container), and as an OPNsense plugin. Its README covers installation and the privileges it needs. It reflects SSDP, mDNS, WS-Discovery and Wake-on-LAN, and can relay other UDP discovery protocols unchanged.

Enable wol for wake, plus the discovery protocol your TVs’ backends use (the table above). For LG TVs:

[reflectors.lan_to_iot]
source_if = "br0"   # the network your tisvlay host is on
target_if = "br2"   # the IoT VLAN your TV is on
ssdp = true         # discovery (LG): searches go LAN → IoT, the TV's replies come back
wol  = true         # wake: magic packets go LAN → IoT
# macs = ["84:90:0A:63:A9:94"]   # optional: limit the entry to your TV(s)
  • Direction matters. source_if is the side that searches and wakes (your desktop’s network); target_if is the TV’s. netflector relays searches and wakes source → target, and proxies the TV’s replies back.
  • Interface names come from the router: run ip -br addr on it and match each network’s subnet to its interface. A UniFi gateway, for example, names its default LAN br0 and each additional network a br<N> bridge. Name the per-network interface, not a VLAN trunk — netflector ignores VLAN-tagged frames.
  • macs is optional. It narrows the entry to specific devices; use the MAC of the interface the TV is actually connected through (wired or Wi-Fi) — the one stored for the TV, which you can see under CONFIG on the TV’s page (or with tisvlay config <id>).
  • Other protocols aren’t used by tisvlay unless a backend discovers with them. Enable mdns and dial too if you want phones on the LAN to cast to the TV.

Many routers ship an mDNS reflector. It only helps backends that discover over mDNS — LG uses SSDP — and it won’t relay Wake-on-LAN.

On the tisvlay side

With the reflector and firewall rules in place, the TV behaves almost like one on your own network:

  • Discovery finds the TV at its real address on the IoT VLAN. The TV’s reply reaches your host through the reflector, so your host firewall rule for discovery replies applies unchanged (for SSDP, the pinned source port).
  • The MAC won’t be auto-filled when you enter the TV’s IP by hand (the wizard shows no Auto-detected badge), because the host’s ARP cache only sees the router. If the backend can read it from the TV (LG can — see Adding a TV → The Wake-on-LAN MAC), leave the field blank; otherwise enter it by hand — it’s in the TV’s network settings.
  • Wake-on-LAN works across the boundary. Expect the TV to answer a few seconds after the wake, a little later than one on your own network — tisvlay keeps retrying the connection while it boots, so nothing needs tuning. A TV on Wi-Fi still needs its wake-over-Wi-Fi setting turned on (on LG, “TV On via Wi-Fi”).
  • Network awareness follows the route through the router, so cable pulls and Wi-Fi drops on your side are still noticed straight away (see Networking & Reliability).

Without a reflector

If you can’t run a reflector, tisvlay still works, with two limits:

  • Add the TV by address (in the wizard, use Don’t see your TV? Add it manually), since discovery can’t see it.
  • tisvlay can’t wake a TV from standby. Once the TV is powered off, only its remote (or an HDMI-CEC adapter, if you have one) can bring it back. So on the Profiles page, change any event that powers the TV off to screen_off, keeping the TV reachable for the next session; the stock defaults show which events do.

Command Line

Everything in the TisVlay app is also available from a terminal with the tisvlay command, which is handy for scripts, SSH sessions, and automation tools. You never need it for ordinary use.

tisvlay is a pure client of the TisVlay service: every command below is a one-shot request to the service’s control socket. Most commands that act on a TV take its UUID as the first argument; get it from tisvlay list. The global options (socket paths, --json) are listed under tisvlay below.

Which command does what the app does

In the appOn the command line
TVs page: list and statetisvlay list
+ (add a TV)tisvlay discover, then tisvlay pair
Power on / offtisvlay power <id> on|off
Screen switchtisvlay screen <id> on|off
Backlight, Volume, Mutetisvlay backlight, tisvlay volume, tisvlay mute
Current input / Primary inputtisvlay input, tisvlay primary-input
Managed switchtisvlay managed <id> on|off
PROFILES dropdownstisvlay system-profile, tisvlay user-profile
Profiles pagetisvlay profile system …, tisvlay profile user …
CONFIG (edit a TV)tisvlay config
Re-pair / Remove TVtisvlay repair, tisvlay remove
Sidebar Reloadtisvlay reload
Enable TV control bannertisvlay enable-access
Sidebar status dots, Abouttisvlay status

Seeing what’s going on

  • tisvlay list shows every TV and its current state (connection, power, input, …).
  • tisvlay status checks that the service is up and reports its release.
  • tisvlay watch streams every state change (history first, then live). It’s the go-to for scripting and for watching automation happen.

Reading and changing a TV

Every per-TV setting and state works the same way: give a value to change it, leave the value out to read it.

tisvlay volume <id> 30        # set the volume
tisvlay volume <id>           # print it: 30
tisvlay mute <id> --toggle    # flip mute, printing what was sent: on
  • A read prints what you’d type to set it (on/off, a number, an input or profile id), so its output can be fed straight back in. With --json it prints the bare JSON value.
  • On/off settings accept on/off (or true/false, yes/no, 1/0) and take --toggle.
  • Settings that can be unset (the primary input, the assigned profiles) take --clear. A TV with no assigned profile reads as the default profile’s id; an unset primary input prints nothing.
  • Commands that change something print nothing when they succeed. A failure exits non-zero with the reason on stderr.
  • A reader that stops early, as in tisvlay watch | head, ends the command quietly with status 0.
  • power reads on only when the TV is connected and awake, so a TV switched off with its remote reads off, and power --toggle wakes it. A TV whose screen alone is off reads on, and power on leaves it so; screen on lights it.
  • Values the TV reports, such as volume or the current input, come from its last report. If the TV is disconnected the command still prints the value, with a warning on stderr that it may be stale; if the TV hasn’t reported it since the service started, the command fails.
  • Two --toggles in quick succession can both see the old value, so a hotkey can miss a very fast double press.
  • In a script, quote a read you pass on to another command: an empty result would otherwise turn the second command into a read.

Scripting

Read commands take --json for machine-readable output, and tisvlay watch streams every change without polling, so a script can react to state instead of polling for it. The app and the tray use the same interface, so anything they show is scriptable.

Tab completion

Tab completion ships for bash, zsh, and fish (installed by the packages; see Installation). It’s live: <TAB> completes not just commands and flags but TV UUIDs, profile UUIDs, and vendor ids, queried from the running service and annotated with their names, so you never type a UUID by hand.

Command reference

The full command tree below is generated from the tisvlay source, so it always matches --help.

Command Overview:

tisvlay

TisVlay TV control

Usage: tisvlay [OPTIONS] <COMMAND>

A TV setting or state reads when its value is left out and sets when it’s given: tisvlay volume <TV> prints the volume, tisvlay volume <TV> 30 sets it. On/off settings take --toggle, and settings that can be unset take --clear. Commands that change something print nothing on success.

Subcommands:
  • enable-access — Grant the current user TV control by adding it to the daemon’s socket group (one-click, authenticated via polkit). Log out and back in afterward for it to take effect
  • status — Probe the daemon’s liveliness token and report its release; fails when the daemon is absent or runs a release this CLI can’t talk to
  • reload — Apply hand edits of the state files: the daemon’s, the session’s and the tray’s (what the GUI’s Reload button does)
  • list — List TVs with their current state (hydrates from publisher caches)
  • watch — Stream every data key: history first, then live samples
  • discover — Scan for TVs: a bounded window, or with --watch until ctrl-c
  • pair — Pair a new TV, streaming the pairing progress (ctrl-c cancels)
  • repair — Re-run the pairing handshake for an existing TV
  • reconnect — Ask a downed TV to reconnect
  • remove — Remove a TV (deletes all its keys)
  • vendor — List the vendor backends, or show one’s capabilities and pairing fields
  • power — Power a TV on or off, or print whether it’s on (connected and awake)
  • screen — Blank or wake a TV’s screen, or print whether it shows a picture
  • mute — Mute or unmute a TV, or print whether it’s muted
  • managed — Set whether a TV’s profiles act (on the TV, and on this PC for its events), or print it
  • volume — Set a TV’s volume, or print it
  • backlight — Set a TV’s backlight level, or print it
  • input — Switch a TV’s input, or print the current one
  • primary-input — Set a TV’s primary input (the one automation restores and compares against), or print it
  • name — Rename a TV, or print its name
  • config — Patch a TV’s config, or print it (vendor secrets redacted)
  • system-profile — Assign a TV’s system profile, or print the assigned one
  • user-profile — Assign a TV’s user profile (via –user-socket), or print the assigned one
  • profile — The profile catalogs: system profiles hold what TVs do on power events, user profiles what they do on lock, idle and streams
  • stream — Arm or query a stream source (session-service side, via –user-socket). start/stop are the Sunshine global_prep_cmd integration (do = start, undo = stop)
  • service — Run one of TisVlay’s services in the foreground. Their systemd units start them; run one by hand to debug it or try its flags
Options:
  • --socket <SOCKET> — Daemon socket (default: $XDG_RUNTIME_DIR/tisvlay/zenoh.sock, falling back to the packaged /run/tisvlay/zenoh.sock)

  • --user-socket <USER_SOCKET> — Session-service user socket for the profile user, user-profile and stream verbs (default: $XDG_RUNTIME_DIR/tisvlay/session.sock)

  • --site <SITE> — Site chunk used in all key expressions; leave it unless you run more than one daemon

    Default value: main

  • --json — Machine-readable JSON output on read verbs

tisvlay enable-access

Grant the current user TV control by adding it to the daemon’s socket group (one-click, authenticated via polkit). Log out and back in afterward for it to take effect

Usage: tisvlay enable-access

tisvlay status

Probe the daemon’s liveliness token and report its release; fails when the daemon is absent or runs a release this CLI can’t talk to

Usage: tisvlay status

tisvlay reload

Apply hand edits of the state files: the daemon’s, the session’s and the tray’s (what the GUI’s Reload button does)

Usage: tisvlay reload

tisvlay list

List TVs with their current state (hydrates from publisher caches)

Usage: tisvlay list

tisvlay watch

Stream every data key: history first, then live samples

Usage: tisvlay watch

tisvlay discover

Scan for TVs: a bounded window, or with --watch until ctrl-c

Usage: tisvlay discover [OPTIONS]

Options:
  • --vendor <VENDOR>
  • --timeout <TIMEOUT> — How long to scan, e.g. 3s or 500ms (default: the daemon’s window; it scans for at most its own cap)
  • --watch — Keep scanning, streaming findings until ctrl-c

tisvlay pair

Pair a new TV, streaming the pairing progress (ctrl-c cancels)

Usage: tisvlay pair [OPTIONS] --params <PARAMS> <NAME>

Arguments:
  • <NAME>
Options:
  • --vendor <VENDOR>

    Default value: lg

  • --params <PARAMS> — Vendor pairing params as JSON, e.g. ‘{“ip”:“192.168.1.50”}’

  • --unmanaged — Enroll the TV unmanaged: the daemon won’t automate it

tisvlay repair

Re-run the pairing handshake for an existing TV

Usage: tisvlay repair <TV>

Arguments:
  • <TV>

tisvlay reconnect

Ask a downed TV to reconnect

Usage: tisvlay reconnect <TV>

Arguments:
  • <TV>

tisvlay remove

Remove a TV (deletes all its keys)

Usage: tisvlay remove <TV>

Arguments:
  • <TV>

tisvlay vendor

List the vendor backends, or show one’s capabilities and pairing fields

Usage: tisvlay vendor [VENDOR]

Arguments:
  • <VENDOR>

tisvlay power

Power a TV on or off, or print whether it’s on (connected and awake)

Usage: tisvlay power [OPTIONS] <TV> [VALUE]

Arguments:
  • <TV>

  • <VALUE> — Turn it on or off; leave it out to print the current value

    Possible values: on, off

Options:
  • --toggle — Flip the current value, printing the one sent

tisvlay screen

Blank or wake a TV’s screen, or print whether it shows a picture

Usage: tisvlay screen [OPTIONS] <TV> [VALUE]

Arguments:
  • <TV>

  • <VALUE> — Turn it on or off; leave it out to print the current value

    Possible values: on, off

Options:
  • --toggle — Flip the current value, printing the one sent

tisvlay mute

Mute or unmute a TV, or print whether it’s muted

Usage: tisvlay mute [OPTIONS] <TV> [VALUE]

Arguments:
  • <TV>

  • <VALUE> — Turn it on or off; leave it out to print the current value

    Possible values: on, off

Options:
  • --toggle — Flip the current value, printing the one sent

tisvlay managed

Set whether a TV’s profiles act (on the TV, and on this PC for its events), or print it

Usage: tisvlay managed [OPTIONS] <TV> [VALUE]

Arguments:
  • <TV>

  • <VALUE> — Turn it on or off; leave it out to print the current value

    Possible values: on, off

Options:
  • --toggle — Flip the current value, printing the one sent

tisvlay volume

Set a TV’s volume, or print it

Usage: tisvlay volume <TV> [LEVEL]

Arguments:
  • <TV>
  • <LEVEL> — 0–100; leave it out to print the current level

tisvlay backlight

Set a TV’s backlight level, or print it

Usage: tisvlay backlight <TV> [LEVEL]

Arguments:
  • <TV>
  • <LEVEL> — 0–100; leave it out to print the current level

tisvlay input

Switch a TV’s input, or print the current one

Usage: tisvlay input <TV> [INPUT]

Arguments:
  • <TV>
  • <INPUT> — The input ID; leave it out to print the current one

tisvlay primary-input

Set a TV’s primary input (the one automation restores and compares against), or print it

Usage: tisvlay primary-input [OPTIONS] <TV> [INPUT]

Arguments:
  • <TV>
  • <INPUT> — The input ID; leave it out to print the primary one
Options:
  • --clear — Unset the primary input

tisvlay name

Rename a TV, or print its name

Usage: tisvlay name <TV> [NAME]

Arguments:
  • <TV>
  • <NAME> — The new name; leave it out to print the current one

tisvlay config

Patch a TV’s config, or print it (vendor secrets redacted)

Usage: tisvlay config <TV> [PATCH]

Arguments:
  • <TV>
  • <PATCH> — A JSON object merged into the config (null unsets a vendor key); leave it out to print the config

tisvlay system-profile

Assign a TV’s system profile, or print the assigned one

Usage: tisvlay system-profile [OPTIONS] <TV> [PROFILE]

Arguments:
  • <TV>
  • <PROFILE> — The profile to assign; leave it out to print the assigned one (the default profile’s UUID when none is)
Options:
  • --clear — Unassign, so the TV follows the default profile
  • --effective — Print the TV’s resolved profile: the assigned one over the stock defaults

tisvlay user-profile

Assign a TV’s user profile (via –user-socket), or print the assigned one

Usage: tisvlay user-profile [OPTIONS] <TV> [PROFILE]

Arguments:
  • <TV>
  • <PROFILE> — The profile to assign; leave it out to print the assigned one (the default profile’s UUID when none is)
Options:
  • --clear — Unassign, so the TV follows the default profile
  • --effective — Print the TV’s resolved profile: the assigned one over the stock defaults

tisvlay profile

The profile catalogs: system profiles hold what TVs do on power events, user profiles what they do on lock, idle and streams

Usage: tisvlay profile <COMMAND>

Subcommands:
  • system — System profiles (daemon-side)
  • user — User profiles (session-service side, via –user-socket)

tisvlay profile system

System profiles (daemon-side)

Usage: tisvlay profile system <COMMAND>

Subcommands:
  • list — List profile bodies from the catalog caches
  • create — Create a profile, printing its UUID: --body is a SystemProfileConfig body
  • update — Replace a profile’s body (full JSON body; name required inside)
  • duplicate — Copy a profile under a new name, printing the copy’s UUID
  • delete — Delete a profile (assignments referring to it fall back to default)
  • defaults — The stock mapping unset fields inherit

tisvlay profile system list

List profile bodies from the catalog caches

Usage: tisvlay profile system list

tisvlay profile system create

Create a profile, printing its UUID: --body is a SystemProfileConfig body

Usage: tisvlay profile system create [OPTIONS] <NAME>

Arguments:
  • <NAME>
Options:
  • --body <BODY>

tisvlay profile system update

Replace a profile’s body (full JSON body; name required inside)

Usage: tisvlay profile system update <ID> <BODY>

Arguments:
  • <ID>
  • <BODY>

tisvlay profile system duplicate

Copy a profile under a new name, printing the copy’s UUID

Usage: tisvlay profile system duplicate <ID> <NAME>

Arguments:
  • <ID>
  • <NAME>

tisvlay profile system delete

Delete a profile (assignments referring to it fall back to default)

Usage: tisvlay profile system delete <ID>

Arguments:
  • <ID>

tisvlay profile system defaults

The stock mapping unset fields inherit

Usage: tisvlay profile system defaults

tisvlay profile user

User profiles (session-service side, via –user-socket)

Usage: tisvlay profile user <COMMAND>

Subcommands:
  • list — List profile bodies from the catalog caches
  • create — Create a profile, printing its UUID: --body is a UserProfileConfig body
  • update — Replace a profile’s body (full JSON body; name required inside)
  • duplicate — Copy a profile under a new name, printing the copy’s UUID
  • delete — Delete a profile (assignments referring to it fall back to default)
  • defaults — The stock mapping unset fields inherit

tisvlay profile user list

List profile bodies from the catalog caches

Usage: tisvlay profile user list

tisvlay profile user create

Create a profile, printing its UUID: --body is a UserProfileConfig body

Usage: tisvlay profile user create [OPTIONS] <NAME>

Arguments:
  • <NAME>
Options:
  • --body <BODY>

tisvlay profile user update

Replace a profile’s body (full JSON body; name required inside)

Usage: tisvlay profile user update <ID> <BODY>

Arguments:
  • <ID>
  • <BODY>

tisvlay profile user duplicate

Copy a profile under a new name, printing the copy’s UUID

Usage: tisvlay profile user duplicate <ID> <NAME>

Arguments:
  • <ID>
  • <NAME>

tisvlay profile user delete

Delete a profile (assignments referring to it fall back to default)

Usage: tisvlay profile user delete <ID>

Arguments:
  • <ID>

tisvlay profile user defaults

The stock mapping unset fields inherit

Usage: tisvlay profile user defaults

tisvlay stream

Arm or query a stream source (session-service side, via –user-socket). start/stop are the Sunshine global_prep_cmd integration (do = start, undo = stop)

Usage: tisvlay stream <COMMAND>

Subcommands:
  • start — Arm the named source
  • stop — Disarm the named source
  • status — Show the current stream fact (live sources + armed TVs)

tisvlay stream start

Arm the named source

Usage: tisvlay stream start [OPTIONS]

Options:
  • --source <SOURCE>

    Default value: manual

tisvlay stream stop

Disarm the named source

Usage: tisvlay stream stop [OPTIONS]

Options:
  • --source <SOURCE>

    Default value: manual

tisvlay stream status

Show the current stream fact (live sources + armed TVs)

Usage: tisvlay stream status

tisvlay service

Run one of TisVlay’s services in the foreground. Their systemd units start them; run one by hand to debug it or try its flags

Usage: tisvlay service <COMMAND>

Subcommands:
  • daemon — The system daemon: owns the TVs and the system socket
  • session — The per-user session service: user profiles, lock, idle and stream handling, on the user socket
  • tray — The system tray
  • greeter — The login-screen idle client, run inside the greeter session

tisvlay service daemon

The system daemon: owns the TVs and the system socket

Usage: tisvlay service daemon [OPTIONS]

Options:
  • --socket <SOCKET> — Unix socket to listen on (default: $XDG_RUNTIME_DIR/tisvlay/zenoh.sock)

  • --site <SITE> — Site chunk used in all key expressions

    Default value: main

  • --config <CONFIG> — The daemon’s own file — persisted TV table with credentials (default: $XDG_CONFIG_HOME/tisvlay/daemon.toml); –ephemeral disables

  • --ephemeral — Run without persistence (state lives only in the world)

  • --seed <SEED> — Seed config (TOML): declaratively provisioned TVs, re-applied each start; ids already in the daemon’s own file win

  • --dpms-connector <DPMS_CONNECTOR> — Watch this DRM connector’s DPMS state in addition to the EDID-matched ones the daemon watches automatically; its transitions dispatch to every automated TV — the override for setups where the EDID match fails (e.g. card1-HDMI-A-1)

  • --ssdp-port <SSDP_PORT> — SSDP source port for LG discovery — replies return to this port, so it must match your firewall rule (see the user guide’s Firewall & Discovery page); 0 = ephemeral

    Default value: 1901

  • --greeter-access — Grant the login-screen greeter socket access by applying a POSIX ACL for each known display-manager greeter group present (gdm, Debian-gdm, plasmalogin). The modern GDM greeter is a dynamic-userdb account that /etc/group membership can’t reach

  • --greeter-group <GREETER_GROUP> — Explicit greeter group for the socket ACL, overriding –greeter-access — for a DM whose greeter group isn’t in the known set

  • --seat-access <SEAT_ACCESS> — Grant each seat’s active graphical user socket access via a POSIX ACL while their session is the seat foreground (uaccess-style presence grant; the tisvlay group remains the durable grant). --seat-access=false restricts the socket to the group + greeter

    Default value: true

    Possible values: true, false

tisvlay service session

The per-user session service: user profiles, lock, idle and stream handling, on the user socket

Usage: tisvlay service session [OPTIONS]

Options:
  • --system-socket <SYSTEM_SOCKET> — System daemon socket (default: $XDG_RUNTIME_DIR/tisvlay/zenoh.sock)

  • --user-socket <USER_SOCKET> — Per-user socket to listen on (default: $XDG_RUNTIME_DIR/tisvlay/ session.sock)

  • --site <SITE> — Site chunk used in all key expressions

    Default value: main

  • --config <CONFIG> — The session service’s own file — persisted user profiles + assignments (default: $XDG_CONFIG_HOME/tisvlay/session.toml); –ephemeral disables

  • --ephemeral — Run without persistence

  • --sunshine-port <SUNSHINE_PORT> — Sunshine’s base port on loopback, where its unauthenticated /serverinfo answers. Set it when Sunshine runs on a non-default base port (e.g. moved aside so Moonshine keeps the defaults)

    Default value: 47989

tisvlay service tray

The system tray

Usage: tisvlay service tray [OPTIONS]

Options:
  • --socket <SOCKET> — System daemon socket (default: $XDG_RUNTIME_DIR/tisvlay/zenoh.sock)

  • --user-socket <USER_SOCKET> — Session-service user socket (default: $XDG_RUNTIME_DIR/tisvlay/ session.sock). Missing socket just hides user profiles

  • --site <SITE> — Site chunk used in all key expressions

    Default value: main

  • --config <CONFIG> — The tray’s own file — notification settings (default: $XDG_CONFIG_HOME/tisvlay/tray.toml)

tisvlay service greeter

The login-screen idle client, run inside the greeter session

Usage: tisvlay service greeter [OPTIONS]

Options:
  • --socket <SOCKET> — System daemon socket (default: $XDG_RUNTIME_DIR/tisvlay/zenoh.sock)

  • --site <SITE> — Site chunk used in all key expressions

    Default value: main

Configuration Files

Most setup happens in the app: you add TVs with the pairing wizard and edit profiles on the Profiles page, and TisVlay saves the result for you. This page is for the cases where you want the files themselves: a reproducible or headless setup, editing by hand, or looking up the exact schema.

TisVlay is configured with TOML, spread across a few files:

FileOwnerPurpose
/var/lib/tisvlay/daemon.tomlthe daemon (tisvlay user)TV definitions, discovery, and system profiles, the daemon’s own persisted store
~/.config/tisvlay/session.tomleach desktop useruser profiles and per-user TV profile assignments
~/.config/tisvlay/tray.tomleach desktop usertray notification preferences
~/.config/tisvlay/gui.tomleach desktop userthe window’s own preferences: couch mode and its scale

Each service rewrites its own file as state changes, so changes you make in the app, the tray, or the command line are persisted without any file editing. You can also edit a file by hand and reload it. The window’s gui.toml is the exception: it’s read when the window opens and written by the Settings page, and a reload leaves it alone. If it can’t be read when the window opens, it’s moved aside to gui.toml.edited and the window starts with its defaults; a scale outside the Scale control’s range is brought back into it.

To declare TVs declaratively (for a reproducible or headless setup), give the daemon a seed file with tisvlay service daemon --seed <file>; on NixOS, the module’s settings option writes it for you. The seed is re-applied on every start, but only for TVs whose id is absent from daemon.toml, so runtime edits stick, and a still-declared TV that you removed at runtime comes back (drop the seed entry to drop it). Omit an entry’s id and it is derived deterministically from the entry’s content.

TV definitions

Each TV is a [[tv]] entry with a [tv.config] vendor block:

version = 1

[[tv]]
id = "9a3f1b22-c4d8-4e90-b21f-7f1a2c8e5b40"   # assigned at pair time (optional in a seed file)
name = "Office"
vendor = "lg"
primary_input = "HDMI_1"   # the input this PC uses (for input-aware automation)
managed = true             # participate in automation

[tv.profile]
system = "…"               # assigned system profile, by id (see `tisvlay profile system list`)

[tv.config]
ip = "192.168.1.170"
mac = "84:90:0a:63:a9:94"  # Wake-on-LAN; read from the TV at pair time if left out
# LG also stores, set automatically during pairing:
#   client_key = "…"   scheme = "wss"

A CEC TV uses the same shape with CEC keys instead:

[[tv]]
name = "Living Room"
vendor = "cec"

[tv.config]
device = "/dev/cec0"            # the kernel CEC node the TV is reachable on
physical_address = "1.0.0.0"    # the adapter's HDMI port (USB adapters only; ignored on GPU DP-CEC)
emulate_screen_via_power = true # honor screen_off/screen_on by powering the TV (CEC can't blank a live panel)
advertise_osd_name = true       # answer the TV's name poll, so it labels this PC's HDMI input
osd_name = "TisVlay"            # the label the TV shows for the input (14 characters max)

In a seed file the vendor block can also be written inline: config = { ip = "192.168.1.170", mac = "84:90:0a:63:a9:94" }.

With emulate_screen_via_power (default true), the daemon fulfills screen_off/screen_on on a CEC TV by powering it off/on, since CEC can’t blank a panel that stays on; set it false to have the TV ignore screen actions instead.

With advertise_osd_name (default true), tisvlay answers the TV’s name poll on the CEC bus, so the TV labels the PC’s HDMI input osd_name in its input picker, the way a Chromecast names its own input. Set it false to stay silent and keep the TV’s generic input label. If the input reads something else, such as “LG DVD Player”, the TV recorded another identity while TisVlay wasn’t driving the adapter (a Pulse-Eight can answer the TV on its own, for example after its USB cable was unplugged or the PC was switched off), and keeps it until it re-detects the device: unplugging and replugging the HDMI cable at the adapter, with its USB cable left in, clears it.

Discovery

Discovery has no section in daemon.toml. The SSDP source port is a daemon flag, --ssdp-port; Firewall & Discovery explains it and its firewall rule.

Profiles

A profile maps events to actions. There are two kinds: system profiles (in daemon.toml) for machine lifecycle and the login screen, and user profiles (in session.toml) for your desktop session. What each event means, how profiles inherit from one another, and which TVs they act on is explained in Automation; this section is the file schema.

Any field may be omitted from a profile, in which case it inherits (see How profiles resolve). Setting an event replaces its whole value, so re-gating an inherited action means restating the action too (the app does this for you).

Action vocabulary

Every event is one of five actions: none, screen_off, screen_on, power_off, and power_on; the Automation page says what each does. In a file, an event value is either the bare action token, or a table that adds modifiers:

on_session_unlock = { action = "screen_on", restore_input = true, while_streaming = false }
  • restore_input (default false): after an on-ish action completes, switch the TV to its primary_input. Needs a backend that can switch inputs (LG; not CEC).
  • while_streaming (default false): true lets the action fire even while a stream has the TV armed; see Streaming. The stock defaults below set it on the blanks and power-offs, so overriding one of those with a bare action drops it; write the table form to keep it.

System profile

Every field with its stock default (generated from the code, so it’s always current):

# Action on system startup/boot.
on_startup = { action = "power_on", restore_input = true }

# Action when the system powers off or halts.
on_shutdown = { action = "power_off", while_streaming = true }

# Action when the system reboots (including kexec).
on_reboot = { action = "none", while_streaming = true }

# Action on system sleep/suspend.
on_sleep = { action = "power_off", while_streaming = true }

# Action on system wake/resume.
on_wake = { action = "power_on", restore_input = true }

# Action when the matched host display connector blanks (DPMS off).
on_dpms_off = { action = "screen_off", while_streaming = true }

# Action when the matched host display connector wakes (DPMS on).
on_dpms_on = "screen_on"

# Greeter idle timeout in seconds (0 to disable). Drives the
# `tisvlay-greeter` idle client at the login screen.
idle_timeout_seconds = 0

# Action when the greeter goes idle (after `idle_timeout_seconds`).
on_idle_timeout = { action = "screen_off", while_streaming = true }

# Action when greeter activity resumes after idle.
on_idle_resume = "screen_on"

# When to apply automation relative to the TV's current input.
automate_when = "always"

# What the machine does when the TV powers off by itself or from its
# own remote: `suspend` suspends to RAM, `sleep` uses the host's
# configured sleep (which may hibernate). TisVlay's own power-offs
# never count, and nothing happens while a stream arms the TV.
# Default `none`.
on_tv_power_off = "none"

# What the machine does when the TV's remote powers this PC off (on
# HDMI-CEC, the TV's "power off this device"): `suspend` suspends to
# RAM, `sleep` uses the host's configured sleep. For a managed TV,
# whatever its input: the press is addressed to this PC. A toggling
# key counts only while the TV is showing this PC, since a TV
# switching to it may send one to wake it. Nothing happens while a
# stream arms the TV. Default `none`.
on_remote_power = "none"

# Whether the TV remote's keys reach this PC's desktop (on HDMI-CEC,
# as the kernel's remote-control input device). What each key does is
# the desktop's to decide. The packages' keymap keeps the remote's
# power keys away from the desktop; without it (a source build), the
# power key can put the PC to sleep. Default `false`.
remote_keys = false

# What the TV powering on does while this PC sleeps: `wake` lets it
# wake the PC (on HDMI-CEC, through a USB adapter allowed to wake the
# system, armed for every sleep), and `none` keeps the PC asleep (on
# HDMI-CEC, the adapter's USB port is disabled for the sleep, so the
# TV has no CEC device to show for this PC until it resumes). Where
# the TV comes up on another input, `wake` may still wake the PC. An
# unmanaged TV's adapter is left as the host has it. Default `none`.
on_tv_power_on = "none"

automate_when gates automation on the TV’s current input; see Which TVs react.

User profile

# Action on session lock event.
on_session_lock = { action = "screen_off", while_streaming = true }

# Action on session unlock event.
on_session_unlock = { action = "screen_on", restore_input = true }

# Action when user interacts with the lock screen greeter (before unlocking).
on_lock_wake = "screen_on"

# Idle timeout in seconds while the screen is locked (0 to
# disable) — how long a bumped-but-not-unlocked lock screen stays
# awake before `on_idle_timeout` re-blanks it.
locked_idle_timeout_seconds = 0

# Idle timeout in seconds while unlocked (0 to disable) — the
# in-session idle threshold for `on_idle_timeout`/`on_idle_resume`.
unlocked_idle_timeout_seconds = 0

# Action on idle timeout (either state's timer; each timer only
# fires in its own lock state).
on_idle_timeout = { action = "screen_off", while_streaming = true }

# Action when activity resumes after an unlocked idle timeout.
# Activity while locked dispatches `on_lock_wake` instead.
on_idle_resume = "screen_on"

# Action when a stream starts.
on_stream_start = "screen_off"

# Action when the last stream ends.
on_stream_end = "screen_off"

# Action on the first local input after a stream ends —
# wakes the panel that the disconnect blank left dark for the
# returning person.
on_local_return = { action = "screen_on", restore_input = true }

# Whether an unattributed screen-capture signal arms a stream.
# Attributed sources (streaming apps, hooks, logind) always
# arm; this decides only the bare screencast case (OBS, browser
# tab-share). Default off.
screencast_arms = false

# What the desktop does when the TV powers off by itself or from its
# own remote: `lock` locks this session. TisVlay's own power-offs
# never count, and nothing happens while a stream arms the TV.
# Default `none`.
on_tv_power_off = "none"

# What the desktop does when the TV's remote powers this PC off (on
# HDMI-CEC, the TV's "power off this device"): `lock` locks this
# session. For a managed TV, whatever its input: the press is
# addressed to this PC. A toggling key counts only while the TV is
# showing this PC, since a TV switching to it may send one to wake it.
# Nothing happens while a stream arms the TV. Default `none`.
on_remote_power = "none"

The two idle timers set how long to wait in each lock state; the shared on_idle_timeout/on_idle_resume actions decide what happens. A 0 timer disables idle for that lock state.

Hand-editing and reloading

Any of the three service files can be edited by hand while the services run. Then apply the edit with one of:

  • tisvlay reload, which reloads the daemon’s, the session’s and the tray’s files, one line per service (a service that isn’t running is reported, not failed).
  • The app’s Reload button, in the sidebar, which does the same for all three files.
  • systemctl reload tisvlay-daemon, which reloads the daemon’s file only. systemctl reports success as soon as the signal is sent, so the outcome is in the daemon’s journal.

How a reload treats your edit:

  • Only the edit applies. The service compares the file with what it last wrote and applies just the difference, so changes made through the CLI or GUI but not yet written survive. Where both changed the same field, the file wins.
  • A bad edit is refused whole, for example a missing or unparsable file, a repeated id, a changed or unknown vendor, renaming or removing the default profile, or deleting a profile a TV still uses. A parse error points at its file, line and column. A TV in the middle of pairing is left as it is.
  • Comments survive. The services’ own writes keep your comments and layout wherever the values beside them didn’t change.
  • Reload before the service writes again. An edit nobody reloaded is moved aside to <file>.edited (one slot, readable only by its owner) with a warning, never silently overwritten; redo the edit in the current file and reload.

Requirements & Compatibility

Supported TVs

BackendStatus
LG webOS (network)Supported
HDMI-CECSupported — power + screen-blank on any CEC-capable TV; needs a CEC adapter (Installation)
Samsung, Sony, …Not yet — the architecture is vendor- and transport-agnostic and open to new backends

LG control works over IPv4 or IPv6; its SSDP discovery and Wake-on-LAN are IPv4-only (the TV doesn’t answer v6 multicast, and WoL is an L2 IPv4 broadcast).

Host requirements

  • Linux — the services talk to each other over local sockets, and use D-Bus to integrate with the system and desktop. The daemon watches logind on the system bus for sleep, shutdown and the active seat. The session and tray services and the app use logind (lock state and remote sessions), GNOME’s idle monitor where present, the desktop portals (screen-capture detection, and the app’s accent color), the system tray and notifications, so they expect a normal desktop bus.
  • Socket access — automatic for the user at the console; everyone else needs the tisvlay group (the app offers an Enable TV control button if you need it). See Access.
  • Network reachability (network backends) — the host can reach the TV; waking it from standby also needs the same L2 network or a relay. See Troubleshooting → The TV won’t power on.
  • Idle & lock-wake detection (optional; powers the in-session idle timer and unblanking the TV when you touch the lock screen) — a Wayland compositor with ext-idle-notify-v1 (KDE Plasma, wlroots compositors) or GNOME (detected through Mutter’s own idle monitor).
  • For the DPMS display-blank automation, the host must expose the matched connector’s power state via /sys/class/drm/*/dpms (verified on KDE Plasma Wayland).

What runs where

  • tisvlay-daemon runs as a dedicated tisvlay system user; it owns the control socket (/run/tisvlay/zenoh.sock when packaged) and keeps its state under /var/lib/tisvlay/.
  • tisvlay-session and tisvlay-tray run as per-user services and reach the daemon over that socket.
  • The tisvlay CLI targets the same socket; see the Command Line page for overriding it.

Limitations

  • Full-feature control (input, volume, backlight) is LG-only for now.
  • Over HDMI-CEC only power and screen-blank are supported — volume, backlight, and arbitrary input switching aren’t reliable over CEC, and screen-blank is emulated by powering the TV off/on (CEC can’t blank a panel that stays on).
  • Wake-on-LAN doesn’t cross subnets without a relay (TVs on an Isolated Network); CEC TVs wake over the bus instead.

Security & Supply Chain

Every release is built and signed by the project’s release workflow, and ships with what you need to check it: checksums, signatures for the packages and the binaries inside them, and an SBOM (software bill of materials) for each binary. Releases are immutable: once published, a release’s files can’t be replaced.

The commands below use these variables; set FILE to the package you downloaded:

export VERSION=0.6.0
export BASE=https://github.com/TisVlay/TisVlay/releases/download/v${VERSION}
FILE=tisvlay_${VERSION}-1_amd64.deb

Checksums

The quickest check, with no extra tools. It confirms the download arrived intact:

wget ${BASE}/checksums-sha256.txt
sha256sum --ignore-missing -c checksums-sha256.txt

The checksum file comes from the same releases page as the package, so it can’t show that the release itself is genuine. The signatures can.

Signatures

Each package, and each binary inside it, is signed when it’s built, through Sigstore. A signature proves which repository, commit and workflow produced the file.

With the GitHub CLI

With the GitHub CLI, signed in to any GitHub account:

gh attestation verify "$FILE" \
  --repo TisVlay/TisVlay \
  --signer-workflow TisVlay/TisVlay/.github/workflows/build-packages.yml \
  --source-ref refs/tags/v${VERSION}
  • Keep --signer-workflow. Without it, a signature from any workflow in the repository would pass, including ones that run pull-request code.
  • The packages carry the binaries unchanged, so the same command also works on an installed system: gh attestation verify /usr/bin/tisvlay ….
Without a GitHub account (cosign)

Use cosign with the signature bundle from the releases page:

wget ${BASE}/tisvlay-${VERSION}-$(uname -m).provenance.sigstore.json
cosign verify-blob-attestation \
  --bundle tisvlay-${VERSION}-$(uname -m).provenance.sigstore.json \
  --certificate-identity https://github.com/TisVlay/TisVlay/.github/workflows/build-packages.yml@refs/tags/v${VERSION} \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --type https://slsa.dev/provenance/v1 \
  "$FILE"

To check that a file is one the release published, gh release verify-asset v${VERSION} "$FILE".

SBOMs

Each binary has an SBOM: the crates it was built from, with their versions, licenses and checksums, in CycloneDX format. The releases page has them as tisvlay-${VERSION}-$(uname -m).cdx.json and tisvlay-gui-${VERSION}-$(uname -m).cdx.json, and they’re signed like the packages.

Check and print a binary's SBOM
gh attestation verify /usr/bin/tisvlay \
  --repo TisVlay/TisVlay \
  --signer-workflow TisVlay/TisVlay/.github/workflows/build-packages.yml \
  --source-ref refs/tags/v${VERSION} \
  --predicate-type https://cyclonedx.org/bom \
  --format json --jq '.[0].verificationResult.statement.predicate'

Scanning an installed system

The binaries also carry their own dependency list, built in by cargo-auditable. So a vulnerability scanner can check an installed system against published advisories, with nothing to download from the release:

cargo audit bin /usr/bin/tisvlay /usr/bin/tisvlay-gui

Trivy and grype read the same list:

trivy rootfs --scanners vuln /usr/bin/tisvlay
grype /usr/bin/tisvlay

A finding isn’t necessarily new: the advisories the project has already assessed are listed, each with its reason, in deny.toml.

NixOS

Nix checks the cache’s signing key on every prebuilt path it substitutes (see Prebuilt binaries), so there’s nothing to verify by hand. The Nix package’s binaries carry the same dependency list, so the scanning above works on them too.

FAQ

Network or HDMI-CEC: which should I use?

For an LG TV, the network backend: it needs no extra hardware, reaches any TV on your network (not just the one on the cable), and has the fullest feature set. It’s also the only option when your GPU has no working CEC (as on Nvidia). HDMI-CEC works with any CEC-capable TV regardless of brand, but needs a CEC adapter and covers only power and screen-blank. See Requirements & Compatibility.

Which TVs are supported?

LG webOS TVs over the network, and any CEC-capable TV over HDMI-CEC; see Requirements & Compatibility. There’s also a built-in mock vendor for testing.

Who can control the TVs?

Whoever is sitting at the computer, automatically. Anyone else (SSH, scripts) needs to be granted access once; the app offers an Enable TV control button, and Access covers the rest.

Do I need all the parts?

No. The background service plus the app is a complete setup. The session service adds lock, unlock, and idle automation; the tray adds a menu and notifications; the command line adds scripting. Each is independent. See GUI & Tray and Automation.

Is turning the screen off safe for an OLED?

That’s what it’s for. The Screen switch (or Screen Off in the tray) turns the panel off while the TV stays powered and connected, so there’s no burn-in exposure and it resumes instantly. That’s why it’s the default lock and idle action. Power off is deeper (standby): a network TV then refuses control connections (even if it still answers on the network) and has to be woken to come back.

Can I script TisVlay?

Yes. The tisvlay command has --json output for its read commands, and tisvlay watch streams every state change without polling. The app and tray use the same interface, so anything they show is scriptable. See the Command Line page.

Where does TisVlay keep its settings?

In a few TOML files: the service’s store (TVs and system profiles) and your own per-user session and tray files. You normally never touch them, since the app edits them for you. Paths and schema are in Configuration Files.

Is the pairing key a secret?

Moderately. An LG TV’s pairing key (client_key) lets any holder on your network control that TV (power, input, volume; it is not a system credential). It’s stored where only the tisvlay service user can read it, and the service never hands it out: the app’s CONFIG view and tisvlay config leave it out, and it’s redacted from logs. Treat it like a Wi-Fi password: not catastrophic, but don’t paste it into bug reports.

Can I control several TVs?

Yes. Add each one with the + button. Automation is per-TV too: each TV has its own profiles and its own Managed switch, so one TV can blank when you lock while another ignores your desktop entirely. See Automation.


Something not answered here? Troubleshooting covers the common failure modes (discovery, Wake-on-LAN, pairing), and bug reports are welcome on GitHub. To help out in other ways, see the contributing guide.