This commit is contained in:
Martin Rotter 2023-11-28 13:09:01 +01:00
parent cc11c41f32
commit 20f8d81f13
5 changed files with 174 additions and 59 deletions

View File

@ -528,7 +528,7 @@ void FormMain::switchVisibility(bool force_hide) {
QSystemTrayIcon::MessageIcon::Warning});
}
else {
close();
hide();
}
}
else {
@ -1078,9 +1078,15 @@ void FormMain::changeEvent(QEvent* event) {
}
void FormMain::closeEvent(QCloseEvent* event) {
QMainWindow::closeEvent(event);
if (qApp->quitOnLastWindowClosed()) {
QMainWindow::closeEvent(event);
}
else /*if (!event->spontaneous())*/ {
event->ignore();
hide();
}
qDebugNN << LOGSEC_GUI << "Main window's close event";
qDebugNN << LOGSEC_GUI << "Main window close event";
}
void FormMain::showEvent(QShowEvent* event) {
@ -1092,7 +1098,7 @@ void FormMain::showEvent(QShowEvent* event) {
void FormMain::hideEvent(QHideEvent* event) {
QMainWindow::hideEvent(event);
qDebugNN << LOGSEC_GUI << "Main window's hide event";
qDebugNN << LOGSEC_GUI << "Main window hide event";
}
void FormMain::showDocs() {

View File

@ -33,13 +33,14 @@ LibMpvBackend::LibMpvBackend(QWidget* parent)
}
// Create a video child window. Force Qt to create a native window, and
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa.
m_mpvContainer->setAttribute(Qt::WidgetAttribute::WA_DontCreateNativeAncestors);
m_mpvContainer->setAttribute(Qt::WidgetAttribute::WA_NativeWindow);
layout()->addWidget(m_mpvContainer);
auto raw_wid = m_mpvContainer->winId();
#if defined(Q_OS_WIN)
// Truncate to 32-bit, as all Windows handles are. This also ensures
// it doesn't go negative.
@ -49,10 +50,19 @@ LibMpvBackend::LibMpvBackend(QWidget* parent)
#endif
mpv_set_option(m_mpvHandle, "wid", MPV_FORMAT_INT64, &wid);
mpv_set_option_string(m_mpvHandle, "input-default-bindings", "yes");
mpv_set_option_string(m_mpvHandle, "msg-level", "all=v");
mpv::qt::set_option_variant(m_mpvHandle, "hwdec", "auto");
mpv_set_option_string(m_mpvHandle, "terminal", "yes");
mpv_set_option_string(m_mpvHandle, "keep-open", "yes");
// mpv_set_option_string(m_mpvHandle, "input-terminal", "yes");
mpv_set_option_string(m_mpvHandle, "hwdec", "auto");
mpv_set_option_string(m_mpvHandle, "osd-playing-msg", "${media-title}");
mpv_set_option_string(m_mpvHandle, "osc", "yes");
mpv_set_option_string(m_mpvHandle, "input-cursor", "yes");
// mpv_set_option_string(m_mpvHandle, "osd-on-seek", "msg-bar");
// mpv::qt::set_option_variant(m_mpvHandle, "hwdec", "auto");
// Enable keyboard input on the X11 window. For the messy details, see
// --input-vo-keyboard on the manpage.
@ -62,6 +72,8 @@ LibMpvBackend::LibMpvBackend(QWidget* parent)
mpv_observe_property(m_mpvHandle, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(m_mpvHandle, 0, "track-list", MPV_FORMAT_NODE);
mpv_observe_property(m_mpvHandle, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(m_mpvHandle, 0, "duration", MPV_FORMAT_NODE);
mpv_observe_property(m_mpvHandle, 0, "volume", MPV_FORMAT_NODE);
// From this point on, the wakeup function will be called. The callback
// can come from any thread, so we use the QueuedConnection mechanism to
@ -71,6 +83,7 @@ LibMpvBackend::LibMpvBackend(QWidget* parent)
this,
&LibMpvBackend::onMpvEvents,
Qt::ConnectionType::QueuedConnection);
mpv_set_wakeup_callback(m_mpvHandle, wakeup, this);
if (mpv_initialize(m_mpvHandle) < 0) {
@ -88,25 +101,8 @@ void LibMpvBackend::handleMpvEvent(mpv_event* event) {
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property* prop = (mpv_event_property*)event->data;
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
std::stringstream ss;
ss << "At: " << time;
}
else if (prop->format == MPV_FORMAT_NONE) {
}
}
else if (strcmp(prop->name, "chapter-list") == 0 || strcmp(prop->name, "track-list") == 0) {
// Dump the properties as JSON for demo purposes.
if (prop->format == MPV_FORMAT_NODE) {
QVariant v = mpv::qt::node_to_variant((mpv_node*)prop->data);
// Abuse JSON support for easily printing the mpv_node contents.
QJsonDocument d = QJsonDocument::fromVariant(v);
appendLog("Change property " + QString(prop->name) + ":\n");
appendLog(d.toJson().data());
}
}
processPropertyChange(prop);
break;
}
@ -128,16 +124,14 @@ void LibMpvBackend::handleMpvEvent(mpv_event* event) {
}
case MPV_EVENT_LOG_MESSAGE: {
struct mpv_event_log_message* msg = (struct mpv_event_log_message*)event->data;
std::stringstream ss;
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
qDebugNN << LOGSEC_MPV << QString::fromStdString(ss.str());
break;
mpv_event_log_message* msg = (mpv_event_log_message*)event->data;
processLogMessage(msg);
}
case MPV_EVENT_SHUTDOWN: {
mpv_terminate_destroy(m_mpvHandle);
m_mpvHandle = NULL;
m_mpvHandle = nullptr;
break;
}
@ -159,6 +153,35 @@ void LibMpvBackend::onMpvEvents() {
}
}
void LibMpvBackend::processPropertyChange(mpv_event_property* prop) {
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
std::stringstream ss;
ss << "At: " << time;
}
else if (prop->format == MPV_FORMAT_NONE) {
}
}
else if (strcmp(prop->name, "chapter-list") == 0 || strcmp(prop->name, "track-list") == 0) {
// Dump the properties as JSON for demo purposes.
if (prop->format == MPV_FORMAT_NODE) {
QVariant v = mpv::qt::node_to_variant((mpv_node*)prop->data);
// Abuse JSON support for easily printing the mpv_node contents.
QJsonDocument d = QJsonDocument::fromVariant(v);
appendLog("Change property " + QString(prop->name) + ":\n");
appendLog(d.toJson().data());
}
}
}
void LibMpvBackend::processLogMessage(mpv_event_log_message* msg) {
std::stringstream ss;
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
appendLog(QString::fromStdString(ss.str()));
}
void LibMpvBackend::appendLog(const QString& text) {
qDebugNN << LOGSEC_MPV << text;
}
@ -166,16 +189,27 @@ void LibMpvBackend::appendLog(const QString& text) {
bool LibMpvBackend::eventFilter(QObject* watched, QEvent* event) {
Q_UNUSED(watched)
if (event->type() == QEvent::Type::ShortcutOverride) {
// NOTE: If user presses key which is application-wide assigned to some
// action, do not propagate the shortcut to application.
event->accept();
return true;
}
if (event->type() == QEvent::Type::KeyPress) {
char txt = (char)dynamic_cast<QKeyEvent*>(event)->key();
char str[2];
if (m_mpvHandle != nullptr) {
char txt = (char)dynamic_cast<QKeyEvent*>(event)->key();
char str[2];
str[0] = txt;
str[1] = '\0';
str[0] = txt;
str[1] = '\0';
const char* args[] = {"keypress", str, NULL};
const char* args[] = {"keypress", str, nullptr};
mpv_command_async(m_mpvHandle, 0, args);
mpv_command_async(m_mpvHandle, 0, args);
}
event->accept();
return true;
}
@ -185,7 +219,7 @@ bool LibMpvBackend::eventFilter(QObject* watched, QEvent* event) {
void LibMpvBackend::playUrl(const QUrl& url) {
auto eb = url.toString().toLocal8Bit();
const char* css = eb.data();
const char* args[] = {"loadfile", css, NULL};
const char* args[] = {"loadfile", css, nullptr};
mpv_command_async(m_mpvHandle, 0, args);
}

View File

@ -7,6 +7,8 @@
struct mpv_handle;
struct mpv_event;
struct mpv_event_property;
struct mpv_event_log_message;
class LibMpvBackend : public PlayerBackend {
Q_OBJECT
@ -38,6 +40,8 @@ class LibMpvBackend : public PlayerBackend {
void launchMpvEvents();
private:
void processPropertyChange(mpv_event_property* prop);
void processLogMessage(mpv_event_log_message* msg);
void appendLog(const QString& text);
void createPlayer();
void handleMpvEvent(mpv_event* event);

View File

@ -1,3 +1,5 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef LIBMPV_QTHELPER_H_
#define LIBMPV_QTHELPER_H_
@ -14,7 +16,6 @@
namespace mpv {
namespace qt {
// Wrapper around mpv_handle. Does refcounting under the hood.
class Handle {
struct container {
@ -51,25 +52,34 @@ namespace mpv {
switch (node->format) {
case MPV_FORMAT_STRING:
return QVariant(QString::fromUtf8(node->u.string));
case MPV_FORMAT_FLAG:
return QVariant(static_cast<bool>(node->u.flag));
case MPV_FORMAT_INT64:
return QVariant(static_cast<qlonglong>(node->u.int64));
case MPV_FORMAT_DOUBLE:
return QVariant(node->u.double_);
case MPV_FORMAT_NODE_ARRAY: {
mpv_node_list* list = node->u.list;
QVariantList qlist;
for (int n = 0; n < list->num; n++)
for (int n = 0; n < list->num; n++) {
qlist.append(node_to_variant(&list->values[n]));
}
return QVariant(qlist);
}
case MPV_FORMAT_NODE_MAP: {
mpv_node_list* list = node->u.list;
QVariantMap qmap;
for (int n = 0; n < list->num; n++) {
qmap.insert(QString::fromUtf8(list->keys[n]), node_to_variant(&list->values[n]));
}
return QVariant(qmap);
}
default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
@ -81,9 +91,11 @@ namespace mpv {
node_builder(const QVariant& v) {
set(&node_, v);
}
~node_builder() {
free_node(&node_);
}
mpv_node* node() {
return &node_;
}
@ -91,45 +103,63 @@ namespace mpv {
private:
Q_DISABLE_COPY(node_builder)
mpv_node node_;
mpv_node_list* create_list(mpv_node* dst, bool is_map, int num) {
dst->format = is_map ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
mpv_node_list* list = new mpv_node_list();
dst->u.list = list;
if (!list)
if (!list) {
goto err;
}
list->values = new mpv_node[num]();
if (!list->values)
if (!list->values) {
goto err;
}
if (is_map) {
list->keys = new char*[num]();
if (!list->keys)
if (!list->keys) {
goto err;
}
}
return list;
err:
free_node(dst);
return NULL;
return nullptr;
}
char* dup_qstring(const QString& s) {
QByteArray b = s.toUtf8();
char* r = new char[b.size() + 1];
if (r)
if (r) {
std::memcpy(r, b.data(), b.size() + 1);
}
return r;
}
bool test_type(const QVariant& v, QMetaType::Type t) {
// The Qt docs say: "Although this function is declared as returning
// "QVariant::Type(obsolete), the return value should be interpreted
// as QMetaType::Type."
// So a cast really seems to be needed to avoid warnings (urgh).
return static_cast<int>(v.type()) == static_cast<int>(t);
return v.typeId() == static_cast<int>(t);
}
void set(mpv_node* dst, const QVariant& src) {
if (test_type(src, QMetaType::QString)) {
dst->format = MPV_FORMAT_STRING;
dst->u.string = dup_qstring(src.toString());
if (!dst->u.string)
if (!dst->u.string) {
goto fail;
}
}
else if (test_type(src, QMetaType::Bool)) {
dst->format = MPV_FORMAT_FLAG;
@ -147,57 +177,80 @@ namespace mpv {
else if (src.canConvert<QVariantList>()) {
QVariantList qlist = src.toList();
mpv_node_list* list = create_list(dst, false, qlist.size());
if (!list)
if (!list) {
goto fail;
}
list->num = qlist.size();
for (int n = 0; n < qlist.size(); n++)
for (int n = 0; n < qlist.size(); n++) {
set(&list->values[n], qlist[n]);
}
}
else if (src.canConvert<QVariantMap>()) {
QVariantMap qmap = src.toMap();
mpv_node_list* list = create_list(dst, true, qmap.size());
if (!list)
if (!list) {
goto fail;
}
list->num = qmap.size();
for (int n = 0; n < qmap.size(); n++) {
list->keys[n] = dup_qstring(qmap.keys()[n]);
if (!list->keys[n]) {
free_node(dst);
goto fail;
}
set(&list->values[n], qmap.values()[n]);
}
}
else {
goto fail;
}
return;
fail:
dst->format = MPV_FORMAT_NONE;
}
void free_node(mpv_node* dst) {
switch (dst->format) {
case MPV_FORMAT_STRING:
delete[] dst->u.string;
break;
case MPV_FORMAT_NODE_ARRAY:
case MPV_FORMAT_NODE_MAP: {
mpv_node_list* list = dst->u.list;
if (list) {
for (int n = 0; n < list->num; n++) {
if (list->keys)
if (list->keys) {
delete[] list->keys[n];
if (list->values)
}
if (list->values) {
free_node(&list->values[n]);
}
}
delete[] list->keys;
delete[] list->values;
}
delete list;
break;
}
default:;
}
dst->format = MPV_FORMAT_NONE;
}
};
@ -207,7 +260,9 @@ namespace mpv {
*/
struct node_autofree {
mpv_node* ptr;
node_autofree(mpv_node* a_ptr) : ptr(a_ptr) {}
~node_autofree() {
mpv_free_node_contents(ptr);
}
@ -223,8 +278,11 @@ namespace mpv {
*/
static inline QVariant get_property_variant(mpv_handle* ctx, const QString& name) {
mpv_node node;
if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0)
if (mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node) < 0) {
return QVariant();
}
node_autofree f(&node);
return node_to_variant(&node);
}
@ -236,6 +294,7 @@ namespace mpv {
*/
static inline int set_property_variant(mpv_handle* ctx, const QString& name, const QVariant& v) {
node_builder node(v);
return mpv_set_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
}
@ -246,6 +305,7 @@ namespace mpv {
*/
static inline int set_option_variant(mpv_handle* ctx, const QString& name, const QVariant& v) {
node_builder node(v);
return mpv_set_option(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
}
@ -258,8 +318,11 @@ namespace mpv {
static inline QVariant command_variant(mpv_handle* ctx, const QVariant& args) {
node_builder node(args);
mpv_node res;
if (mpv_command_node(ctx, node.node(), &res) < 0)
if (mpv_command_node(ctx, node.node(), &res) < 0) {
return QVariant();
}
node_autofree f(&res);
return node_to_variant(&res);
}
@ -288,8 +351,10 @@ namespace mpv {
* @return error code (<0) or success (>=0)
*/
static inline int get_error(const QVariant& v) {
if (!v.canConvert<ErrorReturn>())
if (!v.canConvert<ErrorReturn>()) {
return 0;
}
return v.value<ErrorReturn>().error;
}
@ -310,8 +375,11 @@ namespace mpv {
static inline QVariant get_property(mpv_handle* ctx, const QString& name) {
mpv_node node;
int err = mpv_get_property(ctx, name.toUtf8().data(), MPV_FORMAT_NODE, &node);
if (err < 0)
if (err < 0) {
return QVariant::fromValue(ErrorReturn(err));
}
node_autofree f(&node);
return node_to_variant(&node);
}
@ -336,8 +404,11 @@ namespace mpv {
node_builder node(args);
mpv_node res;
int err = mpv_command_node(ctx, node.node(), &res);
if (err < 0)
if (err < 0) {
return QVariant::fromValue(ErrorReturn(err));
}
node_autofree f(&res);
return node_to_variant(&res);
}

View File

@ -19,7 +19,7 @@ class ServiceEntryPoint {
// into the model. This method can for example display
// some kind of first-time configuration dialog inside itself
// before returning the root item.
// Returns NULL if initialization of new root cannot be done.
// Returns nullptr if initialization of new root cannot be done.
virtual ServiceRoot* createNewRoot() const = 0;
// Performs initialization of all service accounts created using this entry