首页 > 解决方案 > 将剪贴板内容粘贴到窗口

问题描述

我正在编写一个过度简化的剪贴板管理器供个人使用,并且我正在尝试实现一个功能,我将在其中单击剪贴板历史记录中的文本并将其粘贴到我正在工作的窗口中。

CopyQ有这个功能,所以我想看看它是如何在那里完成的。我从那里获取了我相信它可以满足我想要的代码:

睡眠定时器.h

/*
    Copyright (c) 2020, Lukas Holecek <hluk@email.cz>

    This file is part of CopyQ.

    CopyQ is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CopyQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef SLEEPTIMER_H
#define SLEEPTIMER_H

#include <QCoreApplication>
#include <QElapsedTimer>

#include <cmath>

class SleepTimer final
{
public:
    explicit SleepTimer(int timeoutMs)
        : m_timeoutMs(timeoutMs)
    {
        m_timer.start();
    }

    bool sleep()
    {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 5);
        return m_timer.elapsed() < m_timeoutMs;
    }

    int remaining() const
    {
        const auto remaining = static_cast<int>(m_timeoutMs - m_timer.elapsed());
        return std::max(0, remaining);
    }

private:
    QElapsedTimer m_timer;
    int m_timeoutMs;
};

inline void waitFor(int ms)
{
    SleepTimer t(ms);
    while (t.sleep()) {}
}

#endif // SLEEPTIMER_H

x11platformwindow.h

/*
    Copyright (c) 2020, Lukas Holecek <hluk@email.cz>

    This file is part of CopyQ.

    CopyQ is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CopyQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef X11PLATFORMWINDOW_H
#define X11PLATFORMWINDOW_H


#include <X11/Xlib.h>

#include <memory>

class AppConfig;
class QWidget;

class X11PlatformWindow
{
public:

    explicit X11PlatformWindow(Window winId);

    void raise() ;

    void pasteClipboard() ;

    void copy();

    bool isValid() const;

private:
    bool waitForFocus(int ms);

    void sendKeyPress(int modifier, int key);

    Window m_window;
};

Window getCurrentWindow();

#endif // X11PLATFORMWINDOW_H

x11平台窗口.cpp

/*
    Copyright (c) 2020, Lukas Holecek <hluk@email.cz>

    This file is part of CopyQ.

    CopyQ is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CopyQ is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CopyQ.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "sleeptimer.h"
#include "x11platformwindow.h"

#include <unistd.h>
#include <QX11Info>
#include <QTimer>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

#ifdef HAS_X11TEST
#   include <X11/extensions/XTest.h>
#endif

void waitMs(int msec)
{
    if (msec <= 0)
        return;

    QEventLoop loop;
    QTimer t;
    QObject::connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit);
    t.start(msec);
    loop.exec();
}

void simulateKeyPress(Display *display, Window window, unsigned int modifiers, unsigned int key)
{
    XKeyEvent event;
    XEvent *xev = reinterpret_cast<XEvent *>(&event);
    event.display     = display;
    event.window      = window;
    event.root        = DefaultRootWindow(display);
    event.subwindow   = None;
    event.time        = CurrentTime;
    event.x           = 1;
    event.y           = 1;
    event.x_root      = 1;
    event.y_root      = 1;
    event.same_screen = True;
    event.keycode     = XKeysymToKeycode(display, key);
    event.state       = modifiers;

    event.type = KeyPress;
    XSendEvent(display, window, True, KeyPressMask, xev);
    XSync(display, False);

    event.type = KeyRelease;
    XSendEvent(display, window, True, KeyPressMask, xev);
    XSync(display, False);
}

class X11WindowProperty final
{
public:
    X11WindowProperty(Display *display, Window w, Atom property, long longOffset,
                      long longLength, Atom reqType)
    {
        if ( XGetWindowProperty(display, w, property, longOffset, longLength, false,
                                reqType, &type, &format, &len, &remain, &data) != Success )
        {
            data = nullptr;
        }
    }

    ~X11WindowProperty()
    {
        if (data != nullptr)
            XFree(data);
    }

    bool isValid() const
    {
        return data != nullptr;
    }

    X11WindowProperty(const X11WindowProperty &) = delete;
    X11WindowProperty &operator=(const X11WindowProperty &) = delete;

    Atom type{};
    int format{};
    unsigned long len{};
    unsigned long remain{};
    unsigned char *data;
};

Window getCurrentWindow()
{
    if (!QX11Info::isPlatformX11())
        return 0L;

    auto display = QX11Info::display();
    XSync(display, False);

    static Atom atomWindow = XInternAtom(display, "_NET_ACTIVE_WINDOW", true);

    X11WindowProperty property(display, DefaultRootWindow(display), atomWindow, 0l, 1l, XA_WINDOW);

    if ( property.isValid() && property.type == XA_WINDOW && property.format == 32 && property.len == 1)
        return *reinterpret_cast<Window *>(property.data);

    return 0L;
}



X11PlatformWindow::X11PlatformWindow(Window winId)
    : m_window(winId)
{
}


void X11PlatformWindow::raise()
{
    Q_ASSERT( isValid() );

    if (!QX11Info::isPlatformX11())
        return;


    auto display = QX11Info::display();

    XEvent e{};
    memset(&e, 0, sizeof(e));
    e.type = ClientMessage;
    e.xclient.display = display;
    e.xclient.window = m_window;
    e.xclient.message_type = XInternAtom(display, "_NET_ACTIVE_WINDOW", False);
    e.xclient.format = 32;
    e.xclient.data.l[0] = 2;
    e.xclient.data.l[1] = CurrentTime;
    e.xclient.data.l[2] = 0;
    e.xclient.data.l[3] = 0;
    e.xclient.data.l[4] = 0;

    XWindowAttributes wattr{};
    XGetWindowAttributes(display, m_window, &wattr);

    if (wattr.map_state == IsViewable)
    {
        XSendEvent(display, wattr.screen->root, False,
                   SubstructureNotifyMask | SubstructureRedirectMask,
                   &e);
        XSync(display, False);
        XRaiseWindow(display, m_window);
        XSetInputFocus(display, m_window, RevertToPointerRoot, CurrentTime);
        XSync(display, False);
    }
}

void X11PlatformWindow::pasteClipboard()
{
    sendKeyPress(XK_Shift_L, XK_Insert);
//    sendKeyPress(XK_Shift_L, XK_Insert);
}

void X11PlatformWindow::copy()
{

}

bool X11PlatformWindow::isValid() const
{
    return m_window != 0L;
}

bool X11PlatformWindow::waitForFocus(int ms)
{
    Q_ASSERT( isValid() );

    if (ms >= 0)
    {
        SleepTimer t(ms);
        while (t.sleep())
        {
            const auto currentWindow = getCurrentWindow();
            if (currentWindow == m_window)
                return true;
        }
    }

    return m_window == getCurrentWindow();
}

void X11PlatformWindow::sendKeyPress(int modifier, int key)
{
    Q_ASSERT( isValid() );

    if ( !waitForFocus(50) )
    {
        raise();
        if ( !waitForFocus(150) )
        {
            return;
        }
    }

    waitMs(5000);

    if (!QX11Info::isPlatformX11())
        return;

    auto display = QX11Info::display();

    const int modifierMask = (modifier == XK_Control_L) ? ControlMask : ShiftMask;
    simulateKeyPress(display, m_window, modifierMask, key);

}

这是来自 mainwindow.cpp 的测试代码

void MainWindow::on_button_clicked()
{
    clipboard = QApplication::clipboard();
    
    clipboard->setText("TEST");
    
    this->hide();

    X11PlatformWindow window(lastWindow); // the code I use to get the lastwindow is a little lengthy so I am omitting it

    window.raise();

    qDebug() << lastWindow;

    window.pasteClipboard();
}

此代码将文本粘贴到 Qt Creator 编辑器和浏览器的搜索栏。

我的猜测是窗口被赋予了输入焦点。`X11PlatformWindow::raise() 应该这样做,但我不确定它是否有效。

我找不到如何知道是否XSetInputFocus()成功。

标签: c++qtclipboardxlib

解决方案


我找不到如何知道 XSetInputFocus() 是否成功。

第一步是设置 X 错误处理程序,您将自定义函数指针传递到其中,该指针将解释生成的错误。

https://tronche.com/gui/x/xlib/event-handling/protocol-errors/XSetErrorHandler.html

您要处理的错误XSetInputFocus是:

XSetInputFocus() 可以生成 BadMatch、BadValue 和 BadWindow 错误。

https://tronche.com/gui/x/xlib/input/XSetInputFocus.html

编辑:

X11 用户注意事项

X11 窗口系统具有单独的选择和剪贴板的概念。选择文本后,它立即可用作全局鼠标选择。全局鼠标选择稍后可能会复制到剪贴板。按照惯例,鼠标中键用于粘贴全局鼠标选择。

X11也有所有权的概念;如果您在窗口内更改选择,X11 只会通知所有者和之前的所有者更改,即它不会通知所有应用程序选择或剪贴板数据已更改。

最后,X11 剪贴板是事件驱动的,即如果事件循环没有运行,剪贴板将无法正常工作。类似地,建议存储或检索剪贴板的内容以直接响应用户输入事件,例如鼠标按钮或按键按下和释放。您不应存储或检索剪贴板内容以响应计时器或非用户输入事件。

由于没有在 X11 上的应用程序之间复制和粘贴文件的标准方法,因此目前正在使用各种 MIME 类型和约定。例如,Nautilus 期望为文件提供 x-special/gnome-copied-files MIME 类型,其中数据以剪切/复制操作、换行符和文件的 URL 开头。

https://doc.qt.io/qt-5/qclipboard.html

这正是您在代码中所做的:

您不应存储或检索剪贴板内容以响应计时器或非用户输入事件。


推荐阅读