问题描述
我正在编写一个过于简化的剪贴板管理器供个人使用,我正在尝试实现一项功能,我将在其中单击剪贴板历史记录中的文本并将其粘贴到我正在工作的窗口中。
CopyQ 有这个功能,所以我想看看它是如何在那里完成的。 我从那里获取了代码,我相信它可以满足我的要求:
sleeptimer.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,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
x11platformwindow.cpp
/*
copyright (c) 2020,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,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,sizeof(e));
e.type = ClientMessage;
e.xclient.display = display;
e.xclient.window = m_window;
e.xclient.message_type = XInternAtom(display,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,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,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()
是否成功。
解决方法
我不知道如何知道 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 Window System 具有单独选择和 剪贴板。当文本被选中时,它会立即作为 全局鼠标选择。全局鼠标选择稍后可能会被复制 到剪贴板。按照惯例,鼠标中键用于 粘贴全局鼠标选择。
X11也有所有权的概念;如果你改变选择 在一个窗口内,X11 只会通知所有者和以前的所有者 更改,即它不会通知所有应用程序 选择或剪贴板数据已更改。
最后,X11 剪贴板是事件驱动的,即剪贴板不会 如果事件循环未运行,则正常运行。同样,它是 建议存储或检索剪贴板的内容 直接响应用户输入事件,例如鼠标按钮或键 按下和释放。您不应存储或检索剪贴板 响应定时器或非用户输入事件的内容。
由于没有标准的方式来复制和粘贴文件 X11 上的应用程序,目前有各种 MIME 类型和约定 正在使用。例如,Nautilus 希望提供的文件带有 x-special/gnome-copied-files MIME 类型,数据以 剪切/复制操作、换行符和文件的 URL。
https://doc.qt.io/qt-5/qclipboard.html
这正是您在代码中所做的:
您不应存储或检索剪贴板内容以响应计时器或非用户输入事件。