1 /*
2 Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
3
4 This file is part of CopyQ.
5
6 CopyQ is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 CopyQ is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "x11platform.h"
21
22 #include "app/applicationexceptionhandler.h"
23 #include "common/log.h"
24 #include "common/textdata.h"
25
26 #include <QApplication>
27 #include <QCoreApplication>
28 #include <QDir>
29 #include <QRegularExpression>
30 #include <QStringList>
31 #include <QVariant>
32 #include <QWidget>
33 #include <QX11Info>
34
35 #include "x11platformwindow.h"
36 #include "x11platformclipboard.h"
37
38 #include <X11/Xatom.h>
39
40 #include <memory>
41
42 namespace {
43
44 int (*old_xio_errhandler)(Display *) = nullptr;
45
46 const char *defaultDesktopFileContent =
47 R"([Desktop Entry]
48 Name=CopyQ
49 Icon=copyq
50 GenericName=Clipboard Manager
51 Type=Application
52 Terminal=false
53 X-KDE-autostart-after=panel
54 X-KDE-StartupNotify=false
55 X-KDE-UniqueApplet=true
56 )";
57
58 // Try to handle X11 fatal error gracefully.
copyq_xio_errhandler(Display * display)59 int copyq_xio_errhandler(Display *display)
60 {
61 // Try to call MainWindow::saveTabs().
62 if ( QCoreApplication::instance() ) {
63 for ( auto obj : qApp->topLevelWidgets() ) {
64 if (obj->objectName() == "MainWindow") {
65 QMetaObject::invokeMethod(obj, "saveTabs");
66 break;
67 }
68 }
69 }
70
71 // Call the old handler (possibly for Qt).
72 if (old_xio_errhandler)
73 old_xio_errhandler(display);
74
75 // As documentation for XSetIOErrorHandler states, this function should not return.
76 exit(1);
77 }
78
79 #ifdef COPYQ_DESKTOP_FILE
getDesktopFilename()80 QString getDesktopFilename()
81 {
82 const char *path = getenv("XDG_CONFIG_HOME");
83 QString filename = path ? getTextData(path) : QDir::homePath() + "/.config";
84 filename.append("/autostart/" + QCoreApplication::applicationName() + ".desktop");
85 return filename;
86 }
87 #endif
88
printFileError(const QFile & file,const char * message,LogLevel logLevel=LogError)89 void printFileError(const QFile &file, const char *message, LogLevel logLevel = LogError)
90 {
91 log( QString("%1 \"%2\": %3")
92 .arg( QString::fromLatin1(message), file.fileName(), file.errorString()),
93 logLevel );
94 }
95
maybePrintFileError(const QFile & file,const char * message)96 void maybePrintFileError(const QFile &file, const char *message)
97 {
98 if (file.error() != QFile::NoError)
99 printFileError(file, message);
100 }
101
102 } // namespace
103
platformNativeInterface()104 PlatformNativeInterface *platformNativeInterface()
105 {
106 static X11Platform platform;
107 return &platform;
108 }
109
110 X11Platform::~X11Platform() = default;
111
getWindow(WId winId)112 PlatformWindowPtr X11Platform::getWindow(WId winId)
113 {
114 if (!QX11Info::isPlatformX11())
115 return PlatformWindowPtr();
116
117 std::unique_ptr<X11PlatformWindow> window(new X11PlatformWindow(winId));
118 return PlatformWindowPtr(window->isValid() ? window.release() : nullptr);
119 }
120
getCurrentWindow()121 PlatformWindowPtr X11Platform::getCurrentWindow()
122 {
123 if (!QX11Info::isPlatformX11())
124 return PlatformWindowPtr();
125
126 std::unique_ptr<X11PlatformWindow> window(new X11PlatformWindow());
127 return PlatformWindowPtr(window->isValid() ? window.release() : nullptr);
128 }
129
canAutostart()130 bool X11Platform::canAutostart()
131 {
132 #ifdef COPYQ_DESKTOP_FILE
133 return true;
134 #else
135 return false;
136 #endif
137 }
138
isAutostartEnabled()139 bool X11Platform::isAutostartEnabled()
140 {
141 #ifdef COPYQ_DESKTOP_FILE
142 const QString filename = getDesktopFilename();
143
144 QFile desktopFile(filename);
145
146 if ( !desktopFile.exists() )
147 return false;
148
149 if ( !desktopFile.open(QIODevice::ReadOnly | QIODevice::Text) )
150 return false;
151
152 const QRegularExpression re("^Hidden\\s*=\\s*([a-zA-Z01]+)");
153
154 while ( !desktopFile.atEnd() ) {
155 const QString line = getTextData(desktopFile.readLine());
156 const auto m = re.match(line);
157 if (m.hasMatch()) {
158 const QString value = m.captured(1);
159 return !(value.startsWith("True") || value.startsWith("true") || value.startsWith("0"));
160 }
161 }
162
163 return true;
164 #else
165 return false;
166 #endif
167 }
168
setAutostartEnabled(bool enable)169 void X11Platform::setAutostartEnabled(bool enable)
170 {
171 #ifdef COPYQ_DESKTOP_FILE
172 if ( isAutostartEnabled() == enable )
173 return;
174
175 const QString filename = getDesktopFilename();
176
177 const auto autostartPath = QDir::cleanPath(filename + "/..");
178 QDir autostartDir(autostartPath);
179 if ( !autostartDir.mkpath(".") ) {
180 log( QString("Failed to create autostart path \"%1\"").arg(autostartPath) );
181 return;
182 }
183
184 QFile desktopFile2(filename + ".new");
185 if ( !desktopFile2.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text) ) {
186 printFileError(desktopFile2, "Failed to create new desktop file");
187 return;
188 }
189
190 const QRegularExpression re("^(Hidden|X-GNOME-Autostart-enabled|Exec)\\s*=\\s*");
191
192 QFile desktopFile(filename);
193 bool createUserDesktopFile = !desktopFile.exists();
194 if (createUserDesktopFile)
195 desktopFile.setFileName(COPYQ_DESKTOP_FILE);
196
197 if ( desktopFile.open(QIODevice::ReadOnly | QIODevice::Text) ) {
198 while ( !desktopFile.atEnd() ) {
199 const QString line = getTextData(desktopFile.readLine());
200 if ( !line.contains(re) )
201 desktopFile2.write(line.toUtf8());
202 }
203 desktopFile.close();
204 maybePrintFileError(desktopFile, "Failed to read desktop file");
205 } else {
206 // Installed desktop file not found (can happen when running tests).
207 printFileError(desktopFile, "Failed to open desktop file", LogNote);
208 desktopFile2.write(defaultDesktopFileContent);
209 }
210
211 #ifdef COPYQ_AUTOSTART_COMMAND
212 QString cmd = COPYQ_AUTOSTART_COMMAND;
213 #else
214 QString cmd = "\"" + QApplication::applicationFilePath() + "\"";
215 #endif
216 const QString sessionName = qApp->property("CopyQ_session_name").toString();
217 if ( !sessionName.isEmpty() )
218 cmd.append(" -s " + sessionName);
219 desktopFile2.write("Exec=" + cmd.toUtf8() + "\n");
220
221 desktopFile2.write("Hidden=");
222 desktopFile2.write(enable ? "false" : "true");
223 desktopFile2.write("\n");
224
225 desktopFile2.write("X-GNOME-Autostart-enabled=");
226 desktopFile2.write(enable ? "true" : "false");
227 desktopFile2.write("\n");
228
229 QFile::remove(filename);
230 desktopFile2.rename(filename);
231
232 maybePrintFileError(desktopFile2, "Failed to write desktop file");
233 #else
234 Q_UNUSED(enable)
235 #endif
236 }
237
createConsoleApplication(int & argc,char ** argv)238 QCoreApplication *X11Platform::createConsoleApplication(int &argc, char **argv)
239 {
240 return new ApplicationExceptionHandler<QCoreApplication>(argc, argv);
241 }
242
createServerApplication(int & argc,char ** argv)243 QApplication *X11Platform::createServerApplication(int &argc, char **argv)
244 {
245 if (QX11Info::isPlatformX11())
246 old_xio_errhandler = XSetIOErrorHandler(copyq_xio_errhandler);
247 return new ApplicationExceptionHandler<QApplication>(argc, argv);
248 }
249
createMonitorApplication(int & argc,char ** argv)250 QGuiApplication *X11Platform::createMonitorApplication(int &argc, char **argv)
251 {
252 return new ApplicationExceptionHandler<QGuiApplication>(argc, argv);
253 }
254
createClipboardProviderApplication(int & argc,char ** argv)255 QGuiApplication *X11Platform::createClipboardProviderApplication(int &argc, char **argv)
256 {
257 return new ApplicationExceptionHandler<QGuiApplication>(argc, argv);
258 }
259
createClientApplication(int & argc,char ** argv)260 QCoreApplication *X11Platform::createClientApplication(int &argc, char **argv)
261 {
262 return new ApplicationExceptionHandler<QCoreApplication>(argc, argv);
263 }
264
createTestApplication(int & argc,char ** argv)265 QGuiApplication *X11Platform::createTestApplication(int &argc, char **argv)
266 {
267 return new ApplicationExceptionHandler<QGuiApplication>(argc, argv);
268 }
269
clipboard()270 PlatformClipboardPtr X11Platform::clipboard()
271 {
272 return PlatformClipboardPtr(new X11PlatformClipboard());
273 }
274
getCommandLineArguments(int argc,char ** argv)275 QStringList X11Platform::getCommandLineArguments(int argc, char **argv)
276 {
277 if (argc == 0)
278 return QStringList();
279
280 QStringList arguments;
281 arguments.reserve(argc - 1);
282
283 for (int i = 1; i < argc; ++i)
284 arguments.append( QString::fromUtf8(argv[i]) );
285
286 return arguments;
287 }
288
findPluginDir(QDir * pluginsDir)289 bool X11Platform::findPluginDir(QDir *pluginsDir)
290 {
291 pluginsDir->setPath( qApp->applicationDirPath() );
292
293 if ( pluginsDir->dirName() == QLatin1String("bin")
294 && pluginsDir->cdUp()
295 && (pluginsDir->cd(QLatin1String("lib64")) || pluginsDir->cd(QLatin1String("lib")))
296 && pluginsDir->cd(QLatin1String("copyq")) )
297 {
298 // OK, installed in /usr/local/bin or /usr/bin.
299 return true;
300 }
301
302 pluginsDir->setPath( qApp->applicationDirPath() );
303
304 if ( pluginsDir->cd(QLatin1String("plugins")) ) {
305 // OK, plugins in same directory as executable.
306 pluginsDir->cd(QLatin1String("copyq"));
307 return true;
308 }
309
310 return false;
311 }
312
defaultEditorCommand()313 QString X11Platform::defaultEditorCommand()
314 {
315 return QLatin1String("gedit --standalone -- %1");
316 }
317
translationPrefix()318 QString X11Platform::translationPrefix()
319 {
320 return QString();
321 }
322
sendDummyX11Event()323 void sendDummyX11Event()
324 {
325 if (!QX11Info::isPlatformX11())
326 return;
327
328 auto display = QX11Info::display();
329 auto black = BlackPixel(display, 0);
330 Window window = XCreateSimpleWindow(
331 display, RootWindow(display, 0), -100000, -100000, 1, 1, 0, black, black);
332 XDestroyWindow(display, window);
333 XFlush(display);
334 }
335