Skip to content

Commit

Permalink
Get clipboard owner from script
Browse files Browse the repository at this point in the history
Adds `currentClipboardOwner()` function to scripting which returns name
of the current clipboard owner. This by default returns
`currentWindowTitle()`.

The value is used to set `mimeWindowTitle` format for the clipboard data
in automatic commands and filtering by window title.
  • Loading branch information
hluk committed Dec 10, 2023
1 parent 8cf4dab commit bcc646c
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 75 deletions.
12 changes: 12 additions & 0 deletions docs/scripting-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,18 @@ unlike in GUI, where row numbers start from 1 by default.
:returns: Current window title.
:rtype: string

.. js:function:: String currentClipboardOwner()

Returns name of the current clipboard owner.

The default implementation returns `currentWindowTitle()`.

This is used to set `mimeWindowTitle` format for the clipboard data in
automatic commands and filtering by window title.

:returns: Current clipboard owner name.
:rtype: string

.. js:function:: dialog(...)

Shows messages or asks user for input.
Expand Down
39 changes: 32 additions & 7 deletions src/app/clipboardmonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "platform/platformclipboard.h"

#include <QApplication>
#include <QClipboard>

namespace {

Expand Down Expand Up @@ -49,6 +50,7 @@ bool isClipboardDataHidden(const QVariantMap &data)
ClipboardMonitor::ClipboardMonitor(const QStringList &formats)
: m_clipboard(platformNativeInterface()->clipboard())
, m_formats(formats)
, m_ownerMonitor(this)
{
const AppConfig config;
m_storeClipboard = config.option<Config::check_clipboard>();
Expand Down Expand Up @@ -76,9 +78,33 @@ ClipboardMonitor::ClipboardMonitor(const QStringList &formats)

void ClipboardMonitor::startMonitoring()
{
setClipboardOwner(currentClipboardOwner());
COPYQ_LOG(
QStringLiteral("Initial clipboard owner: %1")
.arg(m_clipboardOwner));

connect(QGuiApplication::clipboard(), &QClipboard::changed,
this, [this](){ m_ownerMonitor.update(); });

m_clipboard->startMonitoring(m_formats);
}

QString ClipboardMonitor::currentClipboardOwner()
{
QString owner;
emit fetchCurrentClipboardOwner(&owner);
return owner;
}

void ClipboardMonitor::setClipboardOwner(const QString &owner)
{
if (m_clipboardOwner != owner) {
m_clipboardOwner = owner;
m_clipboard->setClipboardOwner(m_clipboardOwner);
COPYQ_LOG(QStringLiteral("Clipboard owner: %1").arg(owner));
}
}

void ClipboardMonitor::onClipboardChanged(ClipboardMode mode)
{
QVariantMap data = m_clipboard->data(mode, m_formats);
Expand All @@ -98,6 +124,12 @@ void ClipboardMonitor::onClipboardChanged(ClipboardMode mode)

*clipboardData = data;

// add window title of clipboard owner
if ( !data.contains(mimeOwner) && !data.contains(mimeWindowTitle) ) {
if ( !m_clipboardOwner.isEmpty() )
data.insert(mimeWindowTitle, m_clipboardOwner.toUtf8());
}

COPYQ_LOG( QString("%1 changed, owner is \"%2\"")
.arg(mode == ClipboardMode::Clipboard ? "Clipboard" : "Selection",
getTextData(data, mimeOwner)) );
Expand Down Expand Up @@ -131,13 +163,6 @@ void ClipboardMonitor::onClipboardChanged(ClipboardMode mode)
data.insert(mimeClipboardMode, modeName);
}

// add window title of clipboard owner
if ( !data.contains(mimeOwner) && !data.contains(mimeWindowTitle) ) {
const QByteArray windowTitle = m_clipboard->clipboardOwner();
if ( !windowTitle.isEmpty() )
data.insert(mimeWindowTitle, windowTitle);
}

// run automatic commands
if ( anySessionOwnsClipboardData(data) ) {
emit clipboardChanged(data, ClipboardOwnership::Own);
Expand Down
8 changes: 8 additions & 0 deletions src/app/clipboardmonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#ifndef CLIPBOARDMONITOR_H
#define CLIPBOARDMONITOR_H

#include "app/clipboardownermonitor.h"
#include "common/common.h"
#include "platform/platformnativeinterface.h"
#include "platform/platformclipboard.h"
Expand All @@ -22,11 +23,14 @@ class ClipboardMonitor final : public QObject
public:
explicit ClipboardMonitor(const QStringList &formats);
void startMonitoring();
QString currentClipboardOwner();
void setClipboardOwner(const QString &owner);

signals:
void clipboardChanged(const QVariantMap &data, ClipboardOwnership ownership);
void clipboardUnchanged(const QVariantMap &data);
void synchronizeSelection(ClipboardMode sourceMode, uint sourceTextHash, uint targetTextHash);
void fetchCurrentClipboardOwner(QString *title);

private:
void onClipboardChanged(ClipboardMode mode);
Expand All @@ -40,12 +44,16 @@ class ClipboardMonitor final : public QObject
QString m_clipboardTab;
bool m_storeClipboard;

ClipboardOwnerMonitor m_ownerMonitor;

#ifdef HAS_MOUSE_SELECTIONS
bool m_storeSelection;
bool m_runSelection;
bool m_clipboardToSelection;
bool m_selectionToClipboard;
#endif

QString m_clipboardOwner;
};

#endif // CLIPBOARDMONITOR_H
62 changes: 47 additions & 15 deletions src/app/clipboardownermonitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,49 @@

#include "clipboardownermonitor.h"

#include "app/clipboardmonitor.h"
#include "common/appconfig.h"
#include "platform/platformwindow.h"
#include "common/log.h"

#include <QCoreApplication>

ClipboardOwnerMonitor::ClipboardOwnerMonitor()
constexpr int updateAfterEventIntervalMs = 20;

ClipboardOwnerMonitor::ClipboardOwnerMonitor(ClipboardMonitor *monitor)
: m_monitor(monitor)
{
qApp->installNativeEventFilter(this);

m_timer.setSingleShot(true);
const int delay = AppConfig().option<Config::change_clipboard_owner_delay_ms>();
m_timer.setInterval(delay);
QObject::connect( &m_timer, &QTimer::timeout, [this]() {
m_clipboardOwner = m_newClipboardOwner;

PlatformWindowPtr currentWindow = platformNativeInterface()->getCurrentWindow();
if (currentWindow) {
const auto currentWindowTitle = currentWindow->getTitle().toUtf8();
if (m_newClipboardOwner != currentWindowTitle) {
m_newClipboardOwner = currentWindowTitle;
m_timer.start();
m_timerSetOwner.setSingleShot(true);
m_timerSetOwner.setInterval(delay);

m_timerUpdateAfterEvent.setSingleShot(true);
m_timerUpdateAfterEvent.setInterval(updateAfterEventIntervalMs);

QObject::connect(
&m_timerSetOwner, &QTimer::timeout,
[this]() {
if (!m_nextClipboardOwners.isEmpty()) {
m_monitor->setClipboardOwner(m_nextClipboardOwners.takeFirst());
if (!m_nextClipboardOwners.isEmpty())
m_timerSetOwner.start();
}
});

QObject::connect( &m_timerUpdateAfterEvent, &QTimer::timeout, [this]() {
const QString title = m_monitor->currentClipboardOwner();
if (m_lastClipboardOwner != title) {
m_lastClipboardOwner = title;
if ( m_timerSetOwner.interval() == 0 )
m_nextClipboardOwners = QStringList{m_lastClipboardOwner};
else
m_nextClipboardOwners.append(m_lastClipboardOwner);

if (!m_timerSetOwner.isActive())
m_timerSetOwner.start();

COPYQ_LOG(QStringLiteral("Next clipboard owner: %1").arg(title));
}
});
}
Expand All @@ -33,10 +54,21 @@ ClipboardOwnerMonitor::~ClipboardOwnerMonitor()
qApp->removeNativeEventFilter(this);
}

void ClipboardOwnerMonitor::update()
{
if ( m_timerSetOwner.interval() == 0 ) {
m_lastClipboardOwner = m_monitor->currentClipboardOwner();
m_nextClipboardOwners.clear();
m_monitor->setClipboardOwner(m_lastClipboardOwner);
} else if ( !m_timerUpdateAfterEvent.isActive() ) {
m_timerUpdateAfterEvent.start();
}
}

bool ClipboardOwnerMonitor::nativeEventFilter(const QByteArray &, void *, NativeEventResult *)
{
if ( !m_timer.isActive() )
m_timer.start();
if ( !m_timerUpdateAfterEvent.isActive() )
m_timerUpdateAfterEvent.start();

return false;
}
17 changes: 10 additions & 7 deletions src/app/clipboardownermonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <QByteArray>
#include <QTimer>

#include "platform/platformnativeinterface.h"
class ClipboardMonitor;

#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
using NativeEventResult = qintptr;
Expand All @@ -18,18 +18,21 @@ using NativeEventResult = long;
class ClipboardOwnerMonitor final : public QAbstractNativeEventFilter
{
public:
ClipboardOwnerMonitor();
explicit ClipboardOwnerMonitor(ClipboardMonitor *monitor);
~ClipboardOwnerMonitor();

const QByteArray &clipboardOwner() const { return m_clipboardOwner; }

bool nativeEventFilter(
const QByteArray &, void *message, NativeEventResult *result) override;

void update();
void updateNow();

private:
QByteArray m_clipboardOwner;
QByteArray m_newClipboardOwner;
QTimer m_timer;
ClipboardMonitor *m_monitor;
QString m_lastClipboardOwner;
QStringList m_nextClipboardOwners;
QTimer m_timerSetOwner;
QTimer m_timerUpdateAfterEvent;
};

#endif // CLIPBOARDOWNERMONITOR_H
7 changes: 2 additions & 5 deletions src/platform/dummy/dummyclipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#ifndef DUMMYCLIPBOARD_H
#define DUMMYCLIPBOARD_H

#include "app/clipboardownermonitor.h"
#include "common/clipboardmode.h"
#include "platform/platformclipboard.h"

Expand All @@ -22,20 +21,18 @@ class DummyClipboard : public PlatformClipboard

void setData(ClipboardMode mode, const QVariantMap &dataMap) override;

QByteArray clipboardOwner() override { return m_ownerMonitor.clipboardOwner(); }

const QMimeData *mimeData(ClipboardMode mode) const override;

bool isSelectionSupported() const override { return false; }

bool isHidden(const QMimeData &data) const override;

void setClipboardOwner(const QString &) override {}

protected:
virtual const QMimeData *rawMimeData(ClipboardMode mode) const;
virtual void onChanged(int mode);
void onClipboardChanged(QClipboard::Mode mode);

ClipboardOwnerMonitor m_ownerMonitor;
};

#endif // DUMMYCLIPBOARD_H
2 changes: 0 additions & 2 deletions src/platform/mac/macclipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ class MacClipboard final : public DummyClipboard {

void setData(ClipboardMode mode, const QVariantMap &dataMap) override;

QByteArray clipboardOwner() override;

bool isHidden(const QMimeData &data) const override;

protected:
Expand Down
8 changes: 0 additions & 8 deletions src/platform/mac/macclipboard.mm
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@
return DummyClipboard::setData(mode, dataMapForMac);
}

QByteArray MacClipboard::clipboardOwner()
{
PlatformWindowPtr currentWindow = platformNativeInterface()->getCurrentWindow();
if (currentWindow)
return currentWindow->getTitle().toUtf8();
return QByteArray();
}

bool MacClipboard::isHidden(const QMimeData &data) const
{
return data.hasFormat( QStringLiteral("application/x-nspasteboard-concealed-type") );
Expand Down
4 changes: 2 additions & 2 deletions src/platform/platformclipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ class PlatformClipboard : public QObject
*/
virtual void setData(ClipboardMode mode, const QVariantMap &dataMap) = 0;

virtual QByteArray clipboardOwner() = 0;

virtual const QMimeData *mimeData(ClipboardMode mode) const = 0;

virtual bool isSelectionSupported() const = 0;

virtual bool isHidden(const QMimeData &data) const = 0;

virtual void setClipboardOwner(const QString &owner) = 0;

signals:
/// Notifies about clipboard changes.
void changed(ClipboardMode mode);
Expand Down
11 changes: 6 additions & 5 deletions src/platform/x11/x11platformclipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

#include "systemclipboard/waylandclipboard.h"

#include <QDataStream>

#ifdef COPYQ_WITH_X11
# include <X11/Xlib.h>
# include <X11/Xatom.h>
Expand Down Expand Up @@ -136,7 +138,7 @@ QVariantMap X11PlatformClipboard::data(ClipboardMode mode, const QStringList &fo

auto data = clipboardData.data;
if ( !data.contains(mimeOwner) )
data[mimeWindowTitle] = clipboardData.owner;
data[mimeWindowTitle] = clipboardData.owner.toUtf8();
return data;
}

Expand Down Expand Up @@ -174,14 +176,13 @@ void X11PlatformClipboard::onChanged(int mode)
// Store the current window title right after the clipboard/selection changes.
// This makes sure that the title points to the correct clipboard/selection
// owner most of the times.
const auto currentWindowTitle = clipboardOwner();
if (currentWindowTitle != clipboardData.newOwner) {
if (m_clipboardOwner != clipboardData.newOwner) {
COPYQ_LOG( QString("New %1 owner: \"%2\"")
.arg(
QString::fromLatin1(mode == QClipboard::Clipboard ? "clipboard" : "selection"),
QString::fromUtf8(currentWindowTitle)
m_clipboardOwner
) );
clipboardData.newOwner = currentWindowTitle;
clipboardData.newOwner = m_clipboardOwner;
}

if (mode == QClipboard::Selection) {
Expand Down
8 changes: 6 additions & 2 deletions src/platform/x11/x11platformclipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class X11PlatformClipboard final : public DummyClipboard

bool isSelectionSupported() const override { return m_selectionSupported; }

void setClipboardOwner(const QString &owner) override { m_clipboardOwner = owner; }

protected:
const QMimeData *rawMimeData(ClipboardMode mode) const override;
void onChanged(int mode) override;
Expand All @@ -34,8 +36,8 @@ class X11PlatformClipboard final : public DummyClipboard
struct ClipboardData {
QVariantMap newData;
QVariantMap data;
QByteArray owner;
QByteArray newOwner;
QString owner;
QString newOwner;
QTimer timerEmitChange;
QStringList formats;
quint32 newDataTimestamp;
Expand All @@ -59,6 +61,8 @@ class X11PlatformClipboard final : public DummyClipboard

bool m_monitoring = false;
bool m_selectionSupported = true;

QString m_clipboardOwner;
};

#endif // X11PLATFORMCLIPBOARD_H
Loading

0 comments on commit bcc646c

Please sign in to comment.