1 /**************************************************************************
2 * Copyright (C) 2005-2020 by Oleksandr Shneyder *
3 * <o.shneyder@phoca-gmbh.de> *
4 * Copyright (C) 2016-2020 by Mihai Moldovan <ionic@ionic.de> *
5 * *
6 * This program 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 2 of the License, or *
9 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <https://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18
19 #include <vector>
20 #include <algorithm>
21 #include <QString>
22 #include <QDir>
23 #include <QMessageBox>
24 #include <QFont>
25 #include <QFontInfo>
26 #include <QObject>
27 #include <QStringList>
28
29 #include "x2goutils.h"
30 #include "onmainwindow.h"
31 #include "x2gologdebug.h"
32
expandHome(QString path)33 QString expandHome (QString path) {
34 path = path.trimmed ();
35 if (path.startsWith ("~/") || path.startsWith ("~\\")) {
36 path = path.replace (QString ("~"), QDir::homePath ());
37 }
38 return path;
39 }
40
fixup_resource_URIs(const QString & res_path)41 QString fixup_resource_URIs (const QString &res_path) {
42 QString ret (res_path);
43
44 if (!(res_path.isEmpty ())) {
45 if (ret.at (1) != '/') {
46 ret.insert (1, '/');
47 }
48 }
49
50 return (ret);
51 }
52
wrap_legacy_resource_URIs(const QString & res_path)53 QString wrap_legacy_resource_URIs (const QString &res_path) {
54 QString ret (res_path);
55
56 /*
57 * Skip empty or non-resource URIs.
58 * Assume all paths not starting with a colon are
59 * absolute, non-resource URIs.
60 */
61 if ((!(res_path.isEmpty ())) && (res_path.startsWith (':'))) {
62 std::vector<QString> legacy_locations;
63 legacy_locations.push_back (QString (":/icons/"));
64 legacy_locations.push_back (QString (":/png/"));
65 legacy_locations.push_back (QString (":/svg/"));
66
67 ret = fixup_resource_URIs (ret);
68
69 bool detected = false;
70
71 /* This would be so much easier with C++ and lambdas... */
72 std::vector<QString>::const_iterator it = legacy_locations.begin ();
73 while (it != legacy_locations.end ()) {
74 if (ret.startsWith (*(it++))) {
75 detected = true;
76 break;
77 }
78 }
79
80 if (detected)
81 ret.insert (1, QString ("/img"));
82 }
83
84 return (ret);
85 }
86
convert_to_rich_text(const QString & text,bool force)87 QString convert_to_rich_text (const QString &text, bool force) {
88 QString fixup_text (text);
89 fixup_text.replace ("\n", "\n<br />\n");
90
91 if (force) {
92 // This is a workaround for a bug in Qt. Even though we set Qt::RichText as the text format
93 // later on, the informative text is not recognized as rich text, UNLESS a HTML tag
94 // is used ON THE VERY FIRST LINE.
95 // Make sure, that there always is one...
96 fixup_text.prepend ("<b></b>");
97 }
98
99 return (fixup_text);
100 }
101
show_RichText_Generic_MsgBox(QMessageBox::Icon icon,const QString & main_text,const QString & informative_text,bool app_modal)102 void show_RichText_Generic_MsgBox (QMessageBox::Icon icon, const QString &main_text, const QString &informative_text, bool app_modal) {
103 QString fixup_main_text (convert_to_rich_text (main_text));
104 QString fixup_informative_text (convert_to_rich_text (informative_text, true));
105
106 QMessageBox msg_box (icon, QString ("X2Go Client"), fixup_main_text, QMessageBox::Ok);
107
108 msg_box.setTextFormat (Qt::RichText);
109 msg_box.setInformativeText (fixup_informative_text);
110
111 if (app_modal) {
112 msg_box.setWindowModality (Qt::ApplicationModal);
113 }
114 else {
115 msg_box.setWindowModality (Qt::WindowModal);
116 }
117
118 msg_box.exec ();
119 }
120
show_RichText_WarningMsgBox(const QString & main_text,const QString & informative_text,bool app_modal)121 void show_RichText_WarningMsgBox (const QString &main_text, const QString &informative_text, bool app_modal) {
122 show_RichText_Generic_MsgBox (QMessageBox::Warning, main_text, informative_text, app_modal);
123 }
124
show_RichText_ErrorMsgBox(const QString & main_text,const QString & informative_text,bool app_modal)125 void show_RichText_ErrorMsgBox (const QString &main_text, const QString &informative_text, bool app_modal) {
126 show_RichText_Generic_MsgBox (QMessageBox::Critical, main_text, informative_text, app_modal);
127 }
128
git_changelog_extract_commit_sha(const QString & gitlog)129 QString git_changelog_extract_commit_sha (const QString &gitlog) {
130 QString ret = "";
131
132 /*
133 * Do a poor man's split.
134 * We know that a newline character should be somewhere at the beginning of the string.
135 * We don't need to have Qt split the string up completely as we only care about
136 * a substring: from start to the first newline character.
137 */
138 std::ptrdiff_t pos = gitlog.indexOf ("\n");
139
140 if (0 < pos) {
141 ret = gitlog.left (pos + 1);
142
143 x2goDebug << "First line of git changelog: " << ret;
144
145 pos = ret.lastIndexOf (")");
146
147 if (0 < pos) {
148 std::ptrdiff_t pos_paren_start = ret.lastIndexOf ("(");
149
150 if ((0 < pos_paren_start) && (pos_paren_start < pos)) {
151 ret = ret.mid (pos_paren_start + 1, pos - pos_paren_start - 1);
152 }
153 else {
154 // Either starting parenthesis not found or starting parenthesis comes first.
155 ret = "";
156 }
157 }
158 else {
159 // End parenthesis not found.
160 ret = "";
161 }
162 }
163
164 return (ret);
165 }
166
font_is_monospaced(const QFont & font)167 bool font_is_monospaced (const QFont &font) {
168 const QFontInfo font_info (font);
169 return (font_info.fixedPitch ());
170 }
171
172 #ifdef Q_OS_DARWIN
show_XQuartz_not_found_error()173 void show_XQuartz_not_found_error () {
174 show_XQuartz_generic_error (QObject::tr ("X2Go Client could not find any suitable X11 server."),
175 QString ());
176 }
177
show_XQuartz_start_error()178 void show_XQuartz_start_error () {
179 show_XQuartz_generic_error (QObject::tr ("X2Go Client could not start X11 server."),
180 QObject::tr ("X2Go Client requires XQuartz to be installed.\n\n"
181 "If XQuartz is already installed on your system,\n"
182 "please select the correct path in the now upcoming dialog.\n"
183 "Refer to the end of this message for path examples,\n"
184 "in case you do not know the exact location yourself.\n\n"
185 "Should you have <b>not</b> installed XQuartz yet, please\n"
186 "follow the outlined steps:\n\n"));
187 }
188
show_XQuartz_generic_error(const QString & main_error,const QString & additional_info)189 void show_XQuartz_generic_error (const QString &main_error, const QString &additional_info) {
190 show_RichText_WarningMsgBox (main_error,
191 additional_info +
192 QObject::tr ("MacPorts users, please install either the port <b>xorg-server</b>\n"
193 "or the port <b>xorg-server-devel</b>.\n"
194 "Upon successful installation, please follow the instructions printed\n"
195 "by the port utility to autostart/load the server.\n\n"
196
197 "All other users, please obtain and install XQuartz from:\n"
198
199 "<center><a href=\"https://www.xquartz.org/\">"
200 "https://www.xquartz.org/"
201 "</a></center>\n\n"
202
203 "Afterwards, restart X2Go Client and select the correct path\n"
204 "to the X11 application in the general X2Go Client settings.\n"
205 "This will most likely be\n"
206 "<center><b>/Applications/MacPorts/X11.app</b></center>\n"
207 "or\n"
208 "<center><b>/Applications/Utilities/XQuartz.app</b></center>"));
209 }
210 #endif /* defined (Q_OS_DARWIN) */
211
212 #ifdef Q_OS_UNIX
add_to_path(const QString & orig_path,const QStringList & add,const bool back)213 QString add_to_path (const QString &orig_path, const QStringList &add, const bool back) {
214 QString ret = orig_path;
215 std::vector<bool> found;
216
217 QStringList orig_path_list = orig_path.split (":");
218
219 x2goDebug << "path value at beginning: " << orig_path;
220
221 /*
222 * Clean up add list. We want to make sure no entry ends in a slash
223 * and skip empty entries.
224 */
225 QStringList tmp_clean_add;
226 for (int i = 0; i < add.size (); ++i) {
227 if (!(add[i].isEmpty ())) {
228 if (add[i].right (1) == "/") {
229 QString tmp_elem = add[i];
230 tmp_elem.chop (1);
231
232 if (!(tmp_elem.isEmpty ())) {
233 tmp_clean_add.append (tmp_elem);
234 }
235 }
236 else {
237 tmp_clean_add.append (add[i]);
238 }
239 }
240 }
241
242 x2goDebug << "tmp_clean_add: " << tmp_clean_add;
243
244 /* Nothing to add, really... */
245 if (tmp_clean_add.isEmpty ()) {
246 return (ret);
247 }
248
249 /* Create unique array. */
250 QStringList clean_add;
251 {
252 QStringList::const_iterator begin = tmp_clean_add.constBegin (),
253 end = tmp_clean_add.constEnd ();
254 for (QStringList::const_iterator cit = begin; cit != end; ++cit) {
255 bool tmp_found = false;
256
257 for (QStringList::const_iterator cit2 = cit + 1; cit2 != end; ++cit2) {
258 if (*cit == *cit2) {
259 tmp_found = true;
260 break;
261 }
262 }
263
264 if (!tmp_found) {
265 clean_add.append (*cit);
266 }
267 }
268 }
269
270 x2goDebug << "clean_add: " << clean_add;
271
272 /* Nothing to add. */
273 if (clean_add.isEmpty ()) {
274 return (ret);
275 }
276
277 found.resize (clean_add.size (), false);
278
279 for (int i = 0; i < orig_path_list.length (); ++i) {
280 for (int y = 0; y < clean_add.size (); ++y) {
281 if (!found[y]) {
282 if ((orig_path_list[i] == QString (clean_add[y])) || (orig_path_list[i] == QString (clean_add[y] + '/'))) {
283 found[y] = true;
284 break;
285 }
286 }
287 }
288 }
289
290 for (std::size_t i = 0; i < found.size (); ++i) {
291 x2goDebug << "found entry i (" << i << ") in orig_path_list (" << clean_add[i] << "): " << found[i];
292 }
293
294 if (back) {
295 for (int i = 0; i < clean_add.size (); ++i) {
296 if (!found[i]) {
297 ret.append (QString (":" + clean_add[i]));
298 }
299 }
300 }
301 else {
302 for (int i = (clean_add.size () - 1); i >= 0; --i) {
303 if (!found[i]) {
304 ret.prepend (QString (clean_add[i] + ":"));
305 }
306 }
307 }
308
309 x2goDebug << "return value at end: " << ret;
310
311 return (ret);
312 }
313
find_binary(const QString & path,const QString & binary_name)314 QString find_binary (const QString &path, const QString &binary_name) {
315 QString ret = "";
316
317 if (!(binary_name.isEmpty ())) {
318 QString cur_path = "";
319 QString tmp_path = path;
320
321 if (path.isEmpty ()) {
322 tmp_path = "./";
323 }
324
325 QStringList path_list = tmp_path.split (":");
326
327 for (QStringList::const_iterator const_it = path_list.constBegin (); const_it != path_list.constEnd (); ++const_it) {
328 cur_path = *const_it;
329
330 if (cur_path.isEmpty ()) {
331 cur_path = "./";
332 }
333
334 cur_path = QDir (cur_path).absolutePath ();
335
336 cur_path += "/" + binary_name;
337
338 QFileInfo tmp_file_info = QFileInfo (cur_path);
339
340 if ((tmp_file_info.exists ()) && (tmp_file_info.isExecutable ())) {
341 ret = tmp_file_info.canonicalFilePath ();
342 break;
343 }
344 else {
345 x2goDebug << "Binary at " << cur_path << " either does not exist (" << !(tmp_file_info.exists ())
346 << ") or is not executable (" << tmp_file_info.isExecutable () << ")";
347 }
348 }
349 }
350
351 return (ret);
352 }
353 #endif /* defined (Q_OS_UNIX) */
354