From d5d2b7727fe34a14e6b478bf487cdfb45e7aaac3 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 4 Nov 2020 15:44:53 -0600 Subject: [PATCH] Warn the user if the keyboard service is disabled (#8095) ## Summary of the Pull Request ![kb-service-disabled](https://user-images.githubusercontent.com/18356694/97578533-eb792d80-19be-11eb-9b13-b771327a72a0.png) With this PR, the Terminal will check to make sure the "Touch, Keyboard and Handwriting Panel Service" is enabled at startup. If it isn't, then the Terminal won't be able to receive keyboard input (see #4448 and the 20 linked issues to that one). ## References * See #4448 for more details ## PR Checklist * [x] Closes #7886 * [ ] Should this make #4448 not-open as well? * [x] I work here * [n/a] Tests added/passed * [x] Docs: https://github.com/MicrosoftDocs/terminal/pull/168 ## Validation Steps Performed I manually set the service to "Disabled", restarted the machine, verified the dialog opens (and that I'm unable to type in the Terminal), then re-set the service to automatic and rebooted, and the dialog doesn't appear. --- .../actions/spell-check/dictionary/apis.txt | 1 + src/cascadia/TerminalApp/AppLogic.cpp | 50 ++++++++++++++ src/cascadia/TerminalApp/AppLogic.h | 2 + .../Resources/en-US/Resources.resw | 64 +++++++++-------- src/cascadia/TerminalApp/TerminalPage.cpp | 68 +++++++++++++++++++ src/cascadia/TerminalApp/TerminalPage.h | 5 +- src/cascadia/TerminalApp/TerminalPage.idl | 2 + src/cascadia/TerminalApp/TerminalPage.xaml | 14 ++++ 8 files changed, 178 insertions(+), 28 deletions(-) diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index eef0f5056f..9363cf5137 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -51,6 +51,7 @@ rfind roundf RSHIFT rx +schandle serializer SIZENS spsc diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index fedcbe0594..7418d80ff9 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -470,6 +470,11 @@ namespace winrt::TerminalApp::implementation void AppLogic::_OnLoaded(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) { + const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled(); + if (keyboardServiceIsDisabled) + { + _root->ShowKeyboardServiceWarning(); + } if (FAILED(_settingsLoadedResult)) { const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle"); @@ -482,6 +487,51 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Helper for determining if the "Touch Keyboard and Handwriting Panel + // Service" is enabled. If it isn't, we want to be able to display a + // warning to the user, because they won't be able to type in the + // Terminal. + // Return Value: + // - true if the service is enabled, or if we fail to query the service. We + // return true in that case, to be less noisy (though, that is unexpected) + bool AppLogic::_IsKeyboardServiceEnabled() + { + if (IsUwp()) + { + return true; + } + + // If at any point we fail to open the service manager, the service, + // etc, then just quick return true to disable the dialog. We'd rather + // not be noisy with this dialog if we failed for some reason. + + // Open the service manager. This will return 0 if it failed. + wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) }; + + if (LOG_LAST_ERROR_IF(!hManager.is_valid())) + { + return true; + } + + // Get a handle to the keyboard service + wil::unique_schandle hService{ OpenService(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) }; + if (LOG_LAST_ERROR_IF(!hService.is_valid())) + { + return true; + } + + // Get the current state of the service + SERVICE_STATUS status{ 0 }; + if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status))) + { + return true; + } + + const auto state = status.dwCurrentState; + return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING); + } + // Method Description: // - Get the size in pixels of the client area we'll need to launch this // terminal app. This method will use the default profile's settings to do diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 3487558268..ebd901b44a 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -84,6 +84,8 @@ namespace winrt::TerminalApp::implementation void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); void _ShowLoadWarningsDialog(); + bool _IsKeyboardServiceEnabled(); + void _ShowKeyboardServiceDisabledDialog(); fire_and_forget _LoadErrorsDialogRoutine(); fire_and_forget _ShowLoadWarningsDialogRoutine(); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index c7b08834f5..9c67c78ebb 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@  - @@ -149,6 +149,16 @@ Encountered errors while loading user settings + + OK + + + Warning: + + + The "{0}" isn't running on your machine. This can prevent the Terminal from receiving keyboard input. + {0} will be replaced with the OS-localized name of the TabletInputService + OK diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index d61a9a9ef8..d8129324ca 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2703,6 +2703,74 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Displays a dialog stating the "Touch Keyboard and Handwriting Panel + // Service" is disabled. + void TerminalPage::ShowKeyboardServiceWarning() + { + if (auto presenter{ _dialogPresenter.get() }) + { + presenter.ShowDialog(FindName(L"KeyboardServiceDisabledDialog").try_as()); + } + } + + // Function Description: + // - Helper function to get the OS-localized name for the "Touch Keyboard + // and Handwriting Panel Service". If we can't open up the service for any + // reason, then we'll just return the service's key, "TabletInputService". + // Return Value: + // - The OS-localized name for the TabletInputService + winrt::hstring _getTabletServiceName() + { + auto isUwp = false; + try + { + isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp(); + } + CATCH_LOG(); + + if (isUwp) + { + return winrt::hstring{ TabletInputServiceKey }; + } + + wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) }; + + if (LOG_LAST_ERROR_IF(!hManager.is_valid())) + { + return winrt::hstring{ TabletInputServiceKey }; + } + + DWORD cchBuffer = 0; + GetServiceDisplayName(hManager.get(), TabletInputServiceKey.data(), nullptr, &cchBuffer); + std::wstring buffer; + cchBuffer += 1; // Add space for a null + buffer.resize(cchBuffer); + + if (LOG_LAST_ERROR_IF(!GetServiceDisplayName(hManager.get(), + TabletInputServiceKey.data(), + buffer.data(), + &cchBuffer))) + { + return winrt::hstring{ TabletInputServiceKey }; + } + return winrt::hstring{ buffer }; + } + + // Method Description: + // - Return the fully-formed warning message for the + // "KeyboardServiceDisabled" dialog. This dialog is used to warn the user + // if the keyboard service is disabled, and uses the OS localization for + // the service's actual name. It's bound to the dialog in XAML. + // Return Value: + // - The warning message, including the OS-localized service name. + winrt::hstring TerminalPage::KeyboardServiceDisabledText() + { + const winrt::hstring serviceName{ _getTabletServiceName() }; + const winrt::hstring text{ fmt::format(std::wstring_view(RS_(L"KeyboardServiceWarningText")), serviceName) }; + return text; + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 97d87d4f78..57bbd8c93c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -13,7 +13,7 @@ #include "AppCommandlineArgs.h" static constexpr uint32_t DefaultRowsToScroll{ 3 }; - +static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" }; // fwdecl unittest classes namespace TerminalAppLocalTests { @@ -71,6 +71,9 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::IDialogPresenter DialogPresenter() const; void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); + void ShowKeyboardServiceWarning(); + winrt::hstring KeyboardServiceDisabledText(); + // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 7fb5cb29ac..affaa1a5d6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -26,6 +26,8 @@ namespace TerminalApp // that there's only one application-global dialog visible at a time, // and because of GH#5224. IDialogPresenter DialogPresenter; + void ShowKeyboardServiceWarning(); + String KeyboardServiceDisabledText { get; }; event Windows.Foundation.TypedEventHandler TitleChanged; event Windows.Foundation.TypedEventHandler LastTabClosed; diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index e43a0c8672..4e3e2b5b16 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -83,6 +83,20 @@ the MIT License. See LICENSE in the project root for license information. --> + + + + + +