From 801f902bea43353ea00bd70de959f499021b87d8 Mon Sep 17 00:00:00 2001 From: Street Pea Date: Sat, 10 Dec 2022 15:09:43 +0100 Subject: [PATCH] Add transform/scaling modes to GUI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added zoom and stretch modes to GUI to mirror the transform modes available on Android. They are reachable through a context menu or shortcuts (Ctrl+S/Ctrl+Z). CLI options --stretch and --zoom have been added as well. Co-authored-by: Florian Märkl --- gui/include/avopenglwidget.h | 11 +++- gui/include/streamsession.h | 13 ++++- gui/include/streamwindow.h | 6 ++ gui/include/transformmode.h | 12 ++++ gui/src/avopenglwidget.cpp | 36 ++++++++++-- gui/src/main.cpp | 24 +++++++- gui/src/mainwindow.cpp | 9 ++- gui/src/streamsession.cpp | 15 ++++- gui/src/streamwindow.cpp | 109 ++++++++++++++++++++++++++++++----- 9 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 gui/include/transformmode.h diff --git a/gui/include/avopenglwidget.h b/gui/include/avopenglwidget.h index 6f234e9..19bd287 100644 --- a/gui/include/avopenglwidget.h +++ b/gui/include/avopenglwidget.h @@ -3,6 +3,8 @@ #ifndef CHIAKI_AVOPENGLWIDGET_H #define CHIAKI_AVOPENGLWIDGET_H +#include "transformmode.h" + #include #include @@ -74,21 +76,24 @@ class AVOpenGLWidget: public QOpenGLWidget public: static QSurfaceFormat CreateSurfaceFormat(); - explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr); + explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit); ~AVOpenGLWidget() override; void SwapFrames(); AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; } + void SetTransformMode(TransformMode mode) { transform_mode = mode; } + TransformMode GetTransformMode() const { return transform_mode; } + protected: + TransformMode transform_mode; void mouseMoveEvent(QMouseEvent *event) override; void initializeGL() override; void paintGL() override; - private slots: - void ResetMouseTimeout(); public slots: + void ResetMouseTimeout(); void HideMouse(); }; diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h index cb1397e..e139f45 100644 --- a/gui/include/streamsession.h +++ b/gui/include/streamsession.h @@ -20,6 +20,7 @@ #include "sessionlog.h" #include "controllermanager.h" #include "settings.h" +#include "transformmode.h" #include #include @@ -53,9 +54,17 @@ struct StreamSessionConnectInfo ChiakiConnectVideoProfile video_profile; unsigned int audio_buffer_size; bool fullscreen; + TransformMode transform_mode; bool enable_keyboard; - StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen); + StreamSessionConnectInfo( + Settings *settings, + ChiakiTarget target, + QString host, + QByteArray regist_key, + QByteArray morning, + bool fullscreen, + TransformMode transform_mode); }; class StreamSession : public QObject @@ -124,7 +133,7 @@ class StreamSession : public QObject #endif void HandleKeyboardEvent(QKeyEvent *event); - void HandleMouseEvent(QMouseEvent *event); + bool HandleMouseEvent(QMouseEvent *event); signals: void FfmpegFrameAvailable(); diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index 5c526b0..fd91d13 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -22,10 +22,14 @@ class StreamWindow: public QMainWindow const StreamSessionConnectInfo connect_info; StreamSession *session; + QAction *fullscreen_action; + QAction *stretch_action; + QAction *zoom_action; AVOpenGLWidget *av_widget; void Init(); void UpdateVideoTransform(); + void UpdateTransformModeActions(); protected: void keyPressEvent(QKeyEvent *event) override; @@ -42,6 +46,8 @@ class StreamWindow: public QMainWindow void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); void LoginPINRequested(bool incorrect); void ToggleFullscreen(); + void ToggleStretch(); + void ToggleZoom(); }; #endif // CHIAKI_GUI_STREAMWINDOW_H diff --git a/gui/include/transformmode.h b/gui/include/transformmode.h new file mode 100644 index 0000000..5c01d87 --- /dev/null +++ b/gui/include/transformmode.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL + +#ifndef CHIAKI_TRANSFORMMODE_H +#define CHIAKI_TRANSFORMMODE_H + +enum class TransformMode { + Fit, + Zoom, + Stretch +}; + +#endif diff --git a/gui/src/avopenglwidget.cpp b/gui/src/avopenglwidget.cpp index 857af09..bfd184a 100644 --- a/gui/src/avopenglwidget.cpp +++ b/gui/src/avopenglwidget.cpp @@ -122,9 +122,9 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat() return format; } -AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent) +AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent, TransformMode transform_mode) : QOpenGLWidget(parent), - session(session) + session(session), transform_mode(transform_mode) { enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder()); conversion_config = nullptr; @@ -381,10 +381,10 @@ void AVOpenGLWidget::paintGL() vp_width = widget_width; vp_height = widget_height; } - else + else if(transform_mode == TransformMode::Fit) { float aspect = (float)frame->width / (float)frame->height; - if(aspect < (float)widget_width / (float)widget_height) + if(widget_height && aspect < (float)widget_width / (float)widget_height) { vp_height = widget_height; vp_width = (GLsizei)(vp_height * aspect); @@ -395,6 +395,34 @@ void AVOpenGLWidget::paintGL() vp_height = (GLsizei)(vp_width / aspect); } } + else if(transform_mode == TransformMode::Zoom) + { + float aspect = (float)frame->width / (float)frame->height; + if(widget_height && aspect < (float)widget_width / (float)widget_height) + { + vp_width = widget_width; + vp_height = (GLsizei)(vp_width / aspect); + } + else + { + vp_height = widget_height; + vp_width = (GLsizei)(vp_height * aspect); + } + } + else // transform_mode == TransformMode::Stretch + { + float aspect = (float)frame->width / (float)frame->height; + if(widget_height && aspect < (float)widget_width / (float)widget_height) + { + vp_height = widget_height; + vp_width = widget_width; + } + else + { + vp_width = widget_width; + vp_height = widget_height; + } + } f->glViewport((widget_width - vp_width) / 2, (widget_height - vp_height) / 2, vp_width, vp_height); diff --git a/gui/src/main.cpp b/gui/src/main.cpp index e6597cf..5f2aad7 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -103,9 +103,15 @@ int real_main(int argc, char *argv[]) QCommandLineOption morning_option("morning", "", "morning"); parser.addOption(morning_option); - QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen (only for use with stream command)"); + QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen mode [maintains aspect ratio, adds black bars to fill unsused parts of screen if applicable] (only for use with stream command)"); parser.addOption(fullscreen_option); + QCommandLineOption zoom_option("zoom", "Start window in fullscreen zoomed in to fit screen [maintains aspect ratio, cutting off edges of image to fill screen] (only for use with stream command)"); + parser.addOption(zoom_option); + + QCommandLineOption stretch_option("stretch", "Start window in fullscreen stretched to fit screen [distorts aspect ratio to fill screen] (only for use with stream command)"); + parser.addOption(stretch_option); + parser.process(app); QStringList args = parser.positionalArguments(); @@ -174,7 +180,21 @@ int real_main(int argc, char *argv[]) return 1; } } - StreamSessionConnectInfo connect_info(&settings, target, host, regist_key, morning, parser.isSet(fullscreen_option)); + if ((parser.isSet(stretch_option) && (parser.isSet(zoom_option) || parser.isSet(fullscreen_option))) || (parser.isSet(zoom_option) && parser.isSet(fullscreen_option))) + { + printf("Must choose between fullscreen, zoom or stretch option."); + return 1; + } + + StreamSessionConnectInfo connect_info( + &settings, + target, + host, + regist_key, + morning, + parser.isSet(fullscreen_option), + parser.isSet(zoom_option) ? TransformMode::Zoom : parser.isSet(stretch_option) ? TransformMode::Stretch : TransformMode::Fit); + return RunStream(app, connect_info); } #ifdef CHIAKI_ENABLE_CLI diff --git a/gui/src/mainwindow.cpp b/gui/src/mainwindow.cpp index aa76ab3..c3b1b6a 100644 --- a/gui/src/mainwindow.cpp +++ b/gui/src/mainwindow.cpp @@ -249,7 +249,14 @@ void MainWindow::ServerItemWidgetTriggered() } QString host = server.GetHostAddr(); - StreamSessionConnectInfo info(settings, server.registered_host.GetTarget(), host, server.registered_host.GetRPRegistKey(), server.registered_host.GetRPKey(), false); + StreamSessionConnectInfo info( + settings, + server.registered_host.GetTarget(), + host, + server.registered_host.GetRPRegistKey(), + server.registered_host.GetRPKey(), + false, + TransformMode::Fit); new StreamWindow(info); } else diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index 3f46070..f5b7ace 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -14,7 +14,14 @@ #define SETSU_UPDATE_INTERVAL_MS 4 -StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen) +StreamSessionConnectInfo::StreamSessionConnectInfo( + Settings *settings, + ChiakiTarget target, + QString host, + QByteArray regist_key, + QByteArray morning, + bool fullscreen, + TransformMode transform_mode) : settings(settings) { key_map = settings->GetControllerMappingForDecoding(); @@ -30,6 +37,7 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar this->morning = morning; audio_buffer_size = settings->GetAudioBufferSize(); this->fullscreen = fullscreen; + this->transform_mode = transform_mode; this->enable_keyboard = false; // TODO: from settings } @@ -228,13 +236,16 @@ void StreamSession::SetLoginPIN(const QString &pin) chiaki_session_set_login_pin(&session, (const uint8_t *)data.constData(), data.size()); } -void StreamSession::HandleMouseEvent(QMouseEvent *event) +bool StreamSession::HandleMouseEvent(QMouseEvent *event) { + if(event->button() != Qt::MouseButton::LeftButton) + return false; if(event->type() == QEvent::MouseButtonPress) keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; else keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; SendFeedbackState(); + return true; } void StreamSession::HandleKeyboardEvent(QKeyEvent *event) diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index feff758..d1fc4e8 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent) : QMainWindow(parent), @@ -23,8 +24,6 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget try { - if(connect_info.fullscreen) - showFullScreen(); Init(); } catch(const Exception &e) @@ -40,6 +39,8 @@ StreamWindow::~StreamWindow() delete av_widget; } +#include + void StreamWindow::Init() { session = new StreamSession(connect_info, this); @@ -47,10 +48,36 @@ void StreamWindow::Init() connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested); + const QKeySequence fullscreen_shortcut = Qt::Key_F11; + const QKeySequence stretch_shortcut = Qt::CTRL + Qt::Key_S; + const QKeySequence zoom_shortcut = Qt::CTRL + Qt::Key_Z; + + fullscreen_action = new QAction(tr("Fullscreen"), this); + fullscreen_action->setCheckable(true); + fullscreen_action->setShortcut(fullscreen_shortcut); + addAction(fullscreen_action); + connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); + if(session->GetFfmpegDecoder()) { - av_widget = new AVOpenGLWidget(session, this); + av_widget = new AVOpenGLWidget(session, this, connect_info.transform_mode); setCentralWidget(av_widget); + + av_widget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(av_widget, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) { + av_widget->ResetMouseTimeout(); + + QMenu menu(av_widget); + menu.addAction(fullscreen_action); + menu.addSeparator(); + menu.addAction(stretch_action); + menu.addAction(zoom_action); + releaseKeyboard(); + connect(&menu, &QMenu::aboutToHide, this, [this] { + grabKeyboard(); + }); + menu.exec(av_widget->mapToGlobal(pos)); + }); } else { @@ -63,13 +90,29 @@ void StreamWindow::Init() session->Start(); - auto fullscreen_action = new QAction(tr("Fullscreen"), this); - fullscreen_action->setShortcut(Qt::Key_F11); - addAction(fullscreen_action); - connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); + stretch_action = new QAction(tr("Stretch"), this); + stretch_action->setCheckable(true); + stretch_action->setShortcut(stretch_shortcut); + addAction(stretch_action); + connect(stretch_action, &QAction::triggered, this, &StreamWindow::ToggleStretch); + + zoom_action = new QAction(tr("Zoom"), this); + zoom_action->setCheckable(true); + zoom_action->setShortcut(zoom_shortcut); + addAction(zoom_action); + connect(zoom_action, &QAction::triggered, this, &StreamWindow::ToggleZoom); resize(connect_info.video_profile.width, connect_info.video_profile.height); - show(); + + if(connect_info.fullscreen) + { + showFullScreen(); + fullscreen_action->setChecked(true); + } + else + show(); + + UpdateTransformModeActions(); } void StreamWindow::keyPressEvent(QKeyEvent *event) @@ -86,20 +129,25 @@ void StreamWindow::keyReleaseEvent(QKeyEvent *event) void StreamWindow::mousePressEvent(QMouseEvent *event) { - if(session) - session->HandleMouseEvent(event); + if(session && session->HandleMouseEvent(event)) + return; + QMainWindow::mousePressEvent(event); } void StreamWindow::mouseReleaseEvent(QMouseEvent *event) { - if(session) - session->HandleMouseEvent(event); + if(session && session->HandleMouseEvent(event)) + return; + QMainWindow::mouseReleaseEvent(event); } void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event) { - ToggleFullscreen(); - + if(event->button() == Qt::MouseButton::LeftButton) + { + ToggleFullscreen(); + return; + } QMainWindow::mouseDoubleClickEvent(event); } @@ -175,15 +223,48 @@ void StreamWindow::LoginPINRequested(bool incorrect) void StreamWindow::ToggleFullscreen() { if(isFullScreen()) + { showNormal(); + fullscreen_action->setChecked(false); + } else { showFullScreen(); if(av_widget) av_widget->HideMouse(); + fullscreen_action->setChecked(true); } } +void StreamWindow::UpdateTransformModeActions() +{ + TransformMode tm = av_widget ? av_widget->GetTransformMode() : TransformMode::Fit; + stretch_action->setChecked(tm == TransformMode::Stretch); + zoom_action->setChecked(tm == TransformMode::Zoom); +} + +void StreamWindow::ToggleStretch() +{ + if(!av_widget) + return; + av_widget->SetTransformMode( + av_widget->GetTransformMode() == TransformMode::Stretch + ? TransformMode::Fit + : TransformMode::Stretch); + UpdateTransformModeActions(); +} + +void StreamWindow::ToggleZoom() +{ + if(!av_widget) + return; + av_widget->SetTransformMode( + av_widget->GetTransformMode() == TransformMode::Zoom + ? TransformMode::Fit + : TransformMode::Zoom); + UpdateTransformModeActions(); +} + void StreamWindow::resizeEvent(QResizeEvent *event) { UpdateVideoTransform();