From 37b17be02400ee491d2b74501ee2c7f39cb5be89 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 11 Sep 2024 10:03:00 +0200 Subject: [PATCH] screen_utils: convert screen_getch() to awaitable --- src/KeyDefPage.cxx | 21 ++++++----- src/dialogs/KeyDialog.cxx | 62 +++++++++++++++++++++++++++++++ src/dialogs/KeyDialog.hxx | 77 +++++++++++++++++++++++++++++++++++++++ src/dialogs/meson.build | 1 + src/screen_utils.cxx | 64 -------------------------------- src/screen_utils.hxx | 4 -- 6 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 src/dialogs/KeyDialog.cxx create mode 100644 src/dialogs/KeyDialog.hxx diff --git a/src/KeyDefPage.cxx b/src/KeyDefPage.cxx index 46c06c88..e352efae 100644 --- a/src/KeyDefPage.cxx +++ b/src/KeyDefPage.cxx @@ -9,10 +9,10 @@ #include "ConfigFile.hxx" #include "Bindings.hxx" #include "GlobalBindings.hxx" -#include "screen_utils.hxx" #include "Options.hxx" #include "page/ListPage.hxx" #include "page/ProxyPage.hxx" +#include "dialogs/KeyDialog.hxx" #include "ui/Bell.hxx" #include "ui/ListText.hxx" #include "ui/TextListRenderer.hxx" @@ -99,7 +99,8 @@ class CommandKeysPage final : public ListPage, ListText { /** * Assigns a new key to a key slot. */ - void OverwriteKey(int key_index); + [[nodiscard]] + Co::InvokeTask OverwriteKey(int key_index) noexcept; /** * Assign a new key to a new slot. @@ -153,23 +154,23 @@ CommandKeysPage::DeleteKey(int key_index) bindings->Check(nullptr, 0); } -void -CommandKeysPage::OverwriteKey(int key_index) +Co::InvokeTask +CommandKeysPage::OverwriteKey(int key_index) noexcept { assert(key_index < MAX_COMMAND_KEYS); const auto prompt = fmt::format(fmt::runtime(_("Enter new key for {}")), get_key_command_name(Command(subcmd))); - const int key = screen_getch(screen, prompt.c_str()); + const int key = co_await KeyDialog{screen, prompt}; if (key == ERR) { Alert(_("Aborted")); - return; + co_return; } if (key == '\0') { Alert(_("Ctrl-Space can't be used")); - return; + co_return; } const Command cmd = bindings->FindKey(key); @@ -178,7 +179,7 @@ CommandKeysPage::OverwriteKey(int key_index) GetLocalizedKeyName(key), get_key_command_name(cmd)); Bell(); - return; + co_return; } binding->keys[key_index] = key; @@ -200,7 +201,7 @@ void CommandKeysPage::AddKey() { if (n_keys < MAX_COMMAND_KEYS) - OverwriteKey(n_keys); + CoStart(OverwriteKey(n_keys)); } std::string_view @@ -258,7 +259,7 @@ CommandKeysPage::OnCommand(struct mpdclient &c, Command cmd) } else { /* just to be sure ;-) */ assert(IsKeyPosition(lw.GetCursorIndex())); - OverwriteKey(PositionToKeyIndex(lw.GetCursorIndex())); + CoStart(OverwriteKey(PositionToKeyIndex(lw.GetCursorIndex()))); } return true; case Command::DELETE: diff --git a/src/dialogs/KeyDialog.cxx b/src/dialogs/KeyDialog.cxx new file mode 100644 index 00000000..97257c15 --- /dev/null +++ b/src/dialogs/KeyDialog.cxx @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "KeyDialog.hxx" +#include "ui/Keys.hxx" +#include "ui/Options.hxx" +#include "ui/Window.hxx" +#include "Styles.hxx" + +using std::string_view_literals::operator""sv; + +void +KeyDialog::OnLeave(const Window window) noexcept +{ + curs_set(0); + + if (ui_options.enable_colors) + window.SetBackgroundStyle(Style::STATUS); +} + +void +KeyDialog::OnCancel() noexcept +{ + SetResult(-1); +} + +void +KeyDialog::Paint(const Window window) const noexcept +{ + if (ui_options.enable_colors) + window.SetBackgroundStyle(Style::INPUT); + + SelectStyle(window, Style::STATUS_ALERT); + window.String({0, 0}, prompt); + window.String(": "sv); + + SelectStyle(window, Style::INPUT); + window.ClearToEol(); + + curs_set(1); +} + +static constexpr bool +IsCancelKey(int key) noexcept +{ + return key == KEY_CANCEL || key == KEY_SCANCEL || + key == KEY_CLOSE || + key == KEY_CTL('C') || + key == KEY_CTL('G') || + key == KEY_ESCAPE; +} + +bool +KeyDialog::OnKey(Window, int key) +{ + if (IsCancelKey(key)) + Cancel(); + else + SetResult(key); + + return true; +} diff --git a/src/dialogs/KeyDialog.hxx b/src/dialogs/KeyDialog.hxx new file mode 100644 index 00000000..46dda32b --- /dev/null +++ b/src/dialogs/KeyDialog.hxx @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#pragma once + +#include "ModalDialog.hxx" +#include "co/AwaitableHelper.hxx" + +#include +#include + +/** + * A #ModalDialog that asks the user to press a key. + * + * This dialog is supposed to be awaited from a coroutine using + * co_await. It suspends the caller while waiting for user input. + */ +class KeyDialog final : public ModalDialog { + const std::string_view prompt; + + std::coroutine_handle<> continuation; + + int result = -2; + + using Awaitable = Co::AwaitableHelper; + friend Awaitable; + +public: + /** + * @param _prompt the human-readable prompt to be displayed + * (including question mark if desired); the pointed-by memory + * is owned by the caller and must remain valid during the + * lifetime of this dialog + */ + KeyDialog(ModalDock &_dock, std::string_view _prompt) noexcept + :ModalDialog(_dock), prompt(_prompt) + { + Show(); + } + + ~KeyDialog() noexcept { + Hide(); + } + + /** + * Await completion of this dialog. + * + * @return an ncurses key code or -1 if the dialog was + * canceled + */ + Awaitable operator co_await() noexcept { + return *this; + } + +private: + void SetResult(int _result) noexcept { + result = _result; + + if (continuation) + continuation.resume(); + } + + bool IsReady() const noexcept { + return result != -2; + } + + int TakeValue() noexcept { + return result; + } + +public: + /* virtual methodds from Modal */ + void OnLeave(Window window) noexcept override; + void OnCancel() noexcept override; + void Paint(Window window) const noexcept override; + bool OnKey(Window window, int key) override; +}; diff --git a/src/dialogs/meson.build b/src/dialogs/meson.build index 61a6e461..e85657c4 100644 --- a/src/dialogs/meson.build +++ b/src/dialogs/meson.build @@ -2,6 +2,7 @@ dialogs = static_library( 'dialogs', 'ModalDialog.cxx', 'YesNoDialog.cxx', + 'KeyDialog.cxx', 'TextInputDialog.cxx', include_directories: inc, dependencies: [ diff --git a/src/screen_utils.cxx b/src/screen_utils.cxx index f6d17dbb..f79d014e 100644 --- a/src/screen_utils.cxx +++ b/src/screen_utils.cxx @@ -4,75 +4,11 @@ #include "screen_utils.hxx" #include "screen.hxx" #include "Styles.hxx" -#include "ui/Options.hxx" -#include "util/ScopeExit.hxx" -#include "config.h" - -#ifndef _WIN32 -#include "WaitUserInput.hxx" -#include -#endif #include using std::string_view_literals::operator""sv; -static constexpr bool -ignore_key(int key) noexcept -{ - return -#ifdef HAVE_GETMOUSE - /* ignore mouse events */ - key == KEY_MOUSE || -#endif - key == ERR; -} - -int -screen_getch(ScreenManager &screen, const char *prompt) noexcept -{ - const auto &window = screen.status_bar.GetWindow(); - - if (ui_options.enable_colors) - window.SetBackgroundStyle(Style::INPUT); - - AtScopeExit(&window) { - if (ui_options.enable_colors) - window.SetBackgroundStyle(Style::STATUS); - }; - - SelectStyle(window, Style::STATUS_ALERT); - window.String({0, 0}, prompt); - window.String(": "sv); - window.ClearToEol(); - - echo(); - curs_set(1); - -#ifndef _WIN32 - WaitUserInput wui; -#endif - - int key; - do { - key = window.GetChar(); - -#ifndef _WIN32 - if (key == ERR && errno == EAGAIN) { - if (wui.Wait()) - continue; - else - break; - } -#endif - } while (ignore_key(key)); - - noecho(); - curs_set(0); - - return key; -} - static const char * CompletionDisplayString(const char *value) noexcept { diff --git a/src/screen_utils.hxx b/src/screen_utils.hxx index 22a3ff80..e2d41b0b 100644 --- a/src/screen_utils.hxx +++ b/src/screen_utils.hxx @@ -8,10 +8,6 @@ class ScreenManager; -/* read a character from the status window */ -int -screen_getch(ScreenManager &screen, const char *prompt) noexcept; - void screen_display_completion_list(ScreenManager &screen, Completion::Range range) noexcept;