private void OnInteractiveData(object sender, EventArgs e) { // Obtain the validation status response, var data = _interactiveSession.Pop(); if (_state == SoftwareKeyboardState.ValidationPending) { // TODO(jduncantor): // If application rejects our "attempt", submit another attempt, // and put the applet back in PendingValidation state. // For now we assume success, so we push the final result // to the standard output buffer and carry on our merry way. _normalSession.Push(BuildResponse(_textValue, false)); AppletStateChanged?.Invoke(this, null); _state = SoftwareKeyboardState.Complete; } else if (_state == SoftwareKeyboardState.Complete) { // If we have already completed, we push the result text // back on the output buffer and poll the application. _normalSession.Push(BuildResponse(_textValue, false)); AppletStateChanged?.Invoke(this, null); } else { // We shouldn't be able to get here through standard swkbd execution. throw new InvalidOperationException("Software Keyboard is in an invalid state."); } }
private void OnForegroundInteractiveData(byte[] data) { if (_foregroundState == SoftwareKeyboardState.ValidationPending) { // TODO(jduncantor): // If application rejects our "attempt", submit another attempt, // and put the applet back in PendingValidation state. // For now we assume success, so we push the final result // to the standard output buffer and carry on our merry way. PushForegroundResponse(false); AppletStateChanged?.Invoke(this, null); _foregroundState = SoftwareKeyboardState.Complete; } else if (_foregroundState == SoftwareKeyboardState.Complete) { // If we have already completed, we push the result text // back on the output buffer and poll the application. PushForegroundResponse(false); AppletStateChanged?.Invoke(this, null); } else { // We shouldn't be able to get here through standard swkbd execution. throw new InvalidOperationException("Software Keyboard is in an invalid state."); } }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); var transferMemory = _normalSession.Pop(); _keyboardConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); if (_keyboardConfig.UseUtf8) { _encoding = Encoding.UTF8; } _state = SoftwareKeyboardState.Ready; Execute(); return(ResultCode.Success); }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; _alreadyShown = false; _useChangedStringV2 = false; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); if (keyboardConfig.Length == Marshal.SizeOf <SoftwareKeyboardInitialize>()) { // Initialize the keyboard applet in background mode. _isBackground = true; _keyboardBackgroundInitialize = ReadStruct <SoftwareKeyboardInitialize>(keyboardConfig); _backgroundState = InlineKeyboardState.Uninitialized; return(ResultCode.Success); } else { // Initialize the keyboard applet in foreground mode. _isBackground = false; if (keyboardConfig.Length < Marshal.SizeOf <SoftwareKeyboardConfig>()) { Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}"); } else { _keyboardForegroundConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); } if (!_normalSession.TryPop(out _transferMemory)) { Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); } if (_keyboardForegroundConfig.UseUtf8) { _encoding = Encoding.UTF8; } _foregroundState = SoftwareKeyboardState.Ready; ExecuteForegroundKeyboard(); return(ResultCode.Success); } }
private void Execute() { // If the keyboard type is numbers only, we swap to a default // text that only contains numbers. if (_keyboardConfig.Mode == KeyboardMode.NumbersOnly) { _textValue = DefaultNumb; } // If the max string length is 0, we set it to a large default // length. if (_keyboardConfig.StringLengthMax == 0) { _keyboardConfig.StringLengthMax = 100; } // If the game requests a string with a minimum length less // than our default text, repeat our default text until we meet // the minimum length requirement. // This should always be done before the text truncation step. while (_textValue.Length < _keyboardConfig.StringLengthMin) { _textValue = String.Join(" ", _textValue, _textValue); } // If our default text is longer than the allowed length, // we truncate it. if (_textValue.Length > _keyboardConfig.StringLengthMax) { _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); } // Does the application want to validate the text itself? if (_keyboardConfig.CheckText) { // The application needs to validate the response, so we // submit it to the interactive output buffer, and poll it // for validation. Once validated, the application will submit // back a validation status, which is handled in OnInteractiveDataPushIn. _state = SoftwareKeyboardState.ValidationPending; _interactiveSession.Push(BuildResponse(_textValue, true)); } else { // If the application doesn't need to validate the response, // we push the data to the non-interactive output buffer // and poll it for completion. _state = SoftwareKeyboardState.Complete; _normalSession.Push(BuildResponse(_textValue, false)); AppletStateChanged?.Invoke(this, null); } }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); // TODO: A better way would be handling the background creation properly // in LibraryAppleCreator / Acessor instead of guessing by size. if (keyboardConfig.Length == Marshal.SizeOf <SoftwareKeyboardInitialize>()) { _isBackground = true; _keyboardBgInitialize = ReadStruct <SoftwareKeyboardInitialize>(keyboardConfig); _state = SoftwareKeyboardState.Uninitialized; return(ResultCode.Success); } else { _isBackground = false; if (keyboardConfig.Length < Marshal.SizeOf <SoftwareKeyboardConfig>()) { Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}"); } else { _keyboardFgConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); } if (!_normalSession.TryPop(out _transferMemory)) { Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); } if (_keyboardFgConfig.UseUtf8) { _encoding = Encoding.UTF8; } _state = SoftwareKeyboardState.Ready; ExecuteForegroundKeyboard(); return(ResultCode.Success); } }
private void Execute() { // If the keyboard type is numbers only, we swap to a default // text that only contains numbers. if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly) { _textValue = DEFAULT_NUMB; } // If the max string length is 0, we set it to a large default // length. if (_keyboardConfig.StringLengthMax == 0) { _keyboardConfig.StringLengthMax = 100; } // If our default text is longer than the allowed length, // we truncate it. if (_textValue.Length > _keyboardConfig.StringLengthMax) { _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); } if (!_keyboardConfig.CheckText) { // If the application doesn't need to validate the response, // we push the data to the non-interactive output buffer // and poll it for completion. _state = SoftwareKeyboardState.Complete; _normalSession.Push(BuildResponse(_textValue, false)); AppletStateChanged?.Invoke(this, null); } else { // The application needs to validate the response, so we // submit it to the interactive output buffer, and poll it // for validation. Once validated, the application will submit // back a validation status, which is handled in OnInteractiveDataPushIn. _state = SoftwareKeyboardState.ValidationPending; _interactiveSession.Push(BuildResponse(_textValue, true)); } }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); if (keyboardConfig.Length < Marshal.SizeOf <SoftwareKeyboardConfig>()) { Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}"); } else { _keyboardConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); } if (!_normalSession.TryPop(out _transferMemory)) { Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); } if (_keyboardConfig.UseUtf8) { _encoding = Encoding.UTF8; } _state = SoftwareKeyboardState.Ready; Execute(); return(ResultCode.Success); }
private void Execute() { string initialText = null; // Initial Text is always encoded as a UTF-16 string in the work buffer (passed as transfer memory) // InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters if (_transferMemory != null && _keyboardConfig.InitialStringLength > 0) { initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardConfig.InitialStringOffset, 2 * _keyboardConfig.InitialStringLength); } // If the max string length is 0, we set it to a large default // length. if (_keyboardConfig.StringLengthMax == 0) { _keyboardConfig.StringLengthMax = 100; } var args = new SoftwareKeyboardUiArgs { HeaderText = _keyboardConfig.HeaderText, SubtitleText = _keyboardConfig.SubtitleText, GuideText = _keyboardConfig.GuideText, SubmitText = (!string.IsNullOrWhiteSpace(_keyboardConfig.SubmitText) ? _keyboardConfig.SubmitText : "OK"), StringLengthMin = _keyboardConfig.StringLengthMin, StringLengthMax = _keyboardConfig.StringLengthMax, InitialText = initialText }; // Call the configured GUI handler to get user's input if (_device.UiHandler == null) { Logger.Warning?.Print(LogClass.Application, $"GUI Handler is not set. Falling back to default"); _okPressed = true; } else { _okPressed = _device.UiHandler.DisplayInputDialog(args, out _textValue); } _textValue ??= initialText ?? DefaultText; // If the game requests a string with a minimum length less // than our default text, repeat our default text until we meet // the minimum length requirement. // This should always be done before the text truncation step. while (_textValue.Length < _keyboardConfig.StringLengthMin) { _textValue = String.Join(" ", _textValue, _textValue); } // If our default text is longer than the allowed length, // we truncate it. if (_textValue.Length > _keyboardConfig.StringLengthMax) { _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); } // Does the application want to validate the text itself? if (_keyboardConfig.CheckText) { // The application needs to validate the response, so we // submit it to the interactive output buffer, and poll it // for validation. Once validated, the application will submit // back a validation status, which is handled in OnInteractiveDataPushIn. _state = SoftwareKeyboardState.ValidationPending; _interactiveSession.Push(BuildResponse(_textValue, true)); } else { // If the application doesn't need to validate the response, // we push the data to the non-interactive output buffer // and poll it for completion. _state = SoftwareKeyboardState.Complete; _normalSession.Push(BuildResponse(_textValue, false)); AppletStateChanged?.Invoke(this, null); } }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); if (keyboardConfig.Length == Marshal.SizeOf <SoftwareKeyboardInitialize>()) { // Initialize the keyboard applet in background mode. _isBackground = true; _keyboardBackgroundInitialize = ReadStruct <SoftwareKeyboardInitialize>(keyboardConfig); InlineKeyboardState state = InlineKeyboardState.Uninitialized; SetInlineState(state); string acceptKeyName; string cancelKeyName; if (_device.UiHandler != null) { _dynamicTextInputHandler = _device.UiHandler.CreateDynamicTextInputHandler(); _dynamicTextInputHandler.TextChanged += DynamicTextChanged; acceptKeyName = _dynamicTextInputHandler.AcceptKeyName; cancelKeyName = _dynamicTextInputHandler.CancelKeyName; } else { Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly"); acceptKeyName = ""; cancelKeyName = ""; } _keyboardRenderer = new SoftwareKeyboardRenderer(acceptKeyName, cancelKeyName); _interactiveSession.Push(InlineResponses.FinishedInitialize(state)); return(ResultCode.Success); } else { // Initialize the keyboard applet in foreground mode. _isBackground = false; if (keyboardConfig.Length < Marshal.SizeOf <SoftwareKeyboardConfig>()) { Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}"); } else { _keyboardForegroundConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); } if (!_normalSession.TryPop(out _transferMemory)) { Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); } if (_keyboardForegroundConfig.UseUtf8) { _encoding = Encoding.UTF8; } _foregroundState = SoftwareKeyboardState.Ready; ExecuteForegroundKeyboard(); return(ResultCode.Success); } }
private void OnBackgroundInteractiveData(byte[] data) { // WARNING: Only invoke applet state changes after an explicit finalization // request from the game, this is because the inline keyboard is expected to // keep running in the background sending data by itself. using (MemoryStream stream = new MemoryStream(data)) using (BinaryReader reader = new BinaryReader(stream)) { var request = (InlineKeyboardRequest)reader.ReadUInt32(); long remaining; // Always show the keyboard if the state is 'Ready'. bool showKeyboard = _state == SoftwareKeyboardState.Ready; switch (request) { case InlineKeyboardRequest.Unknown0: // Unknown request sent by some games after calc _interactiveSession.Push(InlineResponses.Default()); break; case InlineKeyboardRequest.UseChangedStringV2: // Not used because we only send the entire string after confirmation. _interactiveSession.Push(InlineResponses.Default()); break; case InlineKeyboardRequest.UseMovedCursorV2: // Not used because we only send the entire string after confirmation. _interactiveSession.Push(InlineResponses.Default()); break; case InlineKeyboardRequest.SetCustomizeDic: remaining = stream.Length - stream.Position; if (remaining != Marshal.SizeOf <SoftwareKeyboardDictSet>()) { Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes!"); } else { var keyboardDictData = reader.ReadBytes((int)remaining); _keyboardDict = ReadStruct <SoftwareKeyboardDictSet>(keyboardDictData); } _interactiveSession.Push(InlineResponses.Default()); break; case InlineKeyboardRequest.Calc: // Put the keyboard in a Ready state, this will force showing _state = SoftwareKeyboardState.Ready; remaining = stream.Length - stream.Position; if (remaining != Marshal.SizeOf <SoftwareKeyboardCalc>()) { Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Calc of {remaining} bytes!"); } else { var keyboardCalcData = reader.ReadBytes((int)remaining); _keyboardCalc = ReadStruct <SoftwareKeyboardCalc>(keyboardCalcData); if (_keyboardCalc.Utf8Mode == 0x1) { _encoding = Encoding.UTF8; } // Force showing the keyboard regardless of the state, an unwanted // input dialog may show, but it is better than a soft lock. if (_keyboardCalc.Appear.ShouldBeHidden == 0) { showKeyboard = true; } } // Send an initialization finished signal. _interactiveSession.Push(InlineResponses.FinishedInitialize()); // Start a task with the GUI handler to get user's input. new Task(() => { bool submit = true; string inputText = (!string.IsNullOrWhiteSpace(_keyboardCalc.InputText) ? _keyboardCalc.InputText : DefaultText); // Call the configured GUI handler to get user's input. if (!showKeyboard) { // Submit the default text to avoid soft locking if the keyboard was ignored by // accident. It's better to change the name than being locked out of the game. submit = true; inputText = DefaultText; Logger.Debug?.Print(LogClass.Application, "Received a dummy Calc, keyboard will not be shown"); } else if (_device.UiHandler == null) { Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default"); } else { var args = new SoftwareKeyboardUiArgs { HeaderText = "", // The inline keyboard lacks these texts SubtitleText = "", GuideText = "", SubmitText = (!string.IsNullOrWhiteSpace(_keyboardCalc.Appear.OkText) ? _keyboardCalc.Appear.OkText : "OK"), StringLengthMin = 0, StringLengthMax = 100, InitialText = inputText }; submit = _device.UiHandler.DisplayInputDialog(args, out inputText); } if (submit) { Logger.Debug?.Print(LogClass.ServiceAm, "Sending keyboard OK..."); if (_encoding == Encoding.UTF8) { _interactiveSession.Push(InlineResponses.DecidedEnterUtf8(inputText)); } else { _interactiveSession.Push(InlineResponses.DecidedEnter(inputText)); } } else { Logger.Debug?.Print(LogClass.ServiceAm, "Sending keyboard Cancel..."); _interactiveSession.Push(InlineResponses.DecidedCancel()); } // TODO: Why is this necessary? Does the software expect a constant stream of responses? Thread.Sleep(500); Logger.Debug?.Print(LogClass.ServiceAm, "Resetting state of the keyboard..."); _interactiveSession.Push(InlineResponses.Default()); }).Start(); break; case InlineKeyboardRequest.Finalize: // The game wants to close the keyboard applet and will wait for a state change. _state = SoftwareKeyboardState.Uninitialized; AppletStateChanged?.Invoke(this, null); break; default: // We shouldn't be able to get here through standard swkbd execution. Logger.Error?.Print(LogClass.ServiceAm, $"Invalid Software Keyboard request {request} during state {_state}!"); _interactiveSession.Push(InlineResponses.Default()); break; } } }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { lock (_lock) { _normalSession = normalSession; _interactiveSession = interactiveSession; _interactiveSession.DataAvailable += OnInteractiveData; var launchParams = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop(); _isBackground = keyboardConfig.Length == Marshal.SizeOf <SoftwareKeyboardInitialize>(); if (_isBackground) { // Initialize the keyboard applet in background mode. _keyboardBackgroundInitialize = ReadStruct <SoftwareKeyboardInitialize>(keyboardConfig); _backgroundState = InlineKeyboardState.Uninitialized; if (_device.UiHandler == null) { Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly"); } else { // Create a text handler that converts keyboard strokes to strings. _dynamicTextInputHandler = _device.UiHandler.CreateDynamicTextInputHandler(); _dynamicTextInputHandler.TextChangedEvent += HandleTextChangedEvent; _dynamicTextInputHandler.KeyPressedEvent += HandleKeyPressedEvent; _npads = new NpadReader(_device); _npads.NpadButtonDownEvent += HandleNpadButtonDownEvent; _npads.NpadButtonUpEvent += HandleNpadButtonUpEvent; _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UiHandler.HostUiTheme); } return(ResultCode.Success); } else { // Initialize the keyboard applet in foreground mode. if (keyboardConfig.Length < Marshal.SizeOf <SoftwareKeyboardConfig>()) { Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}"); } else { _keyboardForegroundConfig = ReadStruct <SoftwareKeyboardConfig>(keyboardConfig); } if (!_normalSession.TryPop(out _transferMemory)) { Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); } if (_keyboardForegroundConfig.UseUtf8) { _encoding = Encoding.UTF8; } _foregroundState = SoftwareKeyboardState.Ready; ExecuteForegroundKeyboard(); return(ResultCode.Success); } } }