unsafe public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; byte[] launchParams = _normalSession.Pop(); byte[] controllerSupportArgPrivate = _normalSession.Pop(); ControllerSupportArgPrivate privateArg = IApplet.ReadStruct <ControllerSupportArgPrivate>(controllerSupportArgPrivate); Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode}" + $"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}"); if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport) { _normalSession.Push(BuildResponse()); // Dummy response for other modes AppletStateChanged?.Invoke(this, null); return(ResultCode.Success); } byte[] controllerSupportArg = _normalSession.Pop(); ControllerSupportArgHeader argHeader; if (privateArg.ArgSize == Marshal.SizeOf <ControllerSupportArg>()) { ControllerSupportArg arg = IApplet.ReadStruct <ControllerSupportArg>(controllerSupportArg); argHeader = arg.Header; // Read enable text here? } else { Logger.PrintStub(LogClass.ServiceHid, $"Unknown revision of ControllerSupportArg."); argHeader = IApplet.ReadStruct <ControllerSupportArgHeader>(controllerSupportArg); // Read just the header } Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {argHeader.PlayerCountMin} {argHeader.PlayerCountMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}"); // Currently, the only purpose of this applet is to help // choose the primary input controller for the game // TODO: Ideally should hook back to HID.Controller. When applet is called, can choose appropriate controller and attach to appropriate id. if (argHeader.PlayerCountMin > 1) { Logger.PrintWarning(LogClass.ServiceHid, "More than one controller was requested."); } ControllerSupportResultInfo result = new ControllerSupportResultInfo { PlayerCount = 1, SelectedId = (uint)GetNpadIdTypeFromIndex(_system.Device.Hid.Npads.PrimaryController) }; Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}"); _normalSession.Push(BuildResponse(result)); AppletStateChanged?.Invoke(this, null); 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); } }
private void PushForegroundResponse(bool interactive) { int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize; using (MemoryStream stream = new MemoryStream(new byte[bufferSize])) using (BinaryWriter writer = new BinaryWriter(stream)) { byte[] output = _encoding.GetBytes(_textValue); if (!interactive) { // Result Code. writer.Write(_lastResult == KeyboardResult.Accept ? 0U : 1U); } else { // In interactive mode, we write the length of the text as a long, rather than // a result code. This field is inclusive of the 64-bit size. writer.Write((long)output.Length + 8); } writer.Write(output); if (!interactive) { _normalSession.Push(stream.ToArray()); } else { _interactiveSession.Push(stream.ToArray()); } } }
// PushInteractiveInData(object<nn::am::service::IStorage>) public ResultCode PushInteractiveInData(ServiceCtx context) { IStorage data = GetObject <IStorage>(context, 0); _interactiveSession.Push(data.Data); 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; _commonArguments = IApplet.ReadStruct <CommonArguments>(_normalSession.Pop()); Logger.Stub?.PrintStub(LogClass.ServiceAm, $"WebApplet version: 0x{_commonArguments.AppletVersion:x8}"); ReadOnlySpan <byte> webArguments = _normalSession.Pop(); (_shimKind, _arguments) = BrowserArgument.ParseArguments(webArguments); Logger.Stub?.PrintStub(LogClass.ServiceAm, $"Web Arguments: {_arguments.Count}"); foreach (BrowserArgument argument in _arguments) { Logger.Stub?.PrintStub(LogClass.ServiceAm, $"{argument.Type}: {argument.GetValue()}"); } if ((_commonArguments.AppletVersion >= 0x80000 && _shimKind == ShimKind.Web) || (_commonArguments.AppletVersion >= 0x30000 && _shimKind == ShimKind.Share)) { List <BrowserOutput> result = new List <BrowserOutput>(); result.Add(new BrowserOutput(BrowserOutputType.ExitReason, (uint)WebExitReason.ExitButton)); _normalSession.Push(BuildResponseNew(result)); } else { WebCommonReturnValue result = new WebCommonReturnValue() { ExitReason = WebExitReason.ExitButton, }; _normalSession.Push(BuildResponseOld(result)); } AppletStateChanged?.Invoke(this, null); return(ResultCode.Success); }
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; // TODO(jduncanator): Parse PlayerSelectConfig from input data _normalSession.Push(BuildResponse()); AppletStateChanged?.Invoke(this, null); 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); } }
unsafe public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; _interactiveSession = interactiveSession; var _ = _normalSession.Pop(); // unknown var controllerSupportArgPrivate = _normalSession.Pop(); var c = ReadStruct <ControllerSupportArgPrivate>(controllerSupportArgPrivate); Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {c.PrivateSize} {c.ArgSize} {c.Mode}" + $"HoldType:{(HidJoyHoldType)c.NpadJoyHoldType} StyleSets:{(ControllerType)c.NpadStyleSet}"); if (c.Mode != ControllerSupportMode.ShowControllerSupport) { _normalSession.Push(BuildResponse()); // Dummy response for other modes AppletStateChanged?.Invoke(this, null); return(ResultCode.Success); } var controllerSupportArg = _normalSession.Pop(); ControllerSupportArgHeader h; if (c.ArgSize == Marshal.SizeOf <ControllerSupportArg>()) { var arg = ReadStruct <ControllerSupportArg>(controllerSupportArg); h = arg.Header; // Read enable text here? } else { Logger.PrintStub(LogClass.ServiceHid, $"Unknown revision of ControllerSupportArg."); h = ReadStruct <ControllerSupportArgHeader>(controllerSupportArg); // Read just the header } Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {h.PlayerCountMin} {h.PlayerCountMax} {h.EnableTakeOverConnection}"); // Currently, the only purpose of this applet is to help // choose the primary input controller for the game // TODO: Ideally should hook back to HID.Controller. When applet is called, can choose appropriate controller and attach to appropriate id. if (h.PlayerCountMin > 1) { Logger.PrintWarning(LogClass.ServiceHid, "Game requested more than 1 controller!"); } var result = new ControllerSupportResultInfo { PlayerCount = 1, SelectedId = (uint)HLE.HOS.Services.Hid.HidServer.HidUtils.GetNpadIdTypeFromIndex(_system.Device.Hid.Npads.PrimaryControllerId) }; Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}"); _normalSession.Push(BuildResponse(result)); AppletStateChanged?.Invoke(this, null); return(ResultCode.Success); }
unsafe public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) { _normalSession = normalSession; byte[] launchParams = _normalSession.Pop(); byte[] controllerSupportArgPrivate = _normalSession.Pop(); ControllerSupportArgPrivate privateArg = IApplet.ReadStruct <ControllerSupportArgPrivate>(controllerSupportArgPrivate); Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode} " + $"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}"); if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport) { _normalSession.Push(BuildResponse()); // Dummy response for other modes AppletStateChanged?.Invoke(this, null); return(ResultCode.Success); } byte[] controllerSupportArg = _normalSession.Pop(); ControllerSupportArgHeader argHeader; if (privateArg.ArgSize == Marshal.SizeOf <ControllerSupportArgV7>()) { ControllerSupportArgV7 arg = IApplet.ReadStruct <ControllerSupportArgV7>(controllerSupportArg); argHeader = arg.Header; Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version 7 EnableExplainText={arg.EnableExplainText != 0}"); // Read enable text here? } else if (privateArg.ArgSize == Marshal.SizeOf <ControllerSupportArgVPre7>()) { ControllerSupportArgVPre7 arg = IApplet.ReadStruct <ControllerSupportArgVPre7>(controllerSupportArg); argHeader = arg.Header; Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Pre-7 EnableExplainText={arg.EnableExplainText != 0}"); // Read enable text here? } else { Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Unknown"); argHeader = IApplet.ReadStruct <ControllerSupportArgHeader>(controllerSupportArg); // Read just the header } int playerMin = argHeader.PlayerCountMin; int playerMax = argHeader.PlayerCountMax; Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {playerMin} {playerMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}"); int configuredCount = 0; PlayerIndex primaryIndex = PlayerIndex.Unknown; while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex)) { ControllerAppletUiArgs uiArgs = new ControllerAppletUiArgs { PlayerCountMin = playerMin, PlayerCountMax = playerMax, SupportedStyles = (ControllerType)privateArg.NpadStyleSet, SupportedPlayers = _system.Device.Hid.Npads.GetSupportedPlayers(), IsDocked = _system.State.DockedMode }; if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs)) { break; } } ControllerSupportResultInfo result = new ControllerSupportResultInfo { PlayerCount = (sbyte)configuredCount, SelectedId = (uint)GetNpadIdTypeFromIndex(primaryIndex) }; Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}"); _normalSession.Push(BuildResponse(result)); AppletStateChanged?.Invoke(this, null); _system.ReturnFocus(); return(ResultCode.Success); }
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; Logger.Debug?.Print(LogClass.ServiceAm, $"Keyboard received command {request} in state {_backgroundState}"); switch (request) { case InlineKeyboardRequest.UseChangedStringV2: Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseChangedStringV2"); break; case InlineKeyboardRequest.UseMovedCursorV2: Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseMovedCursorV2"); break; case InlineKeyboardRequest.SetUserWordInfo: // Read the user word info data. remaining = stream.Length - stream.Position; if (remaining < sizeof(int)) { Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info of {remaining} bytes"); } else { int wordsCount = reader.ReadInt32(); int wordSize = Marshal.SizeOf <SoftwareKeyboardUserWord>(); remaining = stream.Length - stream.Position; if (wordsCount > MaxUserWords) { Logger.Warning?.Print(LogClass.ServiceAm, $"Received {wordsCount} User Words but the maximum is {MaxUserWords}"); } else if (wordsCount * wordSize != remaining) { Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info data of {remaining} bytes for {wordsCount} words"); } else { _keyboardBackgroundUserWords = new SoftwareKeyboardUserWord[wordsCount]; for (int word = 0; word < wordsCount; word++) { byte[] wordData = reader.ReadBytes(wordSize); _keyboardBackgroundUserWords[word] = ReadStruct <SoftwareKeyboardUserWord>(wordData); } } } _interactiveSession.Push(InlineResponses.ReleasedUserWordInfo(_backgroundState)); break; case InlineKeyboardRequest.SetCustomizeDic: // Read the custom dic data. remaining = stream.Length - stream.Position; if (remaining != Marshal.SizeOf <SoftwareKeyboardCustomizeDic>()) { Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes"); } else { var keyboardDicData = reader.ReadBytes((int)remaining); _keyboardBackgroundDic = ReadStruct <SoftwareKeyboardCustomizeDic>(keyboardDicData); } break; case InlineKeyboardRequest.SetCustomizedDictionaries: // Read the custom dictionaries data. remaining = stream.Length - stream.Position; if (remaining != Marshal.SizeOf <SoftwareKeyboardDictSet>()) { Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes"); } else { var keyboardDictData = reader.ReadBytes((int)remaining); _keyboardBackgroundDictSet = ReadStruct <SoftwareKeyboardDictSet>(keyboardDictData); } break; case InlineKeyboardRequest.Calc: // The Calc request is used to communicate configuration changes and commands to the keyboard. // Fields in the Calc struct and operations are masked by the Flags field. // Read the Calc data. SoftwareKeyboardCalcEx newCalc; remaining = stream.Length - stream.Position; if (remaining == Marshal.SizeOf <SoftwareKeyboardCalc>()) { var keyboardCalcData = reader.ReadBytes((int)remaining); var keyboardCalc = ReadStruct <SoftwareKeyboardCalc>(keyboardCalcData); newCalc = keyboardCalc.ToExtended(); } else if (remaining == Marshal.SizeOf <SoftwareKeyboardCalcEx>() || remaining == SoftwareKeyboardCalcEx.AlternativeSize) { var keyboardCalcData = reader.ReadBytes((int)remaining); newCalc = ReadStruct <SoftwareKeyboardCalcEx>(keyboardCalcData); } else { Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Calc of {remaining} bytes"); newCalc = new SoftwareKeyboardCalcEx(); } // Process each individual operation specified in the flags. bool updateText = false; if ((newCalc.Flags & KeyboardCalcFlags.Initialize) != 0) { _interactiveSession.Push(InlineResponses.FinishedInitialize(_backgroundState)); _backgroundState = InlineKeyboardState.Initialized; } if ((newCalc.Flags & KeyboardCalcFlags.SetCursorPos) != 0) { _cursorBegin = newCalc.CursorPos; updateText = true; Logger.Debug?.Print(LogClass.ServiceAm, $"Cursor position set to {_cursorBegin}"); } if ((newCalc.Flags & KeyboardCalcFlags.SetInputText) != 0) { _textValue = newCalc.InputText; updateText = true; Logger.Debug?.Print(LogClass.ServiceAm, $"Input text set to {_textValue}"); } if ((newCalc.Flags & KeyboardCalcFlags.SetUtf8Mode) != 0) { _encoding = newCalc.UseUtf8 ? Encoding.UTF8 : Encoding.Default; Logger.Debug?.Print(LogClass.ServiceAm, $"Encoding set to {_encoding}"); } if (updateText) { _dynamicTextInputHandler.SetText(_textValue, _cursorBegin); _keyboardRenderer.UpdateTextState(_textValue, _cursorBegin, _cursorBegin, null, null); } if ((newCalc.Flags & KeyboardCalcFlags.MustShow) != 0) { ActivateFrontend(); _backgroundState = InlineKeyboardState.Shown; PushChangedString(_textValue, (uint)_cursorBegin, _backgroundState); } // Send the response to the Calc _interactiveSession.Push(InlineResponses.Default(_backgroundState)); break; case InlineKeyboardRequest.Finalize: // Destroy the frontend. DestroyFrontend(); // The calling application wants to close the keyboard applet and will wait for a state change. _backgroundState = InlineKeyboardState.Uninitialized; AppletStateChanged?.Invoke(this, null); break; default: // We shouldn't be able to get here through standard swkbd execution. Logger.Warning?.Print(LogClass.ServiceAm, $"Invalid Software Keyboard request {request} during state {_backgroundState}"); _interactiveSession.Push(InlineResponses.Default(_backgroundState)); break; } } }