kdeui: KSelectionOwner rewrite

natural selection - kill or be killed

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2022-11-05 08:26:58 +02:00
parent d37ea70af9
commit c2a98d4c5d
11 changed files with 203 additions and 925 deletions

View file

@ -251,7 +251,6 @@ install(
KSaveFile
KSelectAction
KSelectionOwner
KSelectionWatcher
KSelector
KSeparator
KService

View file

@ -1 +1 @@
#include "../kmanagerselection.h"
#include "../kselectionowner.h"

View file

@ -1 +0,0 @@
#include "../kmanagerselection.h"

View file

@ -181,7 +181,7 @@ set(kdeui_LIB_SRCS
util/kcursor.cpp
util/kguiitem.cpp
util/kkeyserver.cpp
util/kmanagerselection.cpp
util/kselectionowner.cpp
util/knumvalidator.cpp
util/kpassivepopup.cpp
util/kpassivepopupmessagehandler.cpp
@ -500,7 +500,7 @@ install(
util/kcursor.h
util/kguiitem.h
util/kkeyserver.h
util/kmanagerselection.h
util/kselectionowner.h
util/knumvalidator.h
util/kpassivepopup.h
util/kpassivepopupmessagehandler.h

View file

@ -143,9 +143,7 @@ if (Q_WS_X11)
fixx11h_test
fixx11h_test2
kxerrorhandlertest
kmanagerselectiontest
)
target_link_libraries(kdeui-kmanagerselectiontest ${X11_X11_LIB})
target_link_libraries(kdeui-kxerrorhandlertest ${X11_X11_LIB})
endif (Q_WS_X11)

View file

@ -1,167 +0,0 @@
/*
This file is part of the KDE Libraries
Copyright (C) 2009 Lubos Lunak <l.lunak@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kmanagerselectiontest.h"
#include <QtTest>
#include <kapplication.h>
#include <kmanagerselection.h>
#include <qx11info_x11.h>
#define SNAME "_KDE_KMANAGERSELECTIONTEST"
using namespace QTest;
void KManagerSelectionTest::testAcquireRelease()
{ // test that newOwner() is emitted when there is a new selection owner
KSelectionWatcher watcher( SNAME );
KSelectionOwner owner( SNAME );
QVERIFY( owner.ownerWindow() == None );
QVERIFY( watcher.owner() == None );
SigCheckWatcher sw( watcher );
SigCheckOwner so( owner );
QVERIFY( owner.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL(newOwner(Window)), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
QVERIFY( so.lostownership == false );
}
void KManagerSelectionTest::testInitiallyOwned()
{ // test that lostOwner() is emitted when the selection is disowned
KSelectionOwner owner( SNAME );
SigCheckOwner so( owner );
QVERIFY( owner.claim( false ));
KSelectionWatcher watcher( SNAME );
SigCheckWatcher sw( watcher );
owner.release();
QVERIFY( kWaitForSignal( &watcher, SIGNAL(lostOwner()), 2000 ));
QVERIFY( sw.newowner == false );
QVERIFY( sw.lostowner == true );
QVERIFY( so.lostownership == false );
}
void KManagerSelectionTest::testLostOwnership()
{ // test that lostOwnership() is emitted when something else forces taking the ownership
KSelectionOwner owner1( SNAME );
KSelectionOwner owner2( SNAME );
QVERIFY( owner1.claim( false ));
QVERIFY( !owner2.claim( false ));
XEvent ev;
ev.xselectionclear.type = SelectionClear;
ev.xselectionclear.serial = XLastKnownRequestProcessed( QX11Info::display());
ev.xselectionclear.send_event = True;
ev.xselectionclear.display = QX11Info::display();
ev.xselectionclear.window = owner1.ownerWindow();
ev.xselectionclear.selection = XInternAtom( QX11Info::display(), SNAME, False );
ev.xselectionclear.time = QX11Info::appTime();
QVERIFY( owner2.claim( true, false ));
// SelectionClear event is not sent to the same X client, so fake it
XPutBackEvent( QX11Info::display(), &ev );
QVERIFY( kWaitForSignal( &owner1, SIGNAL(lostOwnership()), 2000 ));
QVERIFY( owner1.ownerWindow() == None );
QVERIFY( owner2.ownerWindow() != None );
}
void KManagerSelectionTest::testWatching()
{ // test that KSelectionWatcher reports changes properly
KSelectionWatcher watcher( SNAME );
KSelectionOwner owner1( SNAME );
KSelectionOwner owner2( SNAME );
SigCheckWatcher sw( watcher );
QVERIFY( owner1.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL(newOwner(Window)), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
sw.newowner = sw.lostowner = false;
QVERIFY( owner2.claim( true, false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL(newOwner(Window)), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
sw.newowner = sw.lostowner = false;
owner2.release();
QVERIFY( kWaitForSignal( &watcher, SIGNAL(lostOwner()), 2000 ));
QVERIFY( sw.newowner == false );
QVERIFY( sw.lostowner == true );
sw.newowner = sw.lostowner = false;
QVERIFY( owner2.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL(newOwner(Window)), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
}
SigCheckOwner::SigCheckOwner( const KSelectionOwner& owner )
: lostownership( false )
{
connect( &owner, SIGNAL(lostOwnership()), this, SLOT(lostOwnership()));
}
void SigCheckOwner::lostOwnership()
{
lostownership = true;
}
SigCheckWatcher::SigCheckWatcher( const KSelectionWatcher& watcher )
: newowner( false )
, lostowner( false )
{
connect( &watcher, SIGNAL(newOwner(Window)), this, SLOT(newOwner()));
connect( &watcher, SIGNAL(lostOwner()), this, SLOT(lostOwner()));
}
void SigCheckWatcher::newOwner()
{
newowner = true;
}
void SigCheckWatcher::lostOwner()
{
lostowner = true;
}
#include <kapplication.h>
// the tested classes need KApplication - this is from qtest_kde.h, with QApp -> KApp
#define QTEST_KDEMAIN_WITH_COMPONENTNAME_KAPP(TestObject, flags, componentName) \
int main(int argc, char *argv[]) \
{ \
setenv("LC_ALL", "C", 1); \
assert( !QDir::homePath().isEmpty() ); \
setenv("KDEHOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test") ), 1); \
setenv("XDG_DATA_HOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test/xdg/local") ), 1); \
setenv("XDG_CONFIG_HOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test/xdg/config") ), 1); \
unsetenv("KDE_DEBUG_COLOR"); \
QFile::remove(QDir::homePath() + QLatin1String("/.kde-unit-test/share/config/qttestrc")); \
KAboutData aboutData( QByteArray(componentName), QByteArray(), ki18n("KDE Test Program"), QByteArray("version") ); \
KCmdLineArgs::init( argc, argv, &aboutData); \
KApplication app; \
app.setApplicationName( QLatin1String("qttest") ); \
qRegisterMetaType<KUrl>(); /*as done by kapplication*/ \
qRegisterMetaType<KUrl::List>(); \
TestObject tc; \
KGlobal::ref(); /* don't quit qeventloop after closing a mainwindow */ \
return QTest::qExec( &tc, argc, argv ); \
}
#define QTEST_KDEMAIN_KAPP(TestObject, flags) QTEST_KDEMAIN_WITH_COMPONENTNAME_KAPP(TestObject, flags, "qttest")
QTEST_KDEMAIN_KAPP(KManagerSelectionTest, GUI)

View file

@ -1,71 +0,0 @@
/*
This file is part of the KDE Libraries
Copyright (C) 2009 Lubos Lunak <l.lunak@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KMANAGERSELECTIONTESTTEST_H
#define KMANAGERSELECTIONTESTTEST_H
#include <QtCore/QObject>
#include "qtest_kde.h"
class KManagerSelectionTest
: public QObject
{
Q_OBJECT
public:
private Q_SLOTS:
void testAcquireRelease();
void testInitiallyOwned();
void testLostOwnership();
void testWatching();
};
class KSelectionOwner;
class KSelectionWatcher;
// For checking whether several signal have or have not been emitted,
// kWaitForSignal() is not powerful enough for that (it may be still
// needed to do the event processing though).
class SigCheckOwner
: public QObject
{
Q_OBJECT
public:
SigCheckOwner( const KSelectionOwner& owner );
private Q_SLOTS:
void lostOwnership();
public:
bool lostownership;
};
class SigCheckWatcher
: public QObject
{
Q_OBJECT
public:
SigCheckWatcher( const KSelectionWatcher& watcher );
private Q_SLOTS:
void newOwner();
void lostOwner();
public:
bool newowner;
bool lostowner;
};
#endif

View file

@ -1,468 +0,0 @@
/****************************************************************************
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "kmanagerselection.h"
#include <config.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <QtCore/QObject>
#include <qx11info_x11.h>
#include <qwidget.h>
#include <kdebug.h>
#include <kapplication.h>
#include <kxerrorhandler.h>
#include <X11/Xatom.h>
class KSelectionOwner::Private : public QWidget
{
public:
Private( KSelectionOwner* owner_P, Atom selection_P, int screen_P )
: selection( selection_P ),
screen( screen_P >= 0 ? screen_P : DefaultScreen( QX11Info::display() ) ),
window( None ),
timestamp( CurrentTime ),
extra1( 0 ),
extra2( 0 ),
owner( owner_P )
{
}
const Atom selection;
const int screen;
Window window;
Time timestamp;
long extra1, extra2;
static Atom manager_atom;
static Atom xa_multiple;
static Atom xa_targets;
static Atom xa_timestamp;
protected:
virtual bool x11Event( XEvent* ev_P )
{
return owner->filterEvent( ev_P );
}
private:
KSelectionOwner* owner;
};
KSelectionOwner::KSelectionOwner( Atom selection_P, int screen_P, QObject* parent_P )
: QObject( parent_P ),
d( new Private( this, selection_P, screen_P ) )
{
kapp->installX11EventFilter( d );
}
KSelectionOwner::KSelectionOwner( const char* selection_P, int screen_P, QObject* parent_P )
: QObject( parent_P ),
d( new Private( this, XInternAtom( QX11Info::display(), selection_P, False ), screen_P ) )
{
kapp->installX11EventFilter( d );
}
KSelectionOwner::~KSelectionOwner()
{
if (kapp) {
kapp->removeX11EventFilter( d );
}
release();
delete d;
}
bool KSelectionOwner::claim( bool force_P, bool force_kill_P )
{
if ( Private::manager_atom == None ) {
getAtoms();
}
if ( d->timestamp != CurrentTime ) {
release();
}
Display* const dpy = QX11Info::display();
Window prev_owner = XGetSelectionOwner( dpy, d->selection );
if ( prev_owner != None ) {
if ( !force_P ) {
// kDebug() << "Selection already owned, failing";
return false;
}
XSelectInput( dpy, prev_owner, StructureNotifyMask );
}
XSetWindowAttributes attrs;
attrs.override_redirect = True;
d->window = XCreateWindow( dpy, RootWindow( dpy, d->screen ), 0, 0, 1, 1,
0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attrs );
// kDebug() << "Using owner window " << window;
Atom tmp = XA_ATOM;
XSelectInput( dpy, d->window, PropertyChangeMask );
XChangeProperty( dpy, d->window, XA_ATOM, XA_ATOM, 32, PropModeReplace,
reinterpret_cast< unsigned char* >( &tmp ), 1 );
XEvent ev;
XSync( dpy, False );
XCheckTypedWindowEvent( dpy, d->window, PropertyNotify, &ev ); // get a timestamp
d->timestamp = ev.xproperty.time;
XSelectInput( dpy, d->window, StructureNotifyMask ); // for DestroyNotify
XSetSelectionOwner( dpy, d->selection, d->window, d->timestamp );
Window new_owner = XGetSelectionOwner( dpy, d->selection );
if ( new_owner != d->window ) {
// kDebug() << "Failed to claim selection : " << new_owner;
XDestroyWindow( dpy, d->window );
d->timestamp = CurrentTime;
return false;
}
if ( prev_owner != None ) {
// kDebug() << "Waiting for previous owner to disown";
for ( int cnt = 0; ; ++cnt ) {
if ( XCheckTypedWindowEvent( dpy, prev_owner, DestroyNotify, &ev ) == True ) {
break;
}
struct timeval tm = { 0, 50000 }; // 50 ms
select( 0, NULL, NULL, NULL, &tm );
if ( cnt == 19 ) {
if ( force_kill_P ) {
KXErrorHandler err;
// kDebug() << "Killing previous owner";
XKillClient( dpy, prev_owner );
err.error( true ); // ignore errors when killing
}
break;
}
}
}
ev.type = ClientMessage;
ev.xclient.window = RootWindow( dpy, d->screen );
ev.xclient.display = dpy;
ev.xclient.message_type = Private::manager_atom;
ev.xclient.format = 32;
ev.xclient.data.l[ 0 ] = d->timestamp;
ev.xclient.data.l[ 1 ] = d->selection;
ev.xclient.data.l[ 2 ] = d->window;
ev.xclient.data.l[ 3 ] = d->extra1;
ev.xclient.data.l[ 4 ] = d->extra2;
XSendEvent( dpy, RootWindow( dpy, d->screen ), False, StructureNotifyMask, &ev );
// kDebug() << "Claimed selection";
return true;
}
// destroy resource first
void KSelectionOwner::release()
{
if ( d->timestamp == CurrentTime ) {
return;
}
XDestroyWindow( QX11Info::display(), d->window ); // also makes the selection not owned
// kDebug() << "Releasing selection";
d->timestamp = CurrentTime;
}
Window KSelectionOwner::ownerWindow() const
{
if ( d->timestamp == CurrentTime ) {
return None;
}
return d->window;
}
void KSelectionOwner::setData( long extra1_P, long extra2_P )
{
d->extra1 = extra1_P;
d->extra2 = extra2_P;
}
bool KSelectionOwner::filterEvent( XEvent* ev_P )
{
if ( d->timestamp != CurrentTime && ev_P->xany.window == d->window ) {
if ( handleMessage( ev_P )) {
return true;
}
}
switch( ev_P->type ) {
case SelectionClear: {
if ( d->timestamp == CurrentTime || ev_P->xselectionclear.selection != d->selection ) {
return false;
}
d->timestamp = CurrentTime;
// kDebug() << "Lost selection";
Window window = d->window;
emit lostOwnership();
XSelectInput( QX11Info::display(), window, 0 );
XDestroyWindow( QX11Info::display(), window );
return true;
}
case DestroyNotify: {
if ( d->timestamp == CurrentTime || ev_P->xdestroywindow.window != d->window ) {
return false;
}
d->timestamp = CurrentTime;
// kDebug() << "Lost selection (destroyed)";
emit lostOwnership();
return true;
}
case SelectionNotify: {
if ( d->timestamp == CurrentTime || ev_P->xselection.selection != d->selection ) {
return false;
}
// ignore?
return false;
}
case SelectionRequest: {
filter_selection_request( ev_P->xselectionrequest );
return false;
}
}
return false;
}
bool KSelectionOwner::handleMessage( XEvent* )
{
return false;
}
void KSelectionOwner::filter_selection_request( XSelectionRequestEvent& ev_P )
{
if ( d->timestamp == CurrentTime || ev_P.selection != d->selection ) {
return;
}
if ( ev_P.time != CurrentTime
&& ev_P.time - d->timestamp > 1U << 31 ) {
return; // too old or too new request
}
// kDebug() << "Got selection request";
bool handled = false;
if ( ev_P.target == Private::xa_multiple ) {
if ( ev_P.property != None ) {
const int MAX_ATOMS = 100; // no need to handle more?
int format;
Atom type;
unsigned long items;
unsigned long after;
unsigned char* data;
if ( XGetWindowProperty( QX11Info::display(), ev_P.requestor, ev_P.property, 0,
MAX_ATOMS, False, AnyPropertyType, &type, &format, &items, &after,
&data ) == Success && format == 32 && items % 2 == 0 ) {
bool handled_array[ MAX_ATOMS ];
Atom* atoms = reinterpret_cast< Atom* >( data );
for ( unsigned int i = 0; i < items / 2; ++i ) {
handled_array[ i ] = handle_selection( atoms[ i * 2 ], atoms[ i * 2 + 1 ], ev_P.requestor );
}
bool all_handled = true;
for ( unsigned int i = 0; i < items / 2; ++i ) {
if ( !handled_array[ i ] ) {
all_handled = false;
atoms[ i * 2 + 1 ] = None;
}
}
if ( !all_handled ) {
XChangeProperty( QX11Info::display(), ev_P.requestor, ev_P.property, XA_ATOM,
32, PropModeReplace, reinterpret_cast< unsigned char* >( atoms ), items );
}
handled = true;
XFree( data );
}
}
} else {
if ( ev_P.property == None ) { // obsolete client
ev_P.property = ev_P.target;
}
handled = handle_selection( ev_P.target, ev_P.property, ev_P.requestor );
}
XEvent ev;
ev.xselection.selection = ev_P.selection;
ev.xselection.type = SelectionNotify;
ev.xselection.display = QX11Info::display();
ev.xselection.requestor = ev_P.requestor;
ev.xselection.target = ev_P.target;
ev.xselection.property = handled ? ev_P.property : None;
XSendEvent( QX11Info::display(), ev_P.requestor, False, 0, &ev );
}
bool KSelectionOwner::handle_selection( Atom target_P, Atom property_P, Window requestor_P )
{
if ( target_P == Private::xa_timestamp ) {
// kDebug() << "Handling timestamp request";
XChangeProperty( QX11Info::display(), requestor_P, property_P, XA_INTEGER, 32,
PropModeReplace, reinterpret_cast< unsigned char* >( &d->timestamp ), 1 );
} else if ( target_P == Private::xa_targets ) {
replyTargets( property_P, requestor_P );
} else if ( genericReply( target_P, property_P, requestor_P )) {
; // handled
} else {
return false; // unknown
}
return true;
}
void KSelectionOwner::replyTargets( Atom property_P, Window requestor_P )
{
Atom atoms[ 3 ] = { Private::xa_multiple, Private::xa_timestamp, Private::xa_targets };
// kDebug() << "Handling targets request";
XChangeProperty( QX11Info::display(), requestor_P, property_P, XA_ATOM, 32, PropModeReplace,
reinterpret_cast< unsigned char* >( atoms ), 3 );
}
bool KSelectionOwner::genericReply( Atom, Atom, Window )
{
return false;
}
void KSelectionOwner::getAtoms()
{
if ( Private::manager_atom == None ) {
Atom atoms[ 4 ];
const char* const names[] = { "MANAGER", "MULTIPLE", "TARGETS", "TIMESTAMP" };
XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 4, False, atoms );
Private::manager_atom = atoms[ 0 ];
Private::xa_multiple = atoms[ 1 ];
Private::xa_targets = atoms[ 2 ];
Private::xa_timestamp = atoms[ 3 ];
}
}
Atom KSelectionOwner::Private::manager_atom = None;
Atom KSelectionOwner::Private::xa_multiple = None;
Atom KSelectionOwner::Private::xa_targets = None;
Atom KSelectionOwner::Private::xa_timestamp = None;
//*******************************************
// KSelectionWatcher
//*******************************************
class KSelectionWatcher::Private : public QWidget
{
public:
Private( KSelectionWatcher* watcher_P, Atom selection_P, int screen_P )
: selection( selection_P ),
screen( screen_P >= 0 ? screen_P : DefaultScreen( QX11Info::display())),
selection_owner( None ),
watcher( watcher_P )
{
kapp->installX11EventFilter( this );
}
const Atom selection;
const int screen;
Window selection_owner;
static Atom manager_atom;
protected:
virtual bool x11Event( XEvent* ev_P )
{
watcher->filterEvent( ev_P );
return false;
}
private:
KSelectionWatcher* watcher;
};
KSelectionWatcher::KSelectionWatcher( Atom selection_P, int screen_P, QObject* parent_P )
: QObject( parent_P ),
d( new Private( this, selection_P, screen_P ))
{
init();
}
KSelectionWatcher::KSelectionWatcher( const char* selection_P, int screen_P, QObject* parent_P )
: QObject( parent_P ),
d( new Private( this, XInternAtom( QX11Info::display(), selection_P, False ), screen_P ))
{
init();
}
KSelectionWatcher::~KSelectionWatcher()
{
delete d;
}
void KSelectionWatcher::init()
{
if ( Private::manager_atom == None ) {
Display* const dpy = QX11Info::display();
Private::manager_atom = XInternAtom( dpy, "MANAGER", False );
XWindowAttributes attrs;
XGetWindowAttributes( dpy, RootWindow( dpy, d->screen ), &attrs );
long event_mask = attrs.your_event_mask;
// StructureNotifyMask on the root window is needed
XSelectInput( dpy, RootWindow( dpy, d->screen ), event_mask | StructureNotifyMask );
}
owner(); // trigger reading of current selection status
}
Window KSelectionWatcher::owner()
{
Display* const dpy = QX11Info::display();
KXErrorHandler handler;
Window current_owner = XGetSelectionOwner( dpy, d->selection );
if ( current_owner == None )
return None;
if ( current_owner == d->selection_owner ) {
return d->selection_owner;
}
XSelectInput( dpy, current_owner, StructureNotifyMask );
if ( !handler.error( true ) && current_owner == XGetSelectionOwner( dpy, d->selection )) {
// kDebug() << "isOwner: " << current_owner;
d->selection_owner = current_owner;
emit newOwner( d->selection_owner );
} else {
d->selection_owner = None;
}
return d->selection_owner;
}
// void return value in order to allow more watchers in one process
void KSelectionWatcher::filterEvent( XEvent* ev_P )
{
if ( ev_P->type == ClientMessage ) {
// kDebug() << "got ClientMessage";
if ( ev_P->xclient.message_type != Private::manager_atom
|| ev_P->xclient.data.l[ 1 ] != static_cast< long >( d->selection )) {
return;
}
// kDebug() << "handling message";
if ( static_cast< long >( owner()) == ev_P->xclient.data.l[ 2 ] ) {
// owner() emits newOwner() if needed, no need to do it twice
}
return;
}
if ( ev_P->type == DestroyNotify ) {
if ( d->selection_owner == None || ev_P->xdestroywindow.window != d->selection_owner ) {
return;
}
d->selection_owner = None; // in case the exactly same ID gets reused as the owner
if ( owner() == None ) {
emit lostOwner(); // it must be safe to delete 'this' in a slot
}
return;
}
}
Atom KSelectionWatcher::Private::manager_atom = None;
#include "moc_kmanagerselection.cpp"

View file

@ -1,212 +0,0 @@
/****************************************************************************
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
****************************************************************************/
#ifndef KMANAGERSELECTION_H
#define KMANAGERSELECTION_H
#include <kdeui_export.h>
#include <QtCore/QObject>
#ifdef Q_WS_X11 // FIXME(E)
#include <X11/Xlib.h>
#include <fixx11h.h>
/**
This class implements claiming and owning manager selections, as described
in the ICCCM, section 2.8. The selection atom is passed to the constructor,
claim() attemps to claim ownership of the selection, release() gives up
the selection ownership. Signal lostOwnership() is emitted when the selection
is claimed by another owner.
@short ICCCM manager selection owner
*/
class KDEUI_EXPORT KSelectionOwner
: public QObject
{
Q_OBJECT
public:
/**
* This constructor initializes the object, but doesn't perform any
* operation on the selection.
*
* @param selection atom representing the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or NULL if there is none
*/
explicit KSelectionOwner( Atom selection, int screen = -1, QObject* parent = NULL );
/**
* @overload
* This constructor accepts the selection name and creates the appropriate atom
* for it automatically.
*
* @param selection name of the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or NULL if there is none
*/
explicit KSelectionOwner( const char* selection, int screen = -1, QObject* parent = NULL );
/**
* Destructor. Calls release().
*/
virtual ~KSelectionOwner();
/**
* This function attemps to claim ownership of the manager selection, using
* the current X timestamp. If @p force is false, and the selection is already
* owned, the selection is not claimed, and false is returned. If claiming
* is forced and the selection is owned by another client, it is waited for up to 1 second
* for the previous owner to disown the selection, if @p force_kill is true,
* and the previous owner fails to disown the selection in time,
* it will be forcibly killed. True is returned after successfully claiming
* ownership of the selection.
*/
bool claim( bool force, bool force_kill = true );
/**
* If the selection is owned, the ownership is given up.
*/
void release();
/**
* If the selection is owned, returns the window used internally
* for owning the selection.
*/
Window ownerWindow() const; // None if not owning the selection
/**
* @internal
*/
bool filterEvent( XEvent* ev_P ); // internal
Q_SIGNALS:
/**
* This signal is emitted if the selection was owned and the ownership
* has been lost due to another client claiming it, this signal is emitted.
* IMPORTANT: It's not safe to delete the instance in a slot connected
* to this signal.
*/
void lostOwnership();
protected:
/**
* Called for every X event received on the window used for owning
* the selection. If true is returned, the event is filtered out.
*/
virtual bool handleMessage( XEvent* ev );
/**
* Called when a SelectionRequest event is received. A reply should
* be sent using the selection handling mechanism described in the ICCCM
* section 2.
*
* @param target requested target type
* @param property property to use for the reply data
* @param requestor requestor window
*/
virtual bool genericReply( Atom target, Atom property, Window requestor );
/**
* Called to announce the supported targets, as described in the ICCCM
* section 2.6. The default implementation announces the required targets
* MULTIPLE, TIMESTAMP and TARGETS.
*/
virtual void replyTargets( Atom property, Window requestor );
/**
* Called to create atoms needed for claiming the selection and
* communication using the selection handling mechanism. The default
* implementation must be called if reimplemented. This method
* may be called repeatedly.
*/
virtual void getAtoms();
/**
* Sets extra data to be sent in the message sent to root window
* after successfully claiming a selection. These extra data
* are in data.l[3] and data.l[4] fields of the XClientMessage.
*/
void setData( long extra1, long extra2 );
private:
void filter_selection_request( XSelectionRequestEvent& ev_P );
bool handle_selection( Atom target_P, Atom property_P, Window requestor_P );
class Private;
Private* const d;
};
/**
This class implements watching manager selections, as described in the ICCCM
section 2.8. It emits signal newOwner() when a new owner claim the selection,
and emits lostOwner() when the selection ownership is given up. To find
out current owner of the selection, owner() can be used.
@short ICCCM manager selection watching
*/
class KDEUI_EXPORT KSelectionWatcher
: public QObject
{
Q_OBJECT
public:
/**
* This constructor initializes the object, but doesn't perform any
* operation on the selection.
*
* @param selection atom representing the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or NULL if there is none
*/
explicit KSelectionWatcher( Atom selection, int screen = -1, QObject* parent = NULL );
/**
* @overload
* This constructor accepts the selection name and creates the appropriate atom
* for it automatically.
*
* @param selection name of the manager selection
* @param screen X screen, or -1 for default
* @param parent parent object, or NULL if there is none
*/
explicit KSelectionWatcher( const char* selection, int screen = -1, QObject* parent = NULL );
virtual ~KSelectionWatcher();
/**
* Return the current owner of the manager selection, if any. Note that if the event
* informing about the owner change is still in the input queue, newOwner() might
* have been emitted yet.
*/
Window owner();
/**
* @internal
*/
void filterEvent( XEvent* ev_P ); // internal
Q_SIGNALS:
/**
* This signal is emitted when the selection is successfully claimed by a new
* owner.
* @param owner the new owner of the selection
*/
void newOwner( Window owner );
/**
* This signal is emitted when the selection is given up, i.e. there's no
* owner. Note that the selection may be immediatelly claimed again,
* so the newOwner() signal may be emitted right after this one.
* It's safe to delete the instance in a slot connected to this signal.
*/
void lostOwner();
private:
void init();
class Private;
Private* const d;
};
#endif
#endif

View file

@ -0,0 +1,144 @@
/* This file is part of the KDE libraries
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kselectionowner.h"
#include "kxerrorhandler.h"
#include "kdebug.h"
#include <QX11Info>
#include <QApplication>
#include <QThread>
#include <QTimer>
#define KSELECTIONOWNER_TIMEOUT 400
#define KSELECTIONOWNER_SLEEPTIME 400
#define KSELECTIONOWNER_CHECKTIME 200
class KSelectionOwnerPrivate
{
public:
KSelectionOwnerPrivate();
Atom x11atom;
Display* x11display;
int x11screen;
Window x11window;
};
KSelectionOwnerPrivate::KSelectionOwnerPrivate()
: x11atom(None),
x11display(QX11Info::display()),
x11screen(QX11Info::appScreen()),
x11window(None)
{
}
KSelectionOwner::KSelectionOwner(const char* atom, const int screen, QObject *parent)
: QObject(parent),
d(new KSelectionOwnerPrivate())
{
d->x11atom = XInternAtom(d->x11display, atom, False);
if (screen >= 0) {
d->x11screen = screen;
}
}
KSelectionOwner::~KSelectionOwner()
{
release();
delete d;
}
bool KSelectionOwner::claim(const bool force)
{
Window currentowner = XGetSelectionOwner(d->x11display, d->x11atom);
if (currentowner != None && !force) {
kDebug() << "Selection is owned";
return false;
}
if (currentowner != None) {
kDebug() << "Selection is owned, destroying owner";
XDestroyWindow(d->x11display, currentowner);
XFlush(d->x11display);
ushort counter = 0;
kDebug() << "Waiting for owner";
while (currentowner != None && counter < 10) {
currentowner = XGetSelectionOwner(d->x11display, d->x11atom);
QCoreApplication::processEvents(QEventLoop::AllEvents, KSELECTIONOWNER_TIMEOUT);
QThread::msleep(KSELECTIONOWNER_SLEEPTIME);
counter++;
}
}
if (currentowner != None) {
kDebug() << "Selection is owned, killing owner";
KXErrorHandler kx11errorhandler;
XKillClient(d->x11display, currentowner);
XFlush(d->x11display);
if (kx11errorhandler.error(true)) {
kWarning() << KXErrorHandler::errorMessage(kx11errorhandler.errorEvent());
return false;
}
}
kDebug() << "Creating selection owner";
XSetWindowAttributes x11attrs;
x11attrs.override_redirect = True;
d->x11window = XCreateWindow(
d->x11display, RootWindow(d->x11display, d->x11screen),
0, 0, 1, 1,
0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &x11attrs
);
XSetSelectionOwner(d->x11display, d->x11atom, d->x11window, CurrentTime);
XFlush(d->x11display);
QTimer::singleShot(KSELECTIONOWNER_CHECKTIME, this, SLOT(_checkOwnership()));
return true;
}
void KSelectionOwner::release()
{
if (d->x11window == None) {
kDebug() << "No owner";
return;
}
kDebug() << "Destroying owner window";
XDestroyWindow(d->x11display, d->x11window);
XFlush(d->x11display);
d->x11window = None;
}
Window KSelectionOwner::ownerWindow() const
{
kDebug() << "Current selection owner is" << d->x11window;
return d->x11window;
}
void KSelectionOwner::_checkOwnership()
{
if (d->x11window == None) {
kDebug() << "Not going to poll";
return;
}
// kDebug() << "Checking selection owner";
Window currentowner = XGetSelectionOwner(d->x11display, d->x11atom);
if (currentowner != d->x11window) {
kDebug() << "Selection owner changed";
emit lostOwnership();
return;
}
QTimer::singleShot(KSELECTIONOWNER_CHECKTIME, this, SLOT(_checkOwnership()));
}

View file

@ -0,0 +1,56 @@
/* This file is part of the KDE libraries
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KSELECTIONOWNER_H
#define KSELECTIONOWNER_H
#include <kdeui_export.h>
#include <QObject>
#include <X11/Xlib.h>
#include <fixx11h.h>
class KSelectionOwnerPrivate;
/*!
Class to claim selection of X11 atom.
*/
class KDEUI_EXPORT KSelectionOwner : public QObject
{
Q_OBJECT
public:
KSelectionOwner(const char* atom, const int screen = -1, QObject *parent = nullptr);
~KSelectionOwner();
bool claim(const bool force);
void release();
Window ownerWindow() const;
Q_SIGNALS:
void lostOwnership();
private Q_SLOTS:
void _checkOwnership();
private:
Q_DISABLE_COPY(KSelectionOwner);
KSelectionOwnerPrivate * const d;
};
#endif // KSELECTIONOWNER_H