Skip to content

Copy cameras to a TV and type with the remote - #116

Open
nomnommust wants to merge 2 commits into
OpenIPC:mainfrom
nomnommust:feat/tv-remote-copy
Open

nomnommust wants to merge 2 commits into
OpenIPC:mainfrom
nomnommust:feat/tv-remote-copy

Conversation

@nomnommust

Copy link
Copy Markdown

Summary

  • Add an on-screen keyboard for Android TV. The system keyboard takes D-pad arrows away from the keys, so OK opens a key grid the remote can move through.
  • Add Settings → Cameras, which copies cameras, layouts, and saved passwords from a Viewer on the same network. Nothing is sent until the PC allows it, and cameras already on the TV are kept.
  • From the settings section list, Right or OK moves onto the controls in that section, so Find a PC is reachable from the remote.

Test plan

  • On a TV, focus a text field, press OK, and confirm arrows move between keys and Back closes the keyboard.
  • On the PC, open Settings → Cameras and choose Share on this network. On the TV, choose Find a PC, pick that computer, and allow the copy on the PC. Confirm the cameras and their passwords arrive, and that cameras which exist only on the TV are still there.
  • In Settings, highlight Cameras and press Right. Focus should land on the buttons, and another Right should reach Find a PC.

The system keyboard takes D-pad arrows away from the keys, and retyping every camera password on the TV is unnecessary when the PC already has them.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Copy cameras to Android TV and enable remote-controlled typing

✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Add a D-pad keyboard and settings focus navigation so TV controls remain reachable.
Diagram

sequenceDiagram
    participant TV as TV Settings
    participant Client as LAN Client
    participant Host as PC Host
    actor User as PC User
    participant Backup as PC Backup
    participant Store as TV Store
    TV->>Client: Discover and pull
    Client->>Host: UDP probe and TCP pull
    Host->>User: Request approval
    User-->>Host: Allow or deny
    opt Approved
        Host->>Backup: Export encrypted config
        Backup-->>Host: JSON payload
        Host-->>Client: Send payload
        Client-->>TV: Preview and confirm
        TV->>Store: Import cameras and secrets
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Authenticated, encrypted pairing
  • ➕ Protects the transfer key and configuration from LAN observers.
  • ➕ Verifies the intended PC before accepting credentials.
  • ➖ Adds pairing UX and protocol complexity.
2. Reuse shared-file configuration sync
  • ➕ Avoids introducing a LAN transfer protocol.
  • ➖ Requires shared storage and does not provide the proposed per-transfer approval flow.

Recommendation: Keep the approval-gated, merge-based backup workflow, but use authenticated, encrypted pairing before transferring saved passwords. The proposed one-time passphrase is sent over plaintext TCP, so a LAN observer could capture it and decrypt the credential payload.

Files changed (12) +949 / -42

Enhancement (8) +847 / -22
CameraTransferService.csAdd approval-gated LAN camera transfer +266/-0

Add approval-gated LAN camera transfer

• Introduces UDP host discovery and a TCP pull protocol. The host requests approval before exporting configuration with credentials encrypted using a receiver-generated passphrase.

src/OpenIPC.Viewer.App/Services/CameraTransferService.cs

Localizer.csLocalize camera-transfer controls and messages +36/-0

Localize camera-transfer controls and messages

• Adds English and Russian text for the Cameras section, discovery, approval, confirmation, and transfer outcomes.

src/OpenIPC.Viewer.App/Services/Localizer.cs

TvOnScreenKeyboard.csAdd a D-pad-operated on-screen keyboard +295/-0

Add a D-pad-operated on-screen keyboard

• Provides an Avalonia key grid with directional navigation, shift, space, backspace, a masked password preview, and dismissal that restores text-field focus.

src/OpenIPC.Viewer.App/Services/TvOnScreenKeyboard.cs

MainWindowViewModel.csDismiss the TV keyboard before navigating back +3/-0

Dismiss the TV keyboard before navigating back

• Makes Back close the on-screen keyboard before dismissing dialogs or leaving the current page.

src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs

SettingsPageViewModel.csCoordinate camera sharing and importing +154/-6

Coordinate camera sharing and importing

• Adds Cameras section state, host discovery, source-side approval, destination-side import confirmation, and transfer status. Sharing is cancelled when the section changes.

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs

SettingsPage.axamlAdd Cameras settings and remote-reachable controls +56/-2

Add Cameras settings and remote-reachable controls

• Adds Share and Find a PC controls, discovered-host buttons, and status text. Makes both settings scroll viewers non-focusable so D-pad navigation can reach their contents.

src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml

IConfigBackupService.csAllow credential-aware configuration imports +3/-2

Allow credential-aware configuration imports

• Adds an optional import passphrase so transfer imports can restore encrypted secrets while existing imports continue to leave passwords untouched.

src/OpenIPC.Viewer.Core/Persistence/IConfigBackupService.cs

SqliteConfigBackupService.csRestore camera and SSH credentials during import +34/-12

Restore camera and SSH credentials during import

• Extends encrypted exports to include SSH logins and adds passphrase-based credential restoration to merge imports. Cameras and layouts continue to use the existing upsert behavior.

src/OpenIPC.Viewer.Infrastructure/Persistence/SqliteConfigBackupService.cs

Bug fix (2) +80 / -20
TvSoftKeyboard.csOpen the TV key grid instead of the Android keyboard +18/-20

Open the TV key grid instead of the Android keyboard

• Suppresses the system keyboard when a TV text field receives focus. OK or a pointer tap opens the D-pad-accessible keyboard; programmatic focus remains supported.

src/OpenIPC.Viewer.Android/Platform/TvSoftKeyboard.cs

SettingsPage.axaml.csRoute remote focus between settings panes +62/-0

Route remote focus between settings panes

• Handles Right or OK from the section list by focusing a detail control. At the left edge of the detail pane, Left returns focus to the section list.

src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml.cs

Tests (1) +21 / -0
SqliteConfigBackupServiceTests.csTest credential-bearing import round trips +21/-0

Test credential-bearing import round trips

• Verifies that exported camera and SSH passwords are absent from plaintext JSON and restored by a passphrase-enabled import.

tests/OpenIPC.Viewer.Infrastructure.Tests/SqliteConfigBackupServiceTests.cs

Other (1) +1 / -0
SharedComposition.csRegister the camera-transfer service +1/-0

Register the camera-transfer service

• Adds CameraTransferService to shared dependency injection for the settings view model.

src/OpenIPC.Viewer.Composition/SharedComposition.cs

@nomnommust

Copy link
Copy Markdown
Author

hi i made the 32bit build and then had soem issues with typing etc on the app on tv also was a bit annoying havign to type in all the camera details on a tv with only a remote so created a fucntion that allows me to set up the cameras on pc and then scan from the tv for a pc on lan that it can then pull all the camera info for aswell as layouts etc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. LAN impostors can pose as chosen screens ✓ Resolved

Resolving commit: 51da774

Resolution reason
Adds ECDH key agreement with a 6-digit pairing code shown on both screens and required to match
before approval (CameraTransferService PullAsync/HandleClientAsync). Approval dialog now includes
requester name, IP address and pairing code. A single approval slot is enforced via Interlocked
_approving (others refused with NO), handler count is capped at MaxHandlers, and sharing stops after
one successful transfer (session.Cancel on success). This covers the pairing-authentication,
IP-in-dialog, one-pending-approval, and stop-after-one-transfer parts of the recommended fix, though
discovery replies themselves remain unauthenticated and the pairing code is the user-verified check.
Description
CameraTransferService trusts unauthenticated discovery replies and the requester-supplied PULL
name, while HandleClientAsync opens an approval prompt for every connection without a
pending-request limit. A LAN actor can advertise a fake PC whose camera configuration reaches
import, or connect to a sharing PC under an invented TV name and receive passwords if the owner
approves; repeated connections can also flood the owner with prompts.

2. Sharing stays active after leaving Settings ✓ Resolved

Resolving commit: 51da774

Resolution reason
Page detachment from the visual tree now calls LeaveTransfer, which stops sharing and cancels a
page-lifetime token. Pending pull and discovery use that token instead of CancellationToken.None,
and the code checks cancellation before importing so a cancelled pull cannot import afterward.
Description
OnSelectedSectionIndexChanged stops sharing only when the subsection index changes away from 4,
while leaving Settings changes CurrentPage without changing that index. The singleton view model
can therefore keep its listener and discovery responder active after navigation, showing Allow
prompts with no visible sharing UI; a pending pull uses CancellationToken.None and can import
afterward.

3. Anyone on the network can read the copied passwords ✓ Resolved

Resolving commit: 51da774

Resolution reason
The random key in the plaintext hello is replaced by an ephemeral ECDH P-256 exchange. Both sides
derive a shared secret, the export passphrase is SHA-256 of that secret (so it is never sent on the
wire), and a 6-digit pairing code derived from the secret is shown on both screens and must be
confirmed on the host before Allow. This follows the suggested ECDH option, though the wire itself
remains unencrypted; a passive observer cannot derive the passphrase, and the pairing code guards
against MITM.
Description
PullAsync sends its generated AES key in the plaintext PULL hello, and HandleClientAsync uses
that key to encrypt the export before returning it over the same unencrypted TCP connection. Anyone
capturing an approved LAN transfer receives the key, salt and ciphertext needed to decrypt camera
and SSH passwords, while RTSP URIs and the rest of the configuration also travel in the clear.

View action required (1)
4. The Stop sharing button stays disabled ✓ Resolved

Resolving commit: 51da774

Resolution reason
The commit changes the attribute to [RelayCommand(AllowConcurrentExecutions = true)] on
ShareCamerasAsync, exactly the suggested fix. It also adds a lock (_shareGate) around the state
transitions of _shareCts, so a second press while hosting reaches the StopSharing branch safely.
Description
ShareCamerasAsync awaits the full HostAsync session in a generated AsyncRelayCommand without
AllowConcurrentExecutions, leaving its bound button disabled while hosting runs. When the button
label changes to “Stop sharing,” a second press cannot reach the _shareCts is not null
cancellation branch, so sharing continues until the user leaves the Cameras section or another
cancellation path runs.


Remediation recommended

5. The TV's SSH keyboard button loses input ✓ Resolved

Resolving commit: 51da774

Resolution reason
The SSH keyboard button's click handler in SshTerminalContent now calls
TvOnScreenKeyboard.ShowRequested(_proxy) after FocusInput(), which opens the custom on-screen grid
on TV (gated by a new Enabled flag set by the TV head). This gives the SSH input proxy a working
keyboard on TV, addressing the suggested "provide a working keyboard for the SSH input proxy" remedy
via a different mechanism than changing the OnGotFocus logic in TvSoftKeyboard.cs. Also Show() now
permits the grid for readonly-excluded boxes, and Enabled gating prevents phones from changing
behavior.
Description
TvSoftKeyboard.OnGotFocus now posts SetKeyboard(show: false) for programmatic focus as well as
directional focus, without opening the custom grid for that case. On Android TV, the SSH terminal’s
keyboard button programmatically focuses its input proxy, so the system keyboard it requests is
hidden and no replacement opens.

6. TV users can edit read-only fields ✓ Resolved

Resolving commit: 51da774

Resolution reason
Show() rejects read-only text boxes (box.IsReadOnly check), and Type() and Backspace() bail out when
the target is read-only, so the on-screen keyboard can no longer change the bound read-only path.
Description
TvOnScreenKeyboard.Show does not reject read-only text boxes, and its key actions assign directly
to box.Text rather than going through text editing. Pressing OK on the File Manager’s read-only
remote-path field can therefore change the bound path, with the same bypass available to other
read-only text boxes.

7. SSH-only copies erase TV camera logins ✓ Resolved

Resolving commit: 51da774

Resolution reason
Export now writes null for absent camera username/password instead of "", CredsPayload fields became
nullable, and import only sets the username/password secret refs when the payload value is non-null.
A new test verifies an SSH-only source leaves an existing destination login intact.
Description
EncryptCredsAsync now emits a payload for an SSH-only camera but serializes its absent camera
username and password as empty strings; RestoreCredsAsync writes those strings to any supplied
login refs. When that camera already has working login secrets on the destination, importing the
SSH-only source overwrites those secrets with empty values.

View review recommended (3)
8. Host transfer failures leave no diagnosis ✓ Resolved

Resolving commit: 51da774

Resolution reason
The catch-all no longer silently discards errors. Expected network/cancellation/format errors are
still returned quietly, but any other exception is now logged via _log.LogError(ex, ...) with the
stack trace and forwarded to onFailure, which the SettingsPageViewModel routes to TransferStatus via
ReportTransferFailure (host-side status). The listener continues running because the handler returns
false rather than rethrowing, matching the fix prompt's "surface failures without terminating the
listener" requirement.
Description
HandleClientAsync catches and discards every exception, including failures from approval,
credential export and writing the response. When one of those operations fails, the host remains in
its Waiting state and its settings error handler never sees the failure, leaving no host-side status
or traceback.

9. Back loses the selected Settings row ✓ Resolved

Resolving commit: 51da774

Resolution reason
The commit moves the capture of SectionList.SelectedIndex ahead of the _vm.TryGoBack() call in
FocusSectionList, with a comment explaining that TryGoBack clears the selection so the row must be
captured first. This is exactly the suggested fix: capture the row before TryGoBack, then focus the
captured row in the posted callback.
Description
FocusSectionList calls TryGoBack() before saving SectionList.SelectedIndex; in narrow mode
that call sets the two-way-bound selection to -1. Pressing Left from a detail control therefore
restores focus to the list box rather than the row the user just left, requiring them to select a
section again before moving into its controls.

10. An invisible keyboard consumes Back ✓ Resolved

Resolving commit: 51da774

Resolution reason
Added a public TvOnScreenKeyboard.Reset() that tears down the overlay layer, subscription, target,
preview and letters and clears open state; Dismiss now delegates to it. TvSoftKeyboard.Uninstall
calls Reset() and disables Enabled when the owning activity tears down, and Install calls Reset()
when a different activity replaces the previous one. This clears static IsOpen state on activity
teardown/recreation so a later activity's Back is not swallowed, matching the finding's recommended
explicit teardown on recreation.
Description
TvOnScreenKeyboard keeps its overlay and target in static fields, but Android activity teardown
does not dismiss them. If an activity is recreated while the grid is open, TryGoBack treats that
old, invisible overlay as open and consumes the first Back press instead of navigating the new
screen.


Informational

11. Idle connections hold the share open forever ✓ Resolved

Resolving commit: 51da774

Resolution reason
Caps active handlers at 4 via a semaphore, tracks and awaits handler tasks on shutdown, applies a
15-second deadline to header reads via a linked CancellationTokenSource, and serializes approval
dialogs with an Interlocked flag so extra clients are refused.
Description
HostAsync starts an untracked handler for every accepted connection, while HandleClientAsync
relies on the share-wide ct for asynchronous I/O even though its ReceiveTimeout and
SendTimeout settings do not bound ReadAsync or WriteAsync. LAN clients that never finish a
header can keep arbitrarily many sockets and handlers alive until sharing stops, and clients that
complete headers can trigger concurrent approval dialogs.

12. Backspace closes the on-screen keyboard ✓ Resolved

Resolving commit: 51da774

Resolution reason
OnKey now checks Key.Back first and calls Backspace() with e.Handled = true; only Key.Escape calls
DismissAndRestoreFocus. This matches the suggested fix exactly. The comment explains that the
remote's Back button arrives as a back request, so Key.Back must delete.
Description
TvOnScreenKeyboard.OnKey treats Key.Back as a close key, but in Avalonia Key.Back is the
Backspace key, not the Android Back button. A user with a USB or Bluetooth keyboard who presses
Backspace while the grid is open dismisses it instead of deleting a character.

Grey Divider

Tip of the day
💡 Did you know, you can open the rule or relationship behind a finding in one click

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

The transfer key was sent in cleartext, sharing kept running after Settings was left, and Backspace closed the on-screen keyboard.
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