private void DestroyFrontend() { Logger.Debug?.Print(LogClass.ServiceAm, $"Destroying software keyboard frontend"); _keyboardRenderer?.Dispose(); _keyboardRenderer = null; if (_dynamicTextInputHandler != null) { _dynamicTextInputHandler.TextChangedEvent -= HandleTextChangedEvent; _dynamicTextInputHandler.KeyPressedEvent -= HandleKeyPressedEvent; _dynamicTextInputHandler.Dispose(); _dynamicTextInputHandler = null; } if (_npads != null) { _npads.NpadButtonDownEvent -= HandleNpadButtonDownEvent; _npads.NpadButtonUpEvent -= HandleNpadButtonUpEvent; _npads = null; } }
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)) { InlineKeyboardRequest request = (InlineKeyboardRequest)reader.ReadUInt32(); InlineKeyboardState state = GetInlineState(); long remaining; Logger.Debug?.Print(LogClass.ServiceAm, $"Keyboard received command {request} in state {state}"); switch (request) { case InlineKeyboardRequest.UseChangedStringV2: Logger.Stub?.Print(LogClass.ServiceAm, "Keyboard response ChangedStringV2"); break; case InlineKeyboardRequest.UseMovedCursorV2: Logger.Stub?.Print(LogClass.ServiceAm, "Keyboard response MovedCursorV2"); 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(state)); 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 tells the Applet to enter the main input handling loop, which will end // with either a text being submitted or a cancel request from the user. // Read the Calc data. SoftwareKeyboardCalc newCalc; remaining = stream.Length - stream.Position; if (remaining != Marshal.SizeOf <SoftwareKeyboardCalc>()) { Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Calc of {remaining} bytes"); newCalc = new SoftwareKeyboardCalc(); } else { var keyboardCalcData = reader.ReadBytes((int)remaining); newCalc = ReadStruct <SoftwareKeyboardCalc>(keyboardCalcData); } // Make the state transition. if (state < InlineKeyboardState.Ready) { // This field consistently is -1 when the calc is not meant to be shown. if (newCalc.Appear.Padding1 == -1) { state = InlineKeyboardState.Initialized; Logger.Debug?.Print(LogClass.ServiceAm, $"Calc during state {state} is probably a dummy"); } else { // Set the new calc _keyboardBackgroundCalc = newCalc; // Check if the application expects UTF8 encoding instead of UTF16. if (_keyboardBackgroundCalc.UseUtf8) { _encoding = Encoding.UTF8; } string newText = _keyboardBackgroundCalc.InputText; uint cursorPosition = (uint)_keyboardBackgroundCalc.CursorPos; _dynamicTextInputHandler?.SetText(newText); state = InlineKeyboardState.Ready; PushChangedString(newText, cursorPosition, state); } SetInlineState(state); } else if (state == InlineKeyboardState.Complete) { state = InlineKeyboardState.Initialized; } // Send the response to the Calc _interactiveSession.Push(InlineResponses.Default(state)); break; case InlineKeyboardRequest.Finalize: // Destroy the dynamic text input handler if (_dynamicTextInputHandler != null) { _dynamicTextInputHandler.TextChanged -= DynamicTextChanged; _dynamicTextInputHandler.Dispose(); } // The calling application wants to close the keyboard applet and will wait for a state change. SetInlineState(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 {state}"); _interactiveSession.Push(InlineResponses.Default(state)); break; } } }