Skip to content

Add support for tvOS input#65

Draft
omgitsalexl wants to merge 16 commits into
owenselles:mainfrom
omgitsalexl:tvos-controller-keyboard
Draft

Add support for tvOS input#65
omgitsalexl wants to merge 16 commits into
owenselles:mainfrom
omgitsalexl:tvos-controller-keyboard

Conversation

@omgitsalexl

Copy link
Copy Markdown

When streaming certain games, they require text input from the user to continue (e.g. Inputting a password to join a dedicated server in Enshrouded, Palworld, or other games). This pull request adds the ability to pull up the tvOS native keyboard, receive input, and then play this input back to the stream as a sequence of characters.

There are settings added to allow customizing the shortcut keys to pull up the native keyboard, and to adjust the hold time needed to display the keyboard; to help prevent crossover between games that may have the same button sequence.

@aarikmudgal aarikmudgal self-requested a review July 10, 2026 14:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1c59d721b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1064 to +1065
if now >= state.deadline {
keyboardShortcutStates[slot] = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Don't resuppress held shortcut buttons after timeout

When a user holds only one button from the text-input chord, such as the default Y button for an in-game hold action, the state is cleared as soon as the delay expires. On the next sample the still-held button starts a new shortcut state, so the button is suppressed again; the stream sees brief repeated taps instead of a continuous hold. After the timeout for an incomplete chord, keep that partial press in a pass-through state until all involved buttons are released.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, Tested in FH6. Could you make sure that if only part of the shortcut is held, the button continues behaving like a normal held button until it’s released? At the moment, it might be sent as repeated taps.

Comment on lines +1102 to +1103
if shortcutButtons == textInputTriggerSequence.asSet() {
notifyControllerKeyboardShortcut()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the hold delay before opening text entry

For the common case where the full chord is pressed together, this branch calls notifyControllerKeyboardShortcut() immediately instead of waiting for textInputTriggerDelayMs. That makes the new hold-delay setting ineffective and still lets quick accidental chord presses open the Apple TV keyboard, contrary to the setting description that the buttons must be held for the configured duration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should apply the configured hold delay after the full shortcut is pressed? A quick press of the shortcut shouldn’t open the keyboard.

@aarikmudgal

Copy link
Copy Markdown
Collaborator

Hi @omgitsalexl Could you please run the SwiftLint, swiftformat on the branch while I test.

@aarikmudgal aarikmudgal added the enhancement New feature or request label Jul 10, 2026
@aarikmudgal aarikmudgal requested a review from owenselles July 10, 2026 14:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dad7c125ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let shortcutGraceWindow = UInt64(textInputTriggerDelayMs) * 1_000_000
let shortcutButtons = physicalKeyboardShortcutButtons(on: controller)
if var state = keyboardShortcutStates[slot] {
state.pendingButtons.formUnion(shortcutButtons)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't merge sequential shortcut taps into a chord

When a user taps one configured shortcut button, releases it, and then taps another before the delay expires, this line keeps the released button in pendingButtons. At the timeout, keyboardShortcutReplayButtons(for: state.pendingButtons) replays every accumulated button as one chord, so with the default Options+Y shortcut the stream receives a synthetic Options+Y tap instead of the two separate game inputs. Clear or replay the released partial before unioning later presses.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should preserve separate button presses here. If someone taps Y and then Options, the game should receive two individual taps rather than one combined shortcut.

Comment on lines +1559 to +1561
guard let (usage, requiresShift) = Self.usageForCharacter(character) else {
inputLog.info("Skipping unsupported replay character: \(String(character), privacy: .public)")
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't submit text after dropping unsupported characters

When the Apple TV keyboard returns characters outside this hard-coded HID map (for example accented letters, CJK IME output, or emoji), this path silently skips them; replaySubmittedText still appends Return afterward, so the remote game/chat can receive a truncated or empty submission. Either reject/disable sending when any character is unmapped, or use an input path that preserves the full String.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I couldn't test yet but maybe you can validate the text before sending it? If it contains unsupported characters, keep the keyboard open and show an error instead of sending incomplete text.
Also, please avoid writing the character to logs since it could be part of a password.

@aarikmudgal

Copy link
Copy Markdown
Collaborator

@omgitsalexl I didn't get the chance to test in a game where I need to type which I will do in sometime, need to look for a game where I can input. But did high level testing and noticed that the review comments from the bots are valid. Also, maybe the UI overlay for the text input could be styled a bit better, like centre the text, etc. Also, after entering the text, and clicking on "done" closes the entire overlay, and I was never able to "send" it.

@aarikmudgal

Copy link
Copy Markdown
Collaborator

Codex:
I think the safest approach is to treat shortcut detection as a complete button gesture rather than accumulating every button seen during the delay. Once the full shortcut is pressed, start the hold timer and open the keyboard only if those buttons stay pressed for the configured duration. If the shortcut is incomplete or released early, forward the original button presses normally and preserve their order, so a held Y remains held, while Y followed by Options remains two separate taps.
Before replaying entered text, first confirm that every character can be converted to a remote key event. If any character is unsupported, keep the text-entry screen open and show an error instead of sending truncated text or Enter. Raw characters should not be logged because the input may contain a password.

@omgitsalexl

Copy link
Copy Markdown
Author

I really appreciate the thorough review! I had only tested it myself in a very specific manner, inputting server details mostly.

Regarding the done button vs a send button: pressing done closes the window and sends input at the same time. I could definitely make this more clear to the user.

The Codex bot does indeed have some good points as well, and yours regarding the potential logging issues.

I'll take all these into account and try to get it cleaned up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants