/*
Copyright (c) 2017, 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 <QApplication>
#include "x11platformclipboard.h"
#include "common/common.h"
#include "common/mimetypes.h"
#include "common/log.h"
#include "x11displayguard.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
namespace {
/// Return true only if selection is incomplete, i.e. mouse button or shift key is pressed.
bool isSelectionIncomplete(Display *display)
{
// If mouse button or shift is pressed then assume that user is selecting text.
XEvent event{};
XQueryPointer(display, DefaultRootWindow(display),
&event.xbutton.root, &event.xbutton.window,
&event.xbutton.x_root, &event.xbutton.y_root,
&event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
return event.xbutton.state & (Button1Mask | ShiftMask);
}
bool isClipboardEmpty(Display *display)
{
static Atom atom = XInternAtom(display, "CLIPBOARD", False);
return XGetSelectionOwner(display, atom) == None;
}
bool isSelectionEmpty(Display *display)
{
static Atom atom = XA_PRIMARY;
return XGetSelectionOwner(display, atom) == None;
}
} // namespace
X11PlatformClipboard::X11PlatformClipboard(const std::shared_ptr<X11DisplayGuard> &d)
: d(d)
{
initSingleShotTimer( &m_timerCheckClipboard, 50, this, SLOT(onClipboardChanged()) );
initSingleShotTimer( &m_timerCheckSelection, 100, this, SLOT(onSelectionChanged()) );
initSingleShotTimer( &m_timerResetClipboard, 500, this, SLOT(resetClipboard()) );
initSingleShotTimer( &m_timerResetSelection, 500, this, SLOT(resetSelection()) );
}
void X11PlatformClipboard::loadSettings(const QVariantMap &settings)
{
m_formats = settings.value("formats", m_formats).toStringList();
}
QVariantMap X11PlatformClipboard::data(Mode mode, const QStringList &) const
{
return mode == PlatformClipboard::Clipboard ? m_clipboardData : m_selectionData;
}
void X11PlatformClipboard::setData(Mode mode, const QVariantMap &dataMap)
{
DummyClipboard::setData(mode, dataMap);
}
void X11PlatformClipboard::onChanged(QClipboard::Mode mode)
{
// Omit checking clipboard and selection too fast.
if (mode == QClipboard::Clipboard)
m_timerCheckClipboard.start();
else
m_timerCheckSelection.start();
}
void X11PlatformClipboard::onClipboardChanged()
{
m_timerResetClipboard.stop();
const QVariantMap data = DummyClipboard::data(Clipboard, m_formats);
const bool foreignData = !ownsClipboardData(data);
if ( foreignData && maybeResetClipboard() )
return;
if (m_clipboardData == data)
return;
m_clipboardData = data;
emit changed(Clipboard);
// Check selection too if some signals where not delivered.
m_timerCheckSelection.start();
}
void X11PlatformClipboard::onSelectionChanged()
{
m_timerResetSelection.stop();
if ( waitIfSelectionIncomplete() )
return;
// Always assume that only plain text can be in primary selection buffer.
// Asking a app for bigger data when mouse selection changes can make the app hang for a moment.
const QVariantMap data = DummyClipboard::data( Selection, QStringList(mimeText) );
const bool foreignData = !ownsClipboardData(data);
if ( foreignData && maybeResetSelection() )
return;
if (m_selectionData == data)
return;
m_selectionData = data;
emit changed(Selection);
// Check clipboard too if some signals where not delivered.
m_timerCheckClipboard.start();
}
void X11PlatformClipboard::resetClipboard()
{
if (!m_clipboardData.isEmpty()) {
COPYQ_LOG("Resetting clipboard");
DummyClipboard::setData(Clipboard, m_clipboardData);
}
}
void X11PlatformClipboard::resetSelection()
{
if (!m_selectionData.isEmpty()) {
COPYQ_LOG("Resetting selection");
DummyClipboard::setData(Selection, m_selectionData);
}
}
bool X11PlatformClipboard::waitIfSelectionIncomplete()
{
if (!d->display())
return true;
if ( isSelectionIncomplete(d->display()) ) {
m_timerCheckSelection.start();
return true;
}
return false;
}
bool X11PlatformClipboard::maybeResetClipboard()
{
if (!d->display())
return false;
if ( m_clipboardData.isEmpty() || !isClipboardEmpty(d->display()) )
return false;
COPYQ_LOG("Clipboard is empty");
m_timerResetClipboard.start();
return true;
}
bool X11PlatformClipboard::maybeResetSelection()
{
if (!d->display())
return false;
if ( m_selectionData.isEmpty() || !isSelectionEmpty(d->display()) )
return false;
COPYQ_LOG("Selection is empty");
m_timerResetSelection.start();
return true;
}