Qt+mpv制作windows/linux 下的动态壁纸软件(含源码)

mpv是啥?

mpv之前是mediaplayer,windwos之前好像都有预装这个,都来经过发展,现在是一个对跨平台开发很友好的播放器工具。

Qt是啥?

制作桌面应用程序的开发语言及工具,基于C++,跨平台。

基础开发步骤

1.动态壁纸需要一个播放视频的软件,那么可以借鉴mpv的官方demo
https://github.com/mpv-player/mpv-examples/tree/master/libmpv
可以使用里面的qt文件夹里面的demo或者qt_opengl里面的demo
(我是使用的opengl那个demo)
demo编译就会发现,可以播放视频了,那么就以这个demo作为我们的播放模块的代码

2.动态壁纸需要放到图标层以下(windows)
程序需要msvc编译,不能用mingw编译,怀疑是mingw用不了部分win32的部分api

需要在qt pro文件中增加api所需要的库文件

LIBS+=-luser32    //所需要的win32 api库
win32: LIBS += $$PWD/3rd/libmpv.dll.a    //mpv的库文件

INCLUDEPATH += $$PWD/3rd/mpv //mpv头文件地址增加

3.关键代码:设置窗口放到图标下面(windows)

#ifdef Q_OS_WINDOWS
#include <windows.h>

WId viewId;
HHOOK hook;
HWND workerW;

LRESULT CALLBACK HookShoot(_In_ int nCode, _In_ WPARAM wParam,LPARAM lParam){
    if(wParam == WM_MOUSEMOVE || wParam == WM_NCMOUSEMOVE){
         MOUSEHOOKSTRUCT * mshook = (MOUSEHOOKSTRUCT *)lParam;
         PostMessage((HWND)viewId,WM_MOUSEMOVE,0,MAKELPARAM(mshook->pt.x,mshook->pt.y));
    };
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

HWND _WORKERW = nullptr;

inline BOOL CALLBACK EnumWindowsProc(_In_ HWND tophandle, _In_ LPARAM topparamhandle)
{
    HWND defview = FindWindowEx(tophandle, 0, L"SHELLDLL_DefView", nullptr);
    if (defview != nullptr)
    {
        _WORKERW = FindWindowEx(0, tophandle, L"WorkerW", 0);
    }
    return true;
}

HWND GetWorkerDesktop(){
    int result;
    HWND windowHandle = FindWindow(L"Progman", nullptr);
    //使用 0x3e8 命令分割出两个 WorkerW
    SendMessageTimeout(windowHandle, 0x052c, 0 ,0, SMTO_NORMAL, 0x3e8,(PDWORD_PTR)&result);
    //遍历窗体获得窗口句柄
    EnumWindows(EnumWindowsProc,(LPARAM)nullptr);
    // 显示WorkerW
    ShowWindow(_WORKERW,SW_HIDE);
    return windowHandle;
}

void Wallpaper::registerDesktop()
{
//windwos设置
    workerW =  GetWorkerDesktop();

    if(this){
        viewId = this->winId();
        // 返回workerW窗口句柄

        //设置窗口样式
        GetWindowLongA((HWND)viewId, GWL_STYLE);
        //设置壁纸窗体的父窗体
        SetParent((HWND)viewId,workerW);
        SetWindowPos((HWND)viewId,HWND_TOP,0,0,0,0,WS_EX_LEFT|WS_EX_LTRREADING|WS_EX_RIGHTSCROLLBAR|WS_EX_NOACTIVATE);
        // 设置全局鼠标事件钩子(会很卡)
//        hook = SetWindowsHookEx(WH_MOUSE_LL,HookShoot,GetModuleHandle(NULL),0);

        QDesktopWidget *desktop = QApplication::desktop();
        this->move(QPoint(0,0));
        int height = desktop->height();
        int width = desktop->width();
        this->resize(QSize(width,height));
        this->showFullScreen();
    }

4关键代码 linux,设置桌面属性

#include <QtX11Extras/QX11Info>
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
#include <QtPlatformHeaders/QXcbWindowFunctions>
void Wallpaper::registerDesktop()
{

    this->winId();
    QWindow *window = this->windowHandle();
    window->setOpacity(0.99);
    if (QApplication::platformName() == "wayland") {
        qDebug() << "wayland set desktop"; //wayland环境下没有做
    } else {
       //x11环境下直接设置这个即可
        QXcbWindowFunctions::setWmWindowType(window, QXcbWindowFunctions::Desktop);
    }

    //随意激活下窗口,可能不需要
    QTimer::singleShot(1, this, [=] {
        show();
        lower();
    });
}

5 mpv播放器使用关键代码 (可以换用其他播放器)

头文件:

#ifndef PLAYERWINDOW_H
#define PLAYERWINDOW_H

#include <QtWidgets/QOpenGLWidget>
#include "mpv/client.h"
#include "mpv/render_gl.h"
#include "mpv/qthelper.hpp"
#include <QSize>

class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
{
    Q_OBJECT
public:
    MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = nullptr);
    ~MpvWidget();
    void command(const QVariant &params);
    void setProperty(const QString &name, const QVariant &value);
    QVariant getProperty(const QString &name) const;
    QSize sizeHint() const Q_DECL_OVERRIDE { return QSize(480, 270);}

Q_SIGNALS:
    void durationChanged(int value);
    void positionChanged(int value);
protected:
    void initializeGL() Q_DECL_OVERRIDE;
    void paintGL() Q_DECL_OVERRIDE;
private Q_SLOTS:
    void on_mpv_events();
    void maybeUpdate();
private:
    void handle_mpv_event(mpv_event *event);
    static void on_update(void *ctx);

    mpv_handle *mpv;
    mpv_render_context *mpv_gl;
    bool m_bScrrenShot{false};


};



#endif // PLAYERWINDOW_H

cpp:

#include "mpvwidget.h"
#include <stdexcept>
#include <QtGui/QOpenGLContext>
#include <QtCore/QMetaObject>
#include "application.h"

#include <QDateTime>
#include <QStandardPaths>

static void wakeup(void *ctx)
{
    QMetaObject::invokeMethod((MpvWidget *)ctx, "on_mpv_events", Qt::QueuedConnection);
}

static void *get_proc_address(void *ctx, const char *name)
{
    Q_UNUSED(ctx);
    QOpenGLContext *glctx = QOpenGLContext::currentContext();
    if (!glctx)
        return nullptr;
    return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
}

MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
    : QOpenGLWidget(parent, f)
{
    setAttribute(Qt::WA_TranslucentBackground);
    mpv = mpv_create();
    if (!mpv)
        throw std::runtime_error("could not create mpv context");

//    mpv_set_option_string(mpv, "terminal", "yes");
    mpv_set_option_string(mpv, "msg-level", "all=v");
    if (mpv_initialize(mpv) < 0)
        throw std::runtime_error("could not initialize mpv context");

    // Request hw decoding, just for testing.
//    mpv::qt::set_option_variant(mpv, "hwdec", "auto");
    mpv::qt::set_option_variant(mpv, "hwdec", "gpu");

    mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
    mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
    mpv_set_wakeup_callback(mpv, wakeup, this);

    QList<QVariant> list;
    list << "no-correct-pts";
//    mpv::qt::command_variant(mpv, list);
    mpv::qt::set_property_variant(mpv, "correct-pts", "no");
    mpv::qt::set_property_variant(mpv, "fps", "0");

}

MpvWidget::~MpvWidget()
{
    makeCurrent();
    if (mpv_gl)
        mpv_render_context_free(mpv_gl);
    mpv_terminate_destroy(mpv);
}

void MpvWidget::command(const QVariant &params)
{
    mpv::qt::command_variant(mpv, params);
}

void MpvWidget::setProperty(const QString &name, const QVariant &value)
{
    mpv::qt::set_property_variant(mpv, name, value);
}

QVariant MpvWidget::getProperty(const QString &name) const
{
    return mpv::qt::get_property_variant(mpv, name);
}

void MpvWidget::initializeGL()
{
    mpv_opengl_init_params gl_init_params{get_proc_address, nullptr, nullptr};
    mpv_render_param params[] {
        {MPV_RENDER_PARAM_API_TYPE, const_cast<char *>(MPV_RENDER_API_TYPE_OPENGL)},
        {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
        {MPV_RENDER_PARAM_INVALID, nullptr}
    };

    if (mpv_render_context_create(&mpv_gl, mpv, params) < 0)
        throw std::runtime_error("failed to initialize mpv GL context");
    mpv_render_context_set_update_callback(mpv_gl, MpvWidget::on_update, reinterpret_cast<void *>(this));
}

void MpvWidget::paintGL()
{
    int iwidth = width();
    double dwidth = iwidth * devicePixelRatioF();
    int iheight = height();
    double dheight = iheight * devicePixelRatioF();
    int deviceiwidth = dwidth;
    int deviceiheight = dheight;
    mpv_opengl_fbo mpfbo{static_cast<int>(defaultFramebufferObject()), deviceiwidth, deviceiheight, 0};
    int flip_y{1};

    mpv_render_param params[] = {
        {MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo},
        {MPV_RENDER_PARAM_FLIP_Y, &flip_y},
        {MPV_RENDER_PARAM_INVALID, nullptr}
    };
    // See render_gl.h on what OpenGL environment mpv expects, and
    // other API details.
    mpv_render_context_render(mpv_gl, params);

    if (m_bScrrenShot) {
        m_bScrrenShot = false;
        QPixmap pix = QPixmap::fromImage(grabFramebuffer());
        QDateTime::currentMSecsSinceEpoch();
        QString path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/" + QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png";
        pix.save(path);
    }
}

void MpvWidget::on_mpv_events()
{
    // Process all events, until the event queue is empty.
    while (mpv) {
        mpv_event *event = mpv_wait_event(mpv, 0);
        if (event->event_id == MPV_EVENT_NONE) {
            break;
        }
        handle_mpv_event(event);
    }
}

void MpvWidget::handle_mpv_event(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;
                Q_EMIT positionChanged(time);
            }
        } else if (strcmp(prop->name, "duration") == 0) {
            if (prop->format == MPV_FORMAT_DOUBLE) {
                double time = *(double *)prop->data;
                Q_EMIT durationChanged(time);
            }
        }
        break;
    }
    default: ;
        // Ignore uninteresting or unknown events.
    }
}

// Make Qt invoke mpv_render_context_render() to draw a new/updated video frame.
void MpvWidget::maybeUpdate()
{
    // If the Qt window is not visible, Qt's update() will just skip rendering.
    // This confuses mpv's render API, and may lead to small occasional
    // freezes due to video rendering timing out.
    // Handle this by manually redrawing.
    // Note: Qt doesn't seem to provide a way to query whether update() will
    //       be skipped, and the following code still fails when e.g. switching
    //       to a different workspace with a reparenting window manager.
    if (window()->isMinimized()) {
        makeCurrent();
        paintGL();
        context()->swapBuffers(context()->surface());
        doneCurrent();
    } else {
        update();
    }
}

void MpvWidget::on_update(void *ctx)
{
    QMetaObject::invokeMethod((MpvWidget *)ctx, "maybeUpdate");
}

只要将激活窗口包含播放器,对应linux和windows设置就可以使用。

在windows下几乎完美,图标可点击,linux下只能显示视频(ubuntu18.04可以,其他似乎都不行)。

demo git地址(windows/linux)

windwos:
windwos源码
或者
https://download.csdn.net/download/qq_43081702/86741121

linux:
linux源码

windows /linux下demo可执行程序地址

linux可执行程序下载

windows可执行程序下载

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏有凉风,冬有雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值