From df5e64d38a0dd34b93aaffeb45d70ddf70ec2527 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Fri, 13 Mar 2026 19:27:28 -0500 Subject: [PATCH] Fix text input on kiosk: add keycodeToChar fallback for email/license pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kiosk lacks an input method framework, so SDL2 never generates TextInputEvent. The WiFi password page already had a keycodeToChar fallback but email and license key pages did not — characters were never appended to inputText. Also removes excessive debug logging from the previous commit. Co-Authored-By: Claude Opus 4.6 --- internal/application/application.go | 10 ------ internal/page/emailAddressPage.go | 49 +++++++++++++------------- internal/page/licenseKeyPage.go | 53 +++++++++++++++-------------- internal/page/wifiSetupPage.go | 27 +-------------- 4 files changed, 52 insertions(+), 87 deletions(-) diff --git a/internal/application/application.go b/internal/application/application.go index a834d72..edb6023 100644 --- a/internal/application/application.go +++ b/internal/application/application.go @@ -378,17 +378,7 @@ func (app *Application) handleEvents() { app.running = false return - case *sdl.TextInputEvent: - logger.Info("[EVENT] TextInputEvent: text=%q, IsTextInputActive=%v", e.GetText(), sdl.IsTextInputActive()) - case *sdl.KeyboardEvent: - eventType := "KEYDOWN" - if e.Type == sdl.KEYUP { - eventType = "KEYUP" - } - logger.Info("[EVENT] KeyboardEvent: type=%s, sym=%d (%s), scancode=%d, mod=0x%04x, IsTextInputActive=%v", - eventType, e.Keysym.Sym, sdl.GetKeyName(e.Keysym.Sym), e.Keysym.Scancode, e.Keysym.Mod, sdl.IsTextInputActive()) - if e.Type == sdl.KEYDOWN && e.Repeat == 0 { isCtrlPressed := (e.Keysym.Mod&sdl.KMOD_CTRL) != 0 || (e.Keysym.Mod&sdl.KMOD_GUI) != 0 if e.Keysym.Sym == sdl.K_c && isCtrlPressed { diff --git a/internal/page/emailAddressPage.go b/internal/page/emailAddressPage.go index 2e8015a..001f9e6 100644 --- a/internal/page/emailAddressPage.go +++ b/internal/page/emailAddressPage.go @@ -11,7 +11,6 @@ import ( "rhemawavetech.com/rhemawave/internal/colors" "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" - "rhemawavetech.com/rhemawave/internal/logger" "rhemawavetech.com/rhemawave/internal/shaderManager" ) @@ -43,6 +42,9 @@ type EmailAddressPage struct { // isFirstTimeSetup tracks whether page was loaded empty (first-time setup) isFirstTimeSetup bool + + // Text input deduplication + textInputSupported bool } // isValidEmail checks if the given string is a valid email format @@ -112,9 +114,7 @@ func (p *EmailAddressPage) Init(app ApplicationInterface) error { p.isFirstTimeSetup = true } - logger.Info("[EmailAddressPage.Init] Calling sdl.StartTextInput(), isFirstTimeSetup=%v", p.isFirstTimeSetup) sdl.StartTextInput() - logger.Info("[EmailAddressPage.Init] After StartTextInput: IsTextInputActive=%v", sdl.IsTextInputActive()) return nil } @@ -122,34 +122,26 @@ func (p *EmailAddressPage) Init(app ApplicationInterface) error { // HandleEvent processes SDL events and handles them appropriately for the EmailAddressPage. func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error { actualEvent := *event - logger.Info("[EmailAddressPage.HandleEvent] Event type: %T", actualEvent) - - isActive := sdl.IsTextInputActive() - if !isActive { - logger.Info("[EmailAddressPage.HandleEvent] TextInput not active, calling StartTextInput()") + if !sdl.IsTextInputActive() { sdl.StartTextInput() - logger.Info("[EmailAddressPage.HandleEvent] After StartTextInput: IsTextInputActive=%v", sdl.IsTextInputActive()) } switch e := actualEvent.(type) { case *sdl.TextInputEvent: - text := e.GetText() - p.inputText += text - logger.Info("[EmailAddressPage.HandleEvent] TextInputEvent: received=%q, inputText=%q", text, p.inputText) - // Clear error when user starts typing - p.errorMessage = "" - case *sdl.KeyboardEvent: - eventType := "KEYDOWN" - if e.Type == sdl.KEYUP { - eventType = "KEYUP" + if !p.textInputSupported { + // First TextInputEvent: the keyboard fallback already + // handled this character, so just record that text input + // is supported and skip the duplicate append. + p.textInputSupported = true + } else { + p.inputText += e.GetText() + // Clear error when user starts typing + p.errorMessage = "" } - logger.Info("[EmailAddressPage.HandleEvent] KeyboardEvent: type=%s, sym=%d (%s), scancode=%d, mod=0x%04x", - eventType, e.Keysym.Sym, sdl.GetKeyName(e.Keysym.Sym), e.Keysym.Scancode, e.Keysym.Mod) - + case *sdl.KeyboardEvent: if e.Type == sdl.KEYDOWN { switch e.Keysym.Sym { case sdl.K_ESCAPE: - logger.Info("[EmailAddressPage.HandleEvent] ESCAPE pressed, isFirstTimeSetup=%v", p.isFirstTimeSetup) // Block escape during first-time setup if p.isFirstTimeSetup { return nil @@ -158,7 +150,6 @@ func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error { sdl.StopTextInput() p.App.UnwindToPage(&Settings{}) case sdl.K_RETURN: - logger.Info("[EmailAddressPage.HandleEvent] RETURN pressed, inputText=%q", p.inputText) // Validate email - check empty first, then format if p.inputText == "" { p.errorMessage = "Email address cannot be empty" @@ -186,10 +177,19 @@ func (p *EmailAddressPage) HandleEvent(event *sdl.Event) error { case sdl.K_BACKSPACE: if len(p.inputText) > 0 { p.inputText = p.inputText[:len(p.inputText)-1] - logger.Info("[EmailAddressPage.HandleEvent] BACKSPACE: inputText=%q", p.inputText) // Clear error when user edits input p.errorMessage = "" } + default: + // Keyboard fallback: if TextInputEvent is not supported, + // convert printable keycodes to characters directly. + // This handles kiosk systems without an input method framework. + if !p.textInputSupported { + if ch := keycodeToChar(e.Keysym.Sym, e.Keysym.Mod); ch != 0 { + p.inputText += string(ch) + p.errorMessage = "" + } + } } } } @@ -410,7 +410,6 @@ func (p *EmailAddressPage) Destroy() error { p.errorTextureID = 0 } - logger.Info("[EmailAddressPage.Destroy] Calling sdl.StopTextInput()") sdl.StopTextInput() return p.Base.Destroy() } diff --git a/internal/page/licenseKeyPage.go b/internal/page/licenseKeyPage.go index 87d124e..0283f1b 100644 --- a/internal/page/licenseKeyPage.go +++ b/internal/page/licenseKeyPage.go @@ -42,6 +42,9 @@ type LicenseKeyPage struct { // isFirstTimeSetup tracks whether page was loaded empty (first-time setup) isFirstTimeSetup bool + + // Text input deduplication + textInputSupported bool } // isValidLicenseKey checks if the license key has exactly 16 alphanumeric characters @@ -120,9 +123,7 @@ func (p *LicenseKeyPage) Init(app ApplicationInterface) error { p.isFirstTimeSetup = true } - logger.Info("[LicenseKeyPage.Init] Calling sdl.StartTextInput(), isFirstTimeSetup=%v", p.isFirstTimeSetup) sdl.StartTextInput() - logger.Info("[LicenseKeyPage.Init] After StartTextInput: IsTextInputActive=%v", sdl.IsTextInputActive()) return nil } @@ -158,36 +159,28 @@ func formatLicenseKey(input string) string { // HandleEvent processes SDL events and handles them appropriately for the LicenseKeyPage. func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error { actualEvent := *event - logger.Info("[LicenseKeyPage.HandleEvent] Event type: %T", actualEvent) - - isActive := sdl.IsTextInputActive() - if !isActive { - logger.Info("[LicenseKeyPage.HandleEvent] TextInput not active, calling StartTextInput()") + if !sdl.IsTextInputActive() { sdl.StartTextInput() - logger.Info("[LicenseKeyPage.HandleEvent] After StartTextInput: IsTextInputActive=%v", sdl.IsTextInputActive()) } switch e := actualEvent.(type) { case *sdl.TextInputEvent: - text := e.GetText() - // Add the new character and reformat - p.inputText += text - p.inputText = formatLicenseKey(p.inputText) - logger.Info("[LicenseKeyPage.HandleEvent] TextInputEvent: received=%q, inputText=%q", text, p.inputText) - // Clear error when user starts typing - p.errorMessage = "" - case *sdl.KeyboardEvent: - eventType := "KEYDOWN" - if e.Type == sdl.KEYUP { - eventType = "KEYUP" + if !p.textInputSupported { + // First TextInputEvent: the keyboard fallback already + // handled this character, so just record that text input + // is supported and skip the duplicate append. + p.textInputSupported = true + } else { + // Add the new character and reformat + p.inputText += e.GetText() + p.inputText = formatLicenseKey(p.inputText) + // Clear error when user starts typing + p.errorMessage = "" } - logger.Info("[LicenseKeyPage.HandleEvent] KeyboardEvent: type=%s, sym=%d (%s), scancode=%d, mod=0x%04x", - eventType, e.Keysym.Sym, sdl.GetKeyName(e.Keysym.Sym), e.Keysym.Scancode, e.Keysym.Mod) - + case *sdl.KeyboardEvent: if e.Type == sdl.KEYDOWN { switch e.Keysym.Sym { case sdl.K_ESCAPE: - logger.Info("[LicenseKeyPage.HandleEvent] ESCAPE pressed, isFirstTimeSetup=%v", p.isFirstTimeSetup) // Block escape during first-time setup if p.isFirstTimeSetup { return nil @@ -196,7 +189,6 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error { sdl.StopTextInput() p.App.UnwindToPage(&Settings{}) case sdl.K_RETURN: - logger.Info("[LicenseKeyPage.HandleEvent] RETURN pressed, inputText=%q", p.inputText) // Validate license key - check empty first, then length, then characters cleaned := strings.ReplaceAll(p.inputText, "-", "") if cleaned == "" { @@ -246,10 +238,20 @@ func (p *LicenseKeyPage) HandleEvent(event *sdl.Event) error { cleaned = cleaned[:len(cleaned)-1] } p.inputText = formatLicenseKey(cleaned) - logger.Info("[LicenseKeyPage.HandleEvent] BACKSPACE: inputText=%q", p.inputText) // Clear error when user edits input p.errorMessage = "" } + default: + // Keyboard fallback: if TextInputEvent is not supported, + // convert printable keycodes to characters directly. + // This handles kiosk systems without an input method framework. + if !p.textInputSupported { + if ch := keycodeToChar(e.Keysym.Sym, e.Keysym.Mod); ch != 0 { + p.inputText += string(ch) + p.inputText = formatLicenseKey(p.inputText) + p.errorMessage = "" + } + } } } } @@ -472,7 +474,6 @@ func (p *LicenseKeyPage) Destroy() error { p.errorTextureID = 0 } - logger.Info("[LicenseKeyPage.Destroy] Calling sdl.StopTextInput()") sdl.StopTextInput() return p.Base.Destroy() } diff --git a/internal/page/wifiSetupPage.go b/internal/page/wifiSetupPage.go index 8b675c7..daf4a75 100644 --- a/internal/page/wifiSetupPage.go +++ b/internal/page/wifiSetupPage.go @@ -14,7 +14,6 @@ import ( "rhemawavetech.com/rhemawave/internal/db" "rhemawavetech.com/rhemawave/internal/fontManager" "rhemawavetech.com/rhemawave/internal/keybinds" - "rhemawavetech.com/rhemawave/internal/logger" "rhemawavetech.com/rhemawave/internal/network" "rhemawavetech.com/rhemawave/internal/shaderManager" ) @@ -166,37 +165,24 @@ func (p *WiFiSetupPage) startScan() { // HandleEvent processes user input. func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error { - logger.Info("[WiFiSetupPage.HandleEvent] Event type: %T, isEnteringPassword=%v", (*event), p.isEnteringPassword) - if p.isEnteringPassword && !sdl.IsTextInputActive() { - logger.Info("[WiFiSetupPage.HandleEvent] TextInput not active during password entry, calling enterPasswordMode()") p.enterPasswordMode() } switch e := (*event).(type) { case *sdl.TextInputEvent: if p.isEnteringPassword { - text := e.GetText() if !p.textInputSupported { // First TextInputEvent: the keyboard fallback already // handled this character, so just record that text input // is supported and skip the duplicate append. - logger.Info("[WiFiSetupPage.HandleEvent] TextInputEvent: text=%q, first event — marking textInputSupported=true, skipping duplicate", text) p.textInputSupported = true } else { - p.passwordInput += text - logger.Info("[WiFiSetupPage.HandleEvent] TextInputEvent: text=%q, passwordInput length=%d", text, len(p.passwordInput)) + p.passwordInput += e.GetText() p.updatePasswordTexture() } } case *sdl.KeyboardEvent: - eventType := "KEYDOWN" - if e.Type == sdl.KEYUP { - eventType = "KEYUP" - } - logger.Info("[WiFiSetupPage.HandleEvent] KeyboardEvent: type=%s, sym=%d (%s), scancode=%d, mod=0x%04x", - eventType, e.Keysym.Sym, sdl.GetKeyName(e.Keysym.Sym), e.Keysym.Scancode, e.Keysym.Mod) - if e.Type != sdl.KEYDOWN { return nil } @@ -211,11 +197,8 @@ func (p *WiFiSetupPage) HandleEvent(event *sdl.Event) error { // handlePasswordInput manages keyboard events during password entry. func (p *WiFiSetupPage) handlePasswordInput(sym sdl.Keycode, mod uint16) { - logger.Info("[WiFiSetupPage.handlePasswordInput] sym=%d (%s), mod=0x%04x, textInputSupported=%v", sym, sdl.GetKeyName(sym), mod, p.textInputSupported) - switch sym { case sdl.K_RETURN: - logger.Info("[WiFiSetupPage.handlePasswordInput] RETURN pressed, submitting password (length=%d)", len(p.passwordInput)) p.exitPasswordMode() p.pendingSSID = p.selectedAP.SSID p.isConnecting = true @@ -231,11 +214,9 @@ func (p *WiFiSetupPage) handlePasswordInput(sym sdl.Keycode, mod uint16) { case sdl.K_BACKSPACE: if len(p.passwordInput) > 0 { p.passwordInput = p.passwordInput[:len(p.passwordInput)-1] - logger.Info("[WiFiSetupPage.handlePasswordInput] BACKSPACE: passwordInput length=%d", len(p.passwordInput)) p.updatePasswordTexture() } case sdl.K_ESCAPE: - logger.Info("[WiFiSetupPage.handlePasswordInput] ESCAPE pressed, cancelling password entry") p.isEnteringPassword = false p.pendingSSID = "" p.connectionError = "" @@ -247,10 +228,7 @@ func (p *WiFiSetupPage) handlePasswordInput(sym sdl.Keycode, mod uint16) { if !p.textInputSupported { if ch := keycodeToChar(sym, mod); ch != 0 { p.passwordInput += string(ch) - logger.Info("[WiFiSetupPage.handlePasswordInput] keycodeToChar fallback: char=%q, passwordInput length=%d", string(ch), len(p.passwordInput)) p.updatePasswordTexture() - } else { - logger.Info("[WiFiSetupPage.handlePasswordInput] keycodeToChar fallback: non-printable key, ignored") } } } @@ -258,15 +236,12 @@ func (p *WiFiSetupPage) handlePasswordInput(sym sdl.Keycode, mod uint16) { // enterPasswordMode disables global keybinds and starts SDL text input. func (p *WiFiSetupPage) enterPasswordMode() { - logger.Info("[WiFiSetupPage.enterPasswordMode] Calling StartTextInput(), disabling keybinds") keybinds.Enabled = false sdl.StartTextInput() - logger.Info("[WiFiSetupPage.enterPasswordMode] After StartTextInput: IsTextInputActive=%v", sdl.IsTextInputActive()) } // exitPasswordMode re-enables global keybinds and stops SDL text input. func (p *WiFiSetupPage) exitPasswordMode() { - logger.Info("[WiFiSetupPage.exitPasswordMode] Calling StopTextInput(), re-enabling keybinds") keybinds.Enabled = true sdl.StopTextInput() }