Beispiel #1
0
        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);
        }
Beispiel #2
0
        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));
            }
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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);
        }
Beispiel #8
0
        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);
            }
        }
Beispiel #9
0
        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);
        }
Beispiel #10
0
        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;
                    }
                }
        }