Skip to content

Feature: Restrict power off to a specific HDMI input (REQUIRED_APP) - #15

Merged
bassidus merged 1 commit into
bassidus:testingfrom
Nikhil-Gohil:main
Aug 9, 2026
Merged

bassidus merged 1 commit into
bassidus:testingfrom
Nikhil-Gohil:main

Conversation

@Nikhil-Gohil

Copy link
Copy Markdown
Contributor

I switch between my home PC and my work laptop frequently during the day. On Windows, I used to use LGTVCompanion and it had a way to only send commands to the TV when we were on a specific hdmi port which I found really useful. Using lgpowercontrol, I would switch away from cachyOS and while on my work laptop, the screen would go blank. I have been testing this implementation for a few days and it seems to work fine. Full disclosure, I used AI to help me make the change but the change is pretty minimal and easy to understand.

@bassidus

bassidus commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Thanks for this, and for explaining the use case, it makes the motivation clear. Switching between a desktop and a work laptop on the same TV is exactly the scenario the current code handles badly, and I've had "skip TV-off when the TV is on another input" on my own maybe-list for a while. Nice to see it actually implemented. No issue at all with AI-assisted work; the diff is small and readable, which is what matters.

A few things before this can land.

It needs to target testing, not main. main is the release branch and is currently 17 commits behind testing, which is where the next version is being put together. Two of those commits collide with your patch: tv() is now called tv_cmd(), and the conf file has lost the UPDATE_CHECK_* / UPDATE_CHANNEL keys (self-update was removed in 4.0). So the patch won't apply as-is, a rebase onto testing is needed.

Review notes:

  1. Fail-closed on errors. check_required_app() skips the off command on any non-zero rc, but rc conflates different things: 2 means unreachable (skipping is right), while 1 covers e.g. PyLGTVServiceNotFoundError on a webOS version that doesn't expose the endpoint. On such a TV the feature would silently stop the TV from ever turning off, with no obvious cause. I'd rather it only skips on rc 2 and proceeds on rc 1, a failed guard shouldn't be able to disable the core function.

  2. Exit code. OFF against an unreachable TV used to return 2; with the guard it returns 0. monitor.py logs on a non-zero return code, so genuine failures would stop showing up in the journal.

  3. Extra round-trip on the suspend path. This is my main concern. The guard opens its own WebSocket session (connect + handshake + disconnect) before power_off gets a second one. On the NetworkManager pre-down path the whole design hinges on fitting inside the window where the network is still up, NM tears connections down roughly 17 ms after logind's PrepareForSleep, and that path is the primary one for suspend. Doubling the work in that window may or may not still fit; I'll need to verify it on real hardware before merging, but if you have a cheap way to avoid the second connection I'm interested.

  4. The wake side is unguarded. If the guard skips the TV-off because the TV is on the laptop's input, the PC still runs ON at resume - turn_screen_on plus set_input HDMI_x - and yanks the TV away from the laptop anyway. Did you hit this in your testing, or does your setup avoid it somehow? Feels like the guard wants a counterpart on the ON path to be complete.

  5. Config naming and discovery. REQUIRED_APP doesn't say what the app is required for, and the value is a raw com.webos.app.hdmi2 that users have to dig out by running bscpylgtvcommand against the sqlite file by hand. The conf file right above it already has HDMI_INPUT="2". Something like POWER_OFF_ONLY_ON_HDMI="2", mapped to the app id internally, would be consistent with the rest of the file and wouldn't require the manual lookup step.

  6. Style nit. The codebase uses # comments above the def rather than docstrings.

If you'd like to take this the rest of the way, rebase onto testing and address 1, 2, 5 and 6 — I'll handle the hardware testing for 3, and 4 is worth a conversation first. If you'd rather not spend more time on it, that's completely fine: say the word and I'll finish it myself, cherry-picking your commit so it stays authored by you.

@Nikhil-Gohil

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review and recommendations! I've addressed points 1, 2, 5 and 6, rebased onto testing, and tested on my setup (lgpowercontrol on cachyos on hdmi2, work laptop on hdmi1).

Changes made:

  • Point 1 (fail-open): check_power_off_guard() now only skips on rc == 2 (unreachable). On rc == 1 (e.g. PyLGTVServiceNotFoundError on an older webOS), it logs a warning and proceeds with the off command so the guard can never silently disable power-off.
  • Point 2 (exit code): When the TV is unreachable, the guard returns 2 instead of 0, so monitor.py still logs it.
  • Point 5 (config naming): Renamed to POWER_OFF_ONLY_ON_HDMI="2" — accepts a plain HDMI number, mapped to com.webos.app.hdmi{N} internally. Consistent with HDMI_INPUT above it, no manual bscpylgtvcommand lookup needed.
  • Point 6 (style): # comment above the function instead of a docstring.

Tests I ran:

Test Result
OFF while TV on wrong HDMI (laptop) Skipped with log: TV on com.webos.app.hdmi1, not on com.webos.app.hdmi2 — skipping off command
OFF while TV on correct HDMI (CachyOS) TV powered off normally
SCREEN_OFF while TV on wrong HDMI Skipped correctly
SCREEN_OFF while TV on correct HDMI TV screen blanked normally
OFF with TV already powered off Returned exit code 2 (unreachable)
POWER_OFF_ONLY_ON_HDMI="" (disabled) Original behavior, TV powers off regardless of input

On point 3 (extra round-trip):

I considered combining the get_current_app check and the off command into a single WebSocket session, but that would have required either duplicating tv_cmd()'s exception handling or modifying tv_cmd()'s signature — both felt too invasive for this PR. In my testing the two-connection approach hasn't caused any issues on the suspend path, but I understand there could be issues. If you can help test we can decide if we need to implement these changes.


On point 4 (unguarded ON path):

In my setup, both the CachyOS desktop and work laptop are connected to the TV via HDMI, but I don't use HDMI_INPUT (it's set to ""). Because of this, the ON path only calls turn_screen_on and never runs set_input, so it doesn't yank the TV away from the laptop when CachyOS wakes.

I agree the issue would exist for users who configure both HDMI_INPUT and POWER_OFF_ONLY_ON_HDMI — that combination would need a matching guard on the ON path. If needed, I can implement similar guards on the ON path but I am not sure what the intended behavior in this case should be. Would prefer to hear from users who actually use this setup.

IMO, if my monitor is asleep and I use my work mouse to wake, I would like the monitor to stay on the work laptop input. But if I use the lgpowercontrol connected mouse to wake, the screen should switch to the lgpowercontrol input. I might be missing something here though so would love some feedback.

@Nikhil-Gohil
Nikhil-Gohil changed the base branch from main to testing August 9, 2026 08:44
@bassidus
bassidus merged commit aa9440f into bassidus:testing Aug 9, 2026
@bassidus

bassidus commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Merged into testing as aa9440f. Thanks for taking this the rest of the way,
and for the test table, points 1, 2, 5 and 6 all look right, and rebasing onto
testing applied cleanly.

Since the guard is off by default (POWER_OFF_ONLY_ON_HDMI=""), nothing changes
for existing users, so there was no reason to hold the commit while the remaining
two questions get sorted. Both are mine to finish, as follow-ups on top of your
work rather than changes to your commit:

Point 3, the extra round-trip. Still mine to measure. Your reasoning for not
touching tv_cmd()'s signature is the right call, that function is the single
most suspend-critical piece of code in the project and I'd rather not reshape it
for this. I'll test the two-connection version on hardware and only revisit
session sharing if the window turns out too tight.

Point 4, the ON path. Your explanation is exactly right, and I found the
mechanism: _tv_off() in suspend.py touches its own flag before running
OFF and never looks at the result, so _tv_on() fires ON at resume whether
or not the guard skipped the off. What saves your setup is HDMI_INPUT="",
ON then only calls turn_screen_on, which against a TV already showing the
laptop returns -102 and is treated as success, so it touches nothing. With
HDMI_INPUT set, set_input is what actually yanks the input away.

So the counterpart isn't a guard on all of ON, just on set_input, and that
one is cheap: it runs post-resume, detached, outside the pre-down window where
round-trips are expensive.

On your instinct about which mouse wakes it, I think you already get most of
that for free. If you wake the laptop, this machine stays asleep and
lgpowercontrol never runs at all. The case that actually needs the guard is this
machine waking or un-blanking while the TV is on the laptop's input, which is
precisely what gating set_input on the current input covers.

One thing I'll add separately: the config value isn't validated, so
POWER_OFF_ONLY_ON_HDMI="HDMI_2" builds com.webos.app.hdmiHDMI_2, never
matches, and quietly disables power-off for good, the same failure mode as
point 1, just reached by a typo instead of an error code. HDMI_INPUT has the
same typo surface but fails loudly. An isdigit() check closes it.

Thanks again, this was a genuinely useful contribution.

bassidus added a commit that referenced this pull request Aug 9, 2026
The guard from #15 kept suspend from turning off a TV that was showing another
source, but the wake path still ran set_input and grabbed it back, so the two
halves worked against each other on any setup that also had HDMI_INPUT set.

POWER_OFF_ONLY_ON_HDMI now overrides HDMI_INPUT rather than growing a second
guard. Deciding per wake who owns the input would need another round-trip plus a
rule for the case where we woke the TV ourselves, and nothing was asked for it -
the contributor's own setup leaves HDMI_INPUT empty, so this is the arrangement
the feature was actually tested against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants