Fix text input on kiosk: add keycodeToChar fallback for email/license pages
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8b0cf81e85
commit
df5e64d38a
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user