feat: Adapt to qt6

This commit is contained in:
Mateusz Fibor 2025-01-05 17:07:13 +01:00
parent d444f26282
commit 4274042673
10 changed files with 140 additions and 188 deletions

View file

@ -79,11 +79,10 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia REQUIRED)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED)
find_package(X11 REQUIRED)
endif()
message(STATUS "[${PROJECT_NAME}] Qt version is: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}")
@ -310,13 +309,15 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
include_directories(${X11_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE
# qx11
Qt${QT_VERSION_MAJOR}::X11Extras
Qt${QT_VERSION_MAJOR}::CorePrivate
# xcb https://doc.qt.io/qt-5/linux-requirements.html
xcb
# pthread
${X11_LIBRARIES}
Threads::Threads
)
@ -334,5 +335,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Multimedia
Qt${QT_VERSION_MAJOR}::OpenGL
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
QtScrcpyCore
)

@ -1 +1 @@
Subproject commit cb9da00b4ac4e855b6cb8a9033fe45a1fabfd05b
Subproject commit 4ca295f2648cd41a0634d89c076908c63f3d5949

View file

@ -1,31 +1,37 @@
#include <QTcpSocket>
#include <QHostAddress>
#include <QAudioOutput>
#include <QTime>
#include <QAudioSink>
#include <QMediaDevices>
#include <QElapsedTimer>
#include <QProcess>
#include <QThread>
#include <QDebug>
#include <QByteArray>
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent)
: QObject(parent)
, m_outputDevice(nullptr)
, m_running(false)
, m_audioSink(nullptr)
{
connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput());
qInfo() << QString("AudioOutput::") << m_sndcpy.readAllStandardOutput();
});
connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError());
qInfo() << QString("AudioOutput::") << m_sndcpy.readAllStandardError();
});
}
AudioOutput::~AudioOutput()
{
if (QProcess::NotRunning != m_sndcpy.state()) {
if (m_sndcpy.state() != QProcess::NotRunning) {
m_sndcpy.kill();
}
stop();
}
bool AudioOutput::start(const QString& serial, int port)
bool AudioOutput::start(const QString &serial, int port)
{
if (m_running) {
stop();
@ -36,7 +42,7 @@ bool AudioOutput::start(const QString& serial, int port)
bool ret = runSndcpyProcess(serial, port);
qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds";
if (!ret) {
return ret;
return false;
}
startAudioOutput();
@ -64,20 +70,15 @@ void AudioOutput::installonly(const QString &serial, int port)
bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
{
if (QProcess::NotRunning != m_sndcpy.state()) {
if (m_sndcpy.state() != QProcess::NotRunning) {
m_sndcpy.kill();
}
#ifdef Q_OS_WIN32
QStringList params;
params << serial;
params << QString("%1").arg(port);
QStringList params{serial, QString::number(port)};
m_sndcpy.start("sndcpy.bat", params);
#else
QStringList params;
params << "sndcpy.sh";
params << serial;
params << QString("%1").arg(port);
QStringList params{"sndcpy.sh", serial, QString::number(port)};
m_sndcpy.start("bash", params);
#endif
@ -86,11 +87,11 @@ bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
}
if (!m_sndcpy.waitForStarted()) {
qWarning() << "AudioOutput::start sndcpy.bat failed";
qWarning() << "AudioOutput::start sndcpy process failed";
return false;
}
if (!m_sndcpy.waitForFinished()) {
qWarning() << "AudioOutput::sndcpy.bat crashed";
qWarning() << "AudioOutput::sndcpy process crashed";
return false;
}
@ -99,41 +100,39 @@ bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
void AudioOutput::startAudioOutput()
{
if (m_audioOutput) {
if (m_audioSink) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
format.setSampleFormat(QAudioFormat::Int16); // 16-bit signed integer format
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(format)) {
QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();
if (!defaultDevice.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioOutput = new QAudioOutput(format, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {
qInfo() << "AudioOutput::audio state changed:" << state;
});
m_audioOutput->setBufferSize(48000*2*15/1000 * 20);
m_outputDevice = m_audioOutput->start();
m_audioSink = new QAudioSink(defaultDevice, format, this);
m_outputDevice = m_audioSink->start();
if (!m_outputDevice) {
qWarning() << "AudioOutput::failed to start audio sink.";
delete m_audioSink;
m_audioSink = nullptr;
return;
}
}
void AudioOutput::stopAudioOutput()
{
if (!m_audioOutput) {
return;
if (m_audioSink) {
m_audioSink->stop();
delete m_audioSink;
m_audioSink = nullptr;
}
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = nullptr;
m_outputDevice = nullptr;
}
void AudioOutput::startRecvData(int port)
@ -156,7 +155,6 @@ void AudioOutput::startRecvData(int port)
});
connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {
qint64 recv = audioSocket->bytesAvailable();
//qDebug() << "AudioOutput::recv data:" << recv;
if (!m_outputDevice) {
return;
@ -165,22 +163,16 @@ void AudioOutput::startRecvData(int port)
m_buffer.reserve(recv);
}
qint64 count = audioSocket->read(m_buffer.data(), audioSocket->bytesAvailable());
qint64 count = audioSocket->read(m_buffer.data(), recv);
m_outputDevice->write(m_buffer.data(), count);
});
connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {
qInfo() << "AudioOutput::audio socket state changed:" << state;
});
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#else
connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#endif
m_workerThread.start();
emit connectTo(port);

View file

@ -6,6 +6,7 @@
#include <QPointer>
#include <QVector>
class QAudioSink;
class QAudioOutput;
class QIODevice;
class AudioOutput : public QObject
@ -36,6 +37,7 @@ private:
QProcess m_sndcpy;
QVector<char> m_buffer;
bool m_running = false;
QAudioSink *m_audioSink = nullptr;
};
#endif // AUDIOOUTPUT_H

View file

@ -8,7 +8,6 @@
#include "config.h"
#include "dialog.h"
#include "mousetap/mousetap.h"
static Dialog *g_mainDlg = Q_NULLPTR;
static QtMessageHandler g_oldMessageHandler = Q_NULLPTR;
@ -55,7 +54,6 @@ int main(int argc, char *argv[])
QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
}
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
@ -142,7 +140,7 @@ void installTranslator()
break;
}
translator.load(languagePath);
qInfo() << "Loading translation result =" << translator.load(languagePath);
qApp->installTranslator(&translator);
}

View file

@ -618,9 +618,11 @@ void Dialog::on_usbConnectBtn_clicked()
int Dialog::findDeviceFromeSerialBox(bool wifi)
{
QRegExp regIP("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\:([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])\\b");
QRegularExpression regIP(R"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\:(?:[0-9]|[1-9]\d{1,4}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$)");
for (int i = 0; i < ui->serialBox->count(); ++i) {
bool isWifi = regIP.exactMatch(ui->serialBox->itemText(i));
QRegularExpressionMatch match = regIP.match(ui->serialBox->itemText(i));
bool isWifi = match.hasMatch();
bool found = wifi ? isWifi : !isWifi;
if (found) {
return i;

View file

@ -68,7 +68,7 @@ void ToolForm::updateGroupControl()
void ToolForm::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept();
}
}
@ -81,7 +81,7 @@ void ToolForm::mouseReleaseEvent(QMouseEvent *event)
void ToolForm::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
move(event->globalPos() - m_dragPosition);
move(event->globalPosition().toPoint() - m_dragPosition);
event->accept();
}
}

View file

@ -1,4 +1,3 @@
#include <QDesktopWidget>
#include <QFileInfo>
#include <QLabel>
#include <QMessageBox>
@ -363,21 +362,10 @@ void VideoForm::installShortcut()
QRect VideoForm::getScreenRect()
{
QRect screenRect;
QWidget *win = window();
if (!win) {
return screenRect;
}
QWindow *winHandle = win->windowHandle();
QScreen *screen = QGuiApplication::primaryScreen();
if (winHandle) {
screen = winHandle->screen();
if (screen) {
screenRect = screen->availableGeometry();
}
if (!screen) {
return screenRect;
}
screenRect = screen->availableGeometry();
return screenRect;
}
@ -572,23 +560,25 @@ void VideoForm::mousePressEvent(QMouseEvent *event)
}
}
if (m_videoWidget->geometry().contains(event->pos())) {
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!device) {
return;
}
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
QPointF mappedPos = m_videoWidget->mapFrom(this, event->position().toPoint());
emit device->mouseEvent(new QMouseEvent(
event->type(), mappedPos, event->globalPosition(),
event->button(), event->buttons(), event->modifiers()),
m_videoWidget->frameSize(), m_videoWidget->size());
// debug keymap pos
if (event->button() == Qt::LeftButton) {
qreal x = event->localPos().x() / m_videoWidget->size().width();
qreal y = event->localPos().y() / m_videoWidget->size().height();
qreal x = mappedPos.x() / m_videoWidget->size().width();
qreal y = mappedPos.y() / m_videoWidget->size().height();
QString posTip = QString(R"("pos": {"x": %1, "y": %2})").arg(x).arg(y);
qInfo() << posTip.toStdString().c_str();
qInfo() << posTip;
}
} else {
if (event->button() == Qt::LeftButton) {
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept();
}
}
@ -601,23 +591,18 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
if (!device) {
return;
}
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
// local check
QPointF local = event->localPos();
if (local.x() < 0) {
local.setX(0);
}
if (local.x() > m_videoWidget->width()) {
local.setX(m_videoWidget->width());
}
if (local.y() < 0) {
local.setY(0);
}
if (local.y() > m_videoWidget->height()) {
local.setY(m_videoWidget->height());
}
event->setLocalPos(local);
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
QPointF mappedPos = m_videoWidget->mapFrom(this, event->position().toPoint());
QPointF local = mappedPos;
if (local.x() < 0) local.setX(0);
if (local.x() > m_videoWidget->width()) local.setX(m_videoWidget->width());
if (local.y() < 0) local.setY(0);
if (local.y() > m_videoWidget->height()) local.setY(m_videoWidget->height());
emit device->mouseEvent(new QMouseEvent(
event->type(), local, event->globalPosition(),
event->button(), event->buttons(), event->modifiers()),
m_videoWidget->frameSize(), m_videoWidget->size());
} else {
m_dragPosition = QPoint(0, 0);
}
@ -626,24 +611,25 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
void VideoForm::mouseMoveEvent(QMouseEvent *event)
{
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (m_videoWidget->geometry().contains(event->pos())) {
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!device) {
return;
}
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
} else if (!m_dragPosition.isNull()) {
if (event->buttons() & Qt::LeftButton) {
move(event->globalPos() - m_dragPosition);
event->accept();
}
QPointF mappedPos = m_videoWidget->mapFrom(this, event->position().toPoint());
emit device->mouseEvent(new QMouseEvent(
event->type(), mappedPos, event->globalPosition(),
event->button(), event->buttons(), event->modifiers()),
m_videoWidget->frameSize(), m_videoWidget->size());
} else if (!m_dragPosition.isNull() && (event->buttons() & Qt::LeftButton)) {
move(event->globalPosition().toPoint() - m_dragPosition);
event->accept();
}
}
void VideoForm::mouseDoubleClickEvent(QMouseEvent *event)
{
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
if (event->button() == Qt::LeftButton && !m_videoWidget->geometry().contains(event->pos())) {
if (event->button() == Qt::LeftButton && !m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!isMaximized()) {
removeBlackRect();
}
@ -653,38 +639,30 @@ void VideoForm::mouseDoubleClickEvent(QMouseEvent *event)
emit device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);
}
if (m_videoWidget->geometry().contains(event->pos())) {
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!device) {
return;
}
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
QPointF mappedPos = m_videoWidget->mapFrom(this, event->position().toPoint());
emit device->mouseEvent(new QMouseEvent(
event->type(), mappedPos, event->globalPosition(),
event->button(), event->buttons(), event->modifiers()),
m_videoWidget->frameSize(), m_videoWidget->size());
}
}
void VideoForm::wheelEvent(QWheelEvent *event)
{
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
if (!device) {
return;
}
QPointF pos = m_videoWidget->mapFrom(this, event->position().toPoint());
QWheelEvent wheelEvent(
pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(), event->buttons(), event->modifiers(), event->phase(), event->inverted());
#else
if (m_videoWidget->geometry().contains(event->pos())) {
if (!device) {
return;
}
QPointF pos = m_videoWidget->mapFrom(this, event->pos());
QWheelEvent wheelEvent(
pos, event->globalPosF(), event->pixelDelta(), event->angleDelta(), event->delta(), event->orientation(),
event->buttons(), event->modifiers(), event->phase(), event->source(), event->inverted());
#endif
emit device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
QWheelEvent adjustedEvent(
pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(),
event->buttons(), event->modifiers(), event->phase(), event->inverted());
emit device->wheelEvent(&adjustedEvent, m_videoWidget->frameSize(), m_videoWidget->size());
}
}
@ -714,7 +692,6 @@ void VideoForm::paintEvent(QPaintEvent *paint)
{
Q_UNUSED(paint)
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

View file

@ -113,10 +113,8 @@ QString Config::s_configPath = "";
Config::Config(QObject *parent) : QObject(parent)
{
m_settings = new QSettings(getConfigPath() + "/config.ini", QSettings::IniFormat);
m_settings->setIniCodec("UTF-8");
m_userData = new QSettings(getConfigPath() + "/userdata.ini", QSettings::IniFormat);
m_userData->setIniCodec("UTF-8");
qDebug()<<m_userData->childGroups();
}

View file

@ -1,11 +1,19 @@
#include <QX11Info>
#include <xcb/xproto.h>
#include <stdlib.h>
#include <stdint.h>
#include <QDebug>
#include <QRect>
#include <xcb/xproto.h>
#include <QGuiApplication>
#include <QRect>
#include "xmousetap.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <xcb/xcb.h>
XMouseTap::XMouseTap() {}
XMouseTap::~XMouseTap() {}
@ -14,71 +22,43 @@ void XMouseTap::initMouseEventTap() {}
void XMouseTap::quitMouseEventTap() {}
static void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window_t window,
QRect rc, int16_t offset_x, int16_t offset_y,
xcb_window_t *grab_window, uint32_t *grab_window_size) {
xcb_query_tree_cookie_t tree_cookie;
xcb_query_tree_reply_t *tree;
tree_cookie = xcb_query_tree(dpy, window);
tree = xcb_query_tree_reply(dpy, tree_cookie, NULL);
xcb_window_t *children = xcb_query_tree_children(tree);
for (int i = 0; i < xcb_query_tree_children_length(tree); i++) {
xcb_get_geometry_cookie_t gg_cookie;
xcb_get_geometry_reply_t *gg;
gg_cookie = xcb_get_geometry(dpy, children[i]);
gg = xcb_get_geometry_reply(dpy, gg_cookie, NULL);
if (gg->x + offset_x <= rc.left() && gg->x + offset_x + gg->width >= rc.right() &&
gg->y + offset_y <= rc.top() && gg->y + offset_y + gg->height >= rc.bottom()) {
if (!*grab_window || gg->width * gg->height <= *grab_window_size) {
*grab_window = children[i];
*grab_window_size = gg->width * gg->height;
}
}
find_grab_window_recursive(dpy, children[i], rc,
gg->x + offset_x, gg->y + offset_y,
grab_window, grab_window_size);
free(gg);
}
free(tree);
}
void XMouseTap::enableMouseEventTap(QRect rc, bool enabled) {
if (enabled && rc.isEmpty()) {
return;
}
xcb_connection_t *dpy = QX11Info::connection();
auto *x11Interface = qApp->nativeInterface<QNativeInterface::QX11Application>();
if (!x11Interface) {
qWarning() << "X11 interface is not available. Ensure the application is running on X11.";
return;
}
Display *display = reinterpret_cast<Display*>(x11Interface->display());
if (!display) {
qWarning() << "Failed to get X Display.";
return;
}
int screenNumber = DefaultScreen(display);
Window rootWindow = RootWindow(display, screenNumber);
XRectangle xRect;
xRect.x = static_cast<short>(rc.x());
xRect.y = static_cast<short>(rc.y());
xRect.width = static_cast<unsigned short>(rc.width());
xRect.height = static_cast<unsigned short>(rc.height());
if (enabled) {
// We grab the top-most smallest window
xcb_window_t grab_window = 0;
uint32_t grab_window_size = 0;
find_grab_window_recursive(dpy, QX11Info::appRootWindow(QX11Info::appScreen()),
rc, 0, 0, &grab_window, &grab_window_size);
if (grab_window) {
xcb_grab_pointer_cookie_t grab_cookie;
xcb_grab_pointer_reply_t *grab;
grab_cookie = xcb_grab_pointer(dpy, /* owner_events = */ 1,
grab_window, /* event_mask = */ 0,
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
grab_window, XCB_NONE, XCB_CURRENT_TIME);
grab = xcb_grab_pointer_reply(dpy, grab_cookie, NULL);
free(grab);
int result = XGrabPointer(display, rootWindow, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
if (result != GrabSuccess) {
qWarning() << "Failed to grab pointer.";
}
} else {
xcb_void_cookie_t ungrab_cookie;
xcb_generic_error_t *error;
ungrab_cookie = xcb_ungrab_pointer_checked(dpy, XCB_CURRENT_TIME);
error = xcb_request_check(dpy, ungrab_cookie);
free(error);
XUngrabPointer(display, CurrentTime);
}
XFlush(display);
}