Add transform/scaling modes to GUI

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 <info@florianmaerkl.de>
This commit is contained in:
Street Pea 2022-12-10 15:09:43 +01:00 committed by Florian Märkl
parent 74d39e6314
commit 801f902bea
9 changed files with 207 additions and 28 deletions

View file

@ -3,6 +3,8 @@
#ifndef CHIAKI_AVOPENGLWIDGET_H #ifndef CHIAKI_AVOPENGLWIDGET_H
#define CHIAKI_AVOPENGLWIDGET_H #define CHIAKI_AVOPENGLWIDGET_H
#include "transformmode.h"
#include <chiaki/log.h> #include <chiaki/log.h>
#include <QOpenGLWidget> #include <QOpenGLWidget>
@ -74,21 +76,24 @@ class AVOpenGLWidget: public QOpenGLWidget
public: public:
static QSurfaceFormat CreateSurfaceFormat(); static QSurfaceFormat CreateSurfaceFormat();
explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr); explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit);
~AVOpenGLWidget() override; ~AVOpenGLWidget() override;
void SwapFrames(); void SwapFrames();
AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; } AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; }
void SetTransformMode(TransformMode mode) { transform_mode = mode; }
TransformMode GetTransformMode() const { return transform_mode; }
protected: protected:
TransformMode transform_mode;
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
void initializeGL() override; void initializeGL() override;
void paintGL() override; void paintGL() override;
private slots:
void ResetMouseTimeout();
public slots: public slots:
void ResetMouseTimeout();
void HideMouse(); void HideMouse();
}; };

View file

@ -20,6 +20,7 @@
#include "sessionlog.h" #include "sessionlog.h"
#include "controllermanager.h" #include "controllermanager.h"
#include "settings.h" #include "settings.h"
#include "transformmode.h"
#include <QObject> #include <QObject>
#include <QImage> #include <QImage>
@ -53,9 +54,17 @@ struct StreamSessionConnectInfo
ChiakiConnectVideoProfile video_profile; ChiakiConnectVideoProfile video_profile;
unsigned int audio_buffer_size; unsigned int audio_buffer_size;
bool fullscreen; bool fullscreen;
TransformMode transform_mode;
bool enable_keyboard; 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 class StreamSession : public QObject
@ -124,7 +133,7 @@ class StreamSession : public QObject
#endif #endif
void HandleKeyboardEvent(QKeyEvent *event); void HandleKeyboardEvent(QKeyEvent *event);
void HandleMouseEvent(QMouseEvent *event); bool HandleMouseEvent(QMouseEvent *event);
signals: signals:
void FfmpegFrameAvailable(); void FfmpegFrameAvailable();

View file

@ -22,10 +22,14 @@ class StreamWindow: public QMainWindow
const StreamSessionConnectInfo connect_info; const StreamSessionConnectInfo connect_info;
StreamSession *session; StreamSession *session;
QAction *fullscreen_action;
QAction *stretch_action;
QAction *zoom_action;
AVOpenGLWidget *av_widget; AVOpenGLWidget *av_widget;
void Init(); void Init();
void UpdateVideoTransform(); void UpdateVideoTransform();
void UpdateTransformModeActions();
protected: protected:
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
@ -42,6 +46,8 @@ class StreamWindow: public QMainWindow
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect); void LoginPINRequested(bool incorrect);
void ToggleFullscreen(); void ToggleFullscreen();
void ToggleStretch();
void ToggleZoom();
}; };
#endif // CHIAKI_GUI_STREAMWINDOW_H #endif // CHIAKI_GUI_STREAMWINDOW_H

View file

@ -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

View file

@ -122,9 +122,9 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat()
return format; return format;
} }
AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent) AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent, TransformMode transform_mode)
: QOpenGLWidget(parent), : QOpenGLWidget(parent),
session(session) session(session), transform_mode(transform_mode)
{ {
enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder()); enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder());
conversion_config = nullptr; conversion_config = nullptr;
@ -381,10 +381,10 @@ void AVOpenGLWidget::paintGL()
vp_width = widget_width; vp_width = widget_width;
vp_height = widget_height; vp_height = widget_height;
} }
else else if(transform_mode == TransformMode::Fit)
{ {
float aspect = (float)frame->width / (float)frame->height; 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_height = widget_height;
vp_width = (GLsizei)(vp_height * aspect); vp_width = (GLsizei)(vp_height * aspect);
@ -395,6 +395,34 @@ void AVOpenGLWidget::paintGL()
vp_height = (GLsizei)(vp_width / aspect); 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); f->glViewport((widget_width - vp_width) / 2, (widget_height - vp_height) / 2, vp_width, vp_height);

View file

@ -103,9 +103,15 @@ int real_main(int argc, char *argv[])
QCommandLineOption morning_option("morning", "", "morning"); QCommandLineOption morning_option("morning", "", "morning");
parser.addOption(morning_option); 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); 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); parser.process(app);
QStringList args = parser.positionalArguments(); QStringList args = parser.positionalArguments();
@ -174,7 +180,21 @@ int real_main(int argc, char *argv[])
return 1; 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); return RunStream(app, connect_info);
} }
#ifdef CHIAKI_ENABLE_CLI #ifdef CHIAKI_ENABLE_CLI

View file

@ -249,7 +249,14 @@ void MainWindow::ServerItemWidgetTriggered()
} }
QString host = server.GetHostAddr(); 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); new StreamWindow(info);
} }
else else

View file

@ -14,7 +14,14 @@
#define SETSU_UPDATE_INTERVAL_MS 4 #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) : settings(settings)
{ {
key_map = settings->GetControllerMappingForDecoding(); key_map = settings->GetControllerMappingForDecoding();
@ -30,6 +37,7 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar
this->morning = morning; this->morning = morning;
audio_buffer_size = settings->GetAudioBufferSize(); audio_buffer_size = settings->GetAudioBufferSize();
this->fullscreen = fullscreen; this->fullscreen = fullscreen;
this->transform_mode = transform_mode;
this->enable_keyboard = false; // TODO: from settings 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()); 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) if(event->type() == QEvent::MouseButtonPress)
keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
else else
keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD; keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
SendFeedbackState(); SendFeedbackState();
return true;
} }
void StreamSession::HandleKeyboardEvent(QKeyEvent *event) void StreamSession::HandleKeyboardEvent(QKeyEvent *event)

View file

@ -10,6 +10,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QCoreApplication> #include <QCoreApplication>
#include <QAction> #include <QAction>
#include <QMenu>
StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent) StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent)
: QMainWindow(parent), : QMainWindow(parent),
@ -23,8 +24,6 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget
try try
{ {
if(connect_info.fullscreen)
showFullScreen();
Init(); Init();
} }
catch(const Exception &e) catch(const Exception &e)
@ -40,6 +39,8 @@ StreamWindow::~StreamWindow()
delete av_widget; delete av_widget;
} }
#include <QGuiApplication>
void StreamWindow::Init() void StreamWindow::Init()
{ {
session = new StreamSession(connect_info, this); session = new StreamSession(connect_info, this);
@ -47,10 +48,36 @@ void StreamWindow::Init()
connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit); connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit);
connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested); 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()) if(session->GetFfmpegDecoder())
{ {
av_widget = new AVOpenGLWidget(session, this); av_widget = new AVOpenGLWidget(session, this, connect_info.transform_mode);
setCentralWidget(av_widget); 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 else
{ {
@ -63,13 +90,29 @@ void StreamWindow::Init()
session->Start(); session->Start();
auto fullscreen_action = new QAction(tr("Fullscreen"), this); stretch_action = new QAction(tr("Stretch"), this);
fullscreen_action->setShortcut(Qt::Key_F11); stretch_action->setCheckable(true);
addAction(fullscreen_action); stretch_action->setShortcut(stretch_shortcut);
connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen); 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); 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) void StreamWindow::keyPressEvent(QKeyEvent *event)
@ -86,20 +129,25 @@ void StreamWindow::keyReleaseEvent(QKeyEvent *event)
void StreamWindow::mousePressEvent(QMouseEvent *event) void StreamWindow::mousePressEvent(QMouseEvent *event)
{ {
if(session) if(session && session->HandleMouseEvent(event))
session->HandleMouseEvent(event); return;
QMainWindow::mousePressEvent(event);
} }
void StreamWindow::mouseReleaseEvent(QMouseEvent *event) void StreamWindow::mouseReleaseEvent(QMouseEvent *event)
{ {
if(session) if(session && session->HandleMouseEvent(event))
session->HandleMouseEvent(event); return;
QMainWindow::mouseReleaseEvent(event);
} }
void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event) void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event)
{ {
ToggleFullscreen(); if(event->button() == Qt::MouseButton::LeftButton)
{
ToggleFullscreen();
return;
}
QMainWindow::mouseDoubleClickEvent(event); QMainWindow::mouseDoubleClickEvent(event);
} }
@ -175,15 +223,48 @@ void StreamWindow::LoginPINRequested(bool incorrect)
void StreamWindow::ToggleFullscreen() void StreamWindow::ToggleFullscreen()
{ {
if(isFullScreen()) if(isFullScreen())
{
showNormal(); showNormal();
fullscreen_action->setChecked(false);
}
else else
{ {
showFullScreen(); showFullScreen();
if(av_widget) if(av_widget)
av_widget->HideMouse(); 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) void StreamWindow::resizeEvent(QResizeEvent *event)
{ {
UpdateVideoTransform(); UpdateVideoTransform();