Merge lp:~townsend/libertine/pasted into lp:libertine/trunk

Proposed by Christopher Townsend on 2016-08-10
Status: Merged
Approved by: Larry Price on 2016-08-29
Approved revision: 162
Merged at revision: 156
Proposed branch: lp:~townsend/libertine/pasted
Merge into: lp:libertine/trunk
Diff against target: 437 lines (+326/-7)
9 files modified
CMakeLists.txt (+10/-7)
debian/changelog (+7/-0)
debian/control (+3/-0)
debian/libertine-tools.install (+1/-0)
pasted/CMakeLists.txt (+10/-0)
pasted/pasted.cpp (+214/-0)
pasted/pasted.h (+68/-0)
python/libertine/Libertine.py (+11/-0)
tools/libertine-launch (+2/-0)
To merge this branch: bzr merge lp:~townsend/libertine/pasted
Reviewer Review Type Date Requested Status
Larry Price 2016-08-10 Approve on 2016-08-16
Review via email: mp+302542@code.launchpad.net

Commit message

Add a background process that allows copying & pasting between an X app, another X app, and native Unity 8 apps.

Description of the change

This branch will merge directly in lp:libertine/trunk first since it depends on the landing of other packages as well.

Install silo 37 (https://launchpad.net/~ci-train-ppa-service/+archive/ubuntu/landing-037/+packages).

To post a comment you must log in.
lp:~townsend/libertine/pasted updated on 2016-08-10
152. By Christopher Townsend on 2016-08-10

Update version and debian/changelog.

153. By Christopher Townsend on 2016-08-10

Fix debian/changelog formatting from last commit.

154. By Christopher Townsend on 2016-08-10

Forgot to add the libertine-launch bits.

Larry Price (larryprice) wrote :

A few inlines.

review: Needs Information
Christopher Townsend (townsend) wrote :

Thanks! Answered your comments from your thorough review:)

I'll work on the fixes and push the changes soon.

lp:~townsend/libertine/pasted updated on 2016-08-16
155. By Christopher Townsend on 2016-08-16

Changes based on last review.

Larry Price (larryprice) wrote :

Just a couple more inlines.

review: Needs Information
Larry Price (larryprice) wrote :

updated qtimer comment - found the docs explicitly saying the parent cleans it up

lp:~townsend/libertine/pasted updated on 2016-08-16
156. By Christopher Townsend on 2016-08-16

More changes per the last review.

Larry Price (larryprice) wrote :

Code looks good - I haven't tested it out yet what with the dependency business on yakkety, but I assume we still have some time.

review: Approve
lp:~townsend/libertine/pasted updated on 2016-09-01
157. By Christopher Townsend on 2016-08-17

Use nullptr comparison.

158. By Christopher Townsend on 2016-08-19

Merge lp:libertine/trunk.

159. By Christopher Townsend on 2016-08-19

Fix debian/control merge in last commit.

160. By Christopher Townsend on 2016-08-24

Use the new persistentSurfaceId API in content-hub.

161. By Christopher Townsend on 2016-08-26

Check if the focus window is PointerRoot instead of any old window.

162. By Christopher Townsend on 2016-08-29

Workaround a bug in Xmir where input focus tracking gets messed up depending on when an application's focus is switched during start up.

163. By Christopher Townsend on 2016-09-01

Add workaround by filtering 2 problematic Mime types that cause The Gimp to crash when copying from The Gimp.

164. By Christopher Townsend on 2016-09-01

Dump QStringLiteral and just do a straight up comparison.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-08-12 17:13:45 +0000
3+++ CMakeLists.txt 2016-09-01 20:02:28 +0000
4@@ -2,8 +2,7 @@
5 cmake_policy(SET CMP0048 NEW)
6
7 project(libertine
8- VERSION 1.3.1
9- LANGUAGES CXX)
10+ VERSION 1.4)
11
12 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
13
14@@ -17,11 +16,14 @@
15
16 include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR})
17
18-find_package(Qt5Core REQUIRED)
19-find_package(Qt5Gui REQUIRED)
20-find_package(Qt5Quick REQUIRED)
21-find_package(Gettext REQUIRED)
22-find_package(Intltool REQUIRED)
23+find_package(Qt5Core REQUIRED)
24+find_package(Qt5Gui REQUIRED)
25+find_package(Qt5Quick REQUIRED)
26+find_package(Qt5Widgets REQUIRED)
27+find_package(Qt5X11Extras REQUIRED)
28+find_package(Gettext REQUIRED)
29+find_package(Intltool REQUIRED)
30+find_package(X11 REQUIRED)
31
32 pkg_check_modules(GLIB2 REQUIRED glib-2.0)
33 include_directories(${GLIB2_INCLUDE_DIRS})
34@@ -37,6 +39,7 @@
35 add_subdirectory(tools)
36 add_subdirectory(liblibertine)
37 add_subdirectory(po)
38+add_subdirectory(pasted)
39
40 include(CTest)
41 add_subdirectory(tests)
42
43=== modified file 'debian/changelog'
44--- debian/changelog 2016-08-12 18:58:42 +0000
45+++ debian/changelog 2016-09-01 20:02:28 +0000
46@@ -1,3 +1,10 @@
47+libertine (1.4-0ubuntu1) UNRELEASED; urgency=medium
48+
49+ * Add a background process that allows copying & pasting between an X app,
50+ another X app, and native Unity 8 apps. (LP: #1471998)
51+
52+ -- Chris Townsend <christopher.townsend@canonical.com> Wed, 19 Aug 2016 08:31:21 -0400
53+
54 libertine (1.3.1+16.10.20160812.3-0ubuntu1) yakkety; urgency=medium
55
56 [ Brandon Schaefer ]
57
58=== modified file 'debian/control'
59--- debian/control 2016-08-12 18:55:11 +0000
60+++ debian/control 2016-09-01 20:02:28 +0000
61@@ -9,9 +9,12 @@
62 dh-translations,
63 gobject-introspection,
64 intltool,
65+ libcontent-hub-dev (>= 0.2),
66 libgirepository1.0-dev,
67 libglib2.0-dev,
68 libgtest-dev,
69+ libqt5x11extras5-dev,
70+ libx11-dev,
71 lsb-release,
72 python3-apt,
73 python3-dev,
74
75=== modified file 'debian/libertine-tools.install'
76--- debian/libertine-tools.install 2016-08-12 16:11:57 +0000
77+++ debian/libertine-tools.install 2016-09-01 20:02:28 +0000
78@@ -2,6 +2,7 @@
79 usr/bin/libertine-session-bridge
80 usr/bin/libertine-container-manager
81 usr/bin/libertine-xmir
82+usr/bin/pasted
83 usr/share/bash-completion/completions/libertine-container-manager
84 usr/share/man
85 usr/share/upstart/sessions/libertine-xmir.conf
86
87=== added directory 'pasted'
88=== added file 'pasted/CMakeLists.txt'
89--- pasted/CMakeLists.txt 1970-01-01 00:00:00 +0000
90+++ pasted/CMakeLists.txt 2016-09-01 20:02:28 +0000
91@@ -0,0 +1,10 @@
92+set(pasted_SRC
93+ pasted.cpp
94+)
95+
96+add_executable(pasted ${pasted_SRC})
97+
98+qt5_use_modules(pasted DBus)
99+
100+target_link_libraries(pasted Qt5::Core Qt5::Gui Qt5::Widgets Qt5::X11Extras content-hub X11)
101+install(TARGETS pasted RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
102
103=== added file 'pasted/pasted.cpp'
104--- pasted/pasted.cpp 1970-01-01 00:00:00 +0000
105+++ pasted/pasted.cpp 2016-09-01 20:02:28 +0000
106@@ -0,0 +1,214 @@
107+/*
108+ * @file pasted.cpp
109+ * @brief Copy & Paste daemon between X apps, other X apps, and native apps
110+ */
111+/*
112+ * Copyright 2016 Canonical Ltd
113+ *
114+ * Libertine is free software: you can redistribute it and/or modify it under
115+ * the terms of the GNU General Public License, version 3, as published by the
116+ * Free Software Foundation.
117+ *
118+ * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY
119+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
120+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
121+ *
122+ * You should have received a copy of the GNU General Public License
123+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
124+ */
125+
126+#include "pasted.h"
127+
128+#include <QDebug>
129+
130+#include <QX11Info>
131+#include <X11/Xatom.h>
132+
133+
134+Pasted::
135+Pasted(int argc, char** argv)
136+: QApplication(argc, argv)
137+, clipboard_(QApplication::clipboard())
138+, content_hub_(cuc::Hub::Client::instance())
139+, mimeDataX_(new QMimeData)
140+, lastMimeData_()
141+, rootWindowHasFocus_(false)
142+, firstSeenWindow_(None)
143+{
144+ setApplicationName("pasted");
145+
146+ QTimer *timer = new QTimer(this);
147+ connect(timer, &QTimer::timeout, this, &Pasted::checkForAppFocus);
148+ timer->start(400);
149+
150+ connect(clipboard_, &QClipboard::dataChanged, this, &Pasted::handleXClipboard);
151+}
152+
153+
154+void Pasted::
155+handleXClipboard()
156+{
157+ qDebug() << "Change in X clipboard";
158+
159+ const QMimeData *xClipboard = clipboard_->mimeData();
160+
161+ if ((xClipboard == nullptr) || xClipboard->formats().empty())
162+ {
163+ qDebug() << "Empty xClipboard. Not setting pasteboard.";
164+ }
165+ else if (!compareMimeData(xClipboard, lastMimeData_.get()))
166+ {
167+ qDebug() << "X clipboard data is different";
168+ updateLastMimeData(xClipboard);
169+
170+ content_hub_->createPasteSync(persistentSurfaceId_, *lastMimeData_);
171+ }
172+}
173+
174+
175+void Pasted::
176+handleContentHubPasteboard()
177+{
178+ const QMimeData *pasteboard = content_hub_->latestPaste(persistentSurfaceId_);
179+
180+ if (!compareMimeData(pasteboard, lastMimeData_.get()))
181+ {
182+ qDebug() << "content-hub pasteboard data is different";
183+ updateXMimeData(pasteboard);
184+
185+ clipboard_->setMimeData(mimeDataX_);
186+ }
187+}
188+
189+
190+void Pasted::
191+checkForAppFocus()
192+{
193+ Window w;
194+ int revert_to; // unused
195+
196+ XGetInputFocus(QX11Info::display(), &w, &revert_to);
197+
198+ if (firstSeenWindow_ == None && w != None)
199+ {
200+ firstSeenWindow_ = w;
201+ }
202+
203+ if (w == None && rootWindowHasFocus_ == true)
204+ {
205+ qDebug() << "Xmir lost focus";
206+ rootWindowHasFocus_ = false;
207+ }
208+ else if ((w == PointerRoot ||
209+ (w && firstSeenWindow_ == PointerRoot))
210+ && rootWindowHasFocus_ == false)
211+ {
212+ qDebug() << "Xmir gained focus";
213+ rootWindowHasFocus_ = true;
214+ setPersistentSurfaceId();
215+ handleContentHubPasteboard();
216+ }
217+}
218+
219+
220+bool Pasted::
221+compareMimeData(const QMimeData *a, const QMimeData *b)
222+{
223+ if ((a == nullptr) || (b == nullptr))
224+ {
225+ return false;
226+ }
227+
228+ if (a->formats() != b->formats())
229+ {
230+ return false;
231+ }
232+
233+ for (const QString& formatName: a->formats())
234+ {
235+ if (a->data(formatName) != b->data(formatName))
236+ {
237+ return false;
238+ }
239+ }
240+
241+ return true;
242+}
243+
244+
245+void Pasted::
246+copyMimeData(QMimeData& target, const QMimeData *source)
247+{
248+ if (source == nullptr)
249+ {
250+ qDebug() << "Copy source is null!";
251+ return;
252+ }
253+
254+ for (const QString& format : source->formats())
255+ {
256+ // Need to filter out these Mime types due to The Gimp crashing when copying these types
257+ if (format != "image/x-MS-bmp" && format != "image/x-bmp")
258+ {
259+ target.setData(format, source->data(format));
260+ }
261+ }
262+}
263+
264+
265+void Pasted::
266+updateLastMimeData(const QMimeData *source)
267+{
268+ lastMimeData_.reset(new QMimeData);
269+
270+ copyMimeData(*lastMimeData_.get(), source);
271+}
272+
273+
274+void Pasted::
275+updateXMimeData(const QMimeData *source)
276+{
277+ updateLastMimeData(source);
278+
279+ mimeDataX_ = new QMimeData;
280+ copyMimeData(*mimeDataX_, source);
281+}
282+
283+
284+void Pasted::
285+setPersistentSurfaceId()
286+{
287+ if (persistentSurfaceId_.isEmpty())
288+ {
289+ Atom prop = XInternAtom(QX11Info::display(), "_MIR_WM_PERSISTENT_ID", 0),
290+ type; // unused
291+ int form, // unused
292+ status;
293+ unsigned long remain, // unused
294+ len; // unused
295+ unsigned char *data;
296+ status = XGetWindowProperty(QX11Info::display(), XDefaultRootWindow(QX11Info::display()), prop,
297+ 0, 1024, 0, XA_STRING, &type, &form, &len, &remain, &data);
298+
299+ if (status)
300+ {
301+ qDebug() << "Failure retrieving the persistentSurfaceID!";
302+ }
303+ else
304+ {
305+ qDebug() << "Setting persistentSurfaceId";
306+ persistentSurfaceId_ = (const char *)data;
307+ }
308+ }
309+}
310+
311+
312+int
313+main(int argc, char* argv[])
314+{
315+ qSetMessagePattern(QString("%{appname}: %{message}"));
316+
317+ Pasted pasted(argc, argv);
318+
319+ pasted.exec();
320+}
321
322=== added file 'pasted/pasted.h'
323--- pasted/pasted.h 1970-01-01 00:00:00 +0000
324+++ pasted/pasted.h 2016-09-01 20:02:28 +0000
325@@ -0,0 +1,68 @@
326+/**
327+ * @file pasted.h
328+ * @brief Copy & Paste daemon between X apps, other X apps, and native apps
329+ */
330+/*
331+ * Copyright 2016 Canonical Ltd
332+ *
333+ * Libertine is free software: you can redistribute it and/or modify it under
334+ * the terms of the GNU General Public License, version 3, as published by the
335+ * Free Software Foundation.
336+ *
337+ * Libertine is distributed in the hope that it will be useful, but WITHOUT ANY
338+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
339+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
340+ *
341+ * You should have received a copy of the GNU General Public License
342+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
343+ */
344+
345+#ifndef _PASTED_H_
346+#define _PASTED_H_
347+
348+#include <memory>
349+
350+#include <QApplication>
351+#include <QClipboard>
352+#include <QMimeData>
353+#include <QString>
354+
355+#include <com/ubuntu/content/hub.h>
356+#include <X11/Xlib.h>
357+
358+namespace cuc = com::ubuntu::content;
359+
360+
361+class Pasted
362+: public QApplication
363+{
364+ Q_OBJECT
365+
366+ public:
367+ Pasted(int argc, char** argv);
368+ virtual ~Pasted() = default;
369+
370+ private:
371+ void updateLastMimeData(const QMimeData *source);
372+ void updateXMimeData(const QMimeData *source);
373+ void handleContentHubPasteboard();
374+ void setPersistentSurfaceId();
375+
376+ static bool compareMimeData(const QMimeData *a, const QMimeData *b);
377+ static void copyMimeData(QMimeData& target, const QMimeData *source);
378+
379+ private slots:
380+ void handleXClipboard();
381+ void checkForAppFocus();
382+
383+ private:
384+ QClipboard *clipboard_;
385+ cuc::Hub *content_hub_;
386+ QMimeData *mimeDataX_;
387+ std::unique_ptr<QMimeData> lastMimeData_;
388+ bool rootWindowHasFocus_;
389+ Window firstSeenWindow_;
390+ QString persistentSurfaceId_;
391+};
392+
393+#endif /* _PASTED_H_ */
394
395=== modified file 'python/libertine/Libertine.py'
396--- python/libertine/Libertine.py 2016-07-11 19:54:31 +0000
397+++ python/libertine/Libertine.py 2016-09-01 20:02:28 +0000
398@@ -446,6 +446,7 @@
399 self.container_id = container_id
400 self.app_exec_line = app_exec_line
401 self.session_bridge = None
402+ self.pasted = None
403
404 """
405 Launches the libertine session bridge. This creates a proxy socket to read to and from
406@@ -464,6 +465,13 @@
407 self.session_bridge = psutil.Popen(args)
408
409 """
410+ Launches the pasted process for allowing copy and paste to work with X apps and
411+ content-hub.
412+ """
413+ def launch_pasted(self):
414+ self.pasted = psutil.Popen("pasted")
415+
416+ """
417 Launches the container from the id and attempts to run the application exec.
418 """
419 def launch_application(self):
420@@ -479,3 +487,6 @@
421 finally:
422 if self.session_bridge is not None:
423 self.session_bridge.terminate()
424+
425+ if self.pasted is not None:
426+ self.pasted.terminate()
427
428=== modified file 'tools/libertine-launch'
429--- tools/libertine-launch 2016-07-06 16:05:36 +0000
430+++ tools/libertine-launch 2016-09-01 20:02:28 +0000
431@@ -96,4 +96,6 @@
432 # should detect the maliit socket, but dont know if its around or not here.
433 detect_session_bridge_socket(dbus_socket_path)
434
435+ la.launch_pasted()
436+
437 la.launch_application()

Subscribers

People subscribed via source and target branches