1 #include "utilsSystem.h"
2 #include "unixutils.h"
3 #include "smallUsefulFunctions.h"
4 
5 #ifdef Q_OS_MAC
6 #include <CoreFoundation/CFURL.h>
7 #include <CoreFoundation/CFBundle.h>
8 #endif
9 
10 #ifdef Q_OS_WIN
11 #include <windows.h>
12 #endif
getDiskFreeSpace(const QString & path,quint64 & freeBytes)13 bool getDiskFreeSpace(const QString &path, quint64 &freeBytes)
14 {
15 #ifdef Q_OS_WIN
16 	wchar_t* d = new wchar_t[path.size() + 1];
17 	int len = path.toWCharArray(d);
18 	d[len] = 0;
19 
20 	ULARGE_INTEGER freeBytesToCaller;
21 	freeBytesToCaller.QuadPart = 0L;
22 
23 	if ( !GetDiskFreeSpaceEx( d, &freeBytesToCaller, NULL, NULL ) ) {
24         delete[] d;
25 		qDebug() << "ERROR: Call to GetDiskFreeSpaceEx() failed on path" << path;
26 		return false;
27 	}
28     delete[] d;
29 	freeBytes = freeBytesToCaller.QuadPart;
30 	return true;
31 #else
32 	Q_UNUSED(path)
33 	Q_UNUSED(freeBytes)
34 	return false;
35 #endif
36 }
37 
getKeyboardLanguage()38 QLocale::Language getKeyboardLanguage() {
39 	return QGuiApplication::inputMethod()->locale().language();
40 }
41 
42 /*!
43  * Redefine or filter some shortcuts depending on the locale
44  * On Windows, AltGr is interpreted as Ctrl+Alt so we shouldn't
45  * use a Ctrl+Alt+Key shortcut if AltGr+Key is used for typing
46  * characters.
47  */
filterLocaleShortcut(QKeySequence ks)48 QKeySequence filterLocaleShortcut(QKeySequence ks)
49 {
50 #ifndef Q_OS_WIN32
51 	return ks;
52 #else
53 	QLocale::Language lang = getKeyboardLanguage();
54 	switch (lang) {
55 	case QLocale::Hungarian:
56 		if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
57 			return QKeySequence("Ctrl+Alt+Shift+F");
58 		}
59 		break;
60 	case QLocale::Polish:
61 		if (ks.matches(QKeySequence("Ctrl+Alt+S"))) {
62 			return QKeySequence();
63 		} else if (ks.matches(QKeySequence("Ctrl+Alt+U"))) {
64 			return QKeySequence("Ctrl+Alt+Shift+U");
65 		}
66 		break;
67 	case QLocale::Turkish:
68 		if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
69 			return QKeySequence("Ctrl+Alt+Shift+F");
70 		}
71 		break;
72 	case QLocale::Czech:
73 		if (ks.matches(QKeySequence("Ctrl+Alt+S"))) {
74 			return QKeySequence();
75 		} else if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
76 			return QKeySequence("Ctrl+Alt+Shift+F");
77 		} else if (ks.matches(QKeySequence("Ctrl+Alt+L"))) {
78 			return QKeySequence("Ctrl+Alt+Shift+L");
79 		}
80 		break;
81 	case QLocale::Croatian:
82 		if (ks.matches(QKeySequence("Ctrl+Alt+F"))) {
83 			return QKeySequence("Ctrl+Alt+Shift+F");
84 		}
85 		break;
86 	default:
87 		return ks;
88 	}
89 	return ks;
90 #endif
91 }
92 
getPathListSeparator()93 QChar getPathListSeparator()
94 {
95 #ifdef Q_OS_WIN32
96 	return QChar(';');
97 #else
98 	return QChar(':');
99 #endif
100 }
101 
splitPaths(const QString & paths)102 QStringList splitPaths(const QString &paths)
103 {
104 	if (paths.isEmpty()) return QStringList();
105 	return paths.split(getPathListSeparator());
106 }
107 
getUserName()108 QString getUserName()
109 {
110 #ifdef Q_OS_WIN32
111 	return QString(qgetenv("USERNAME"));
112 #else
113 	return QString(qgetenv("USER"));
114 #endif
115 }
116 
getUserDocumentFolder()117 QString getUserDocumentFolder()
118 {
119 #ifdef Q_OS_WIN32
120 	// typically "C:/Documents and Settings/Username/My Documents"
121 	QSettings settings(QSettings::UserScope, "Microsoft", "Windows");
122 	settings.beginGroup("CurrentVersion/Explorer/Shell Folders");
123 	return settings.value("Personal").toString();
124 #else
125 	return QDir::homePath();
126 #endif
127 }
128 
findResourceFiles(const QString & dirName,const QString & filter,QStringList additionalPreferredPaths)129 QStringList findResourceFiles(const QString &dirName, const QString &filter, QStringList additionalPreferredPaths)
130 {
131 	QStringList searchFiles;
132 	QString dn = dirName;
133 	if (dn.endsWith('/') || dn.endsWith(QDir::separator())) dn = dn.left(dn.length() - 1); //remove / at the end
134 	if (!dn.startsWith('/') && !dn.startsWith(QDir::separator())) dn = "/" + dn; //add / at beginning
135 	searchFiles << ":" + dn; //resource fall back
136 	searchFiles.append(additionalPreferredPaths);
137     searchFiles << QCoreApplication::applicationDirPath() + "/../share/texstudio"; //appimage relative path
138 	searchFiles << QCoreApplication::applicationDirPath() + dn; //windows new
139     searchFiles << QCoreApplication::applicationDirPath() + "/"; //windows old
140     searchFiles << QCoreApplication::applicationDirPath() + "/dictionaries/"; //windows new
141     searchFiles << QCoreApplication::applicationDirPath() + "/translations/"; //windows new
142     searchFiles << QCoreApplication::applicationDirPath() + "/help/"; //windows new
143     searchFiles << QCoreApplication::applicationDirPath() + "/utilities/"; //windows new
144 	// searchFiles<<QCoreApplication::applicationDirPath() + "/data/"+fileName; //windows new
145 
146 #if !defined(PREFIX)
147 #define PREFIX ""
148 #endif
149 
150 #if defined( Q_WS_X11 ) || defined (Q_OS_LINUX)
151 	searchFiles << PREFIX"/share/texstudio" + dn; //X_11
152 #endif
153 #ifdef Q_OS_MAC
154 	CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
155 	CFStringRef macPath = CFURLCopyFileSystemPath(appUrlRef,
156 	                      kCFURLPOSIXPathStyle);
157 	const char *pathPtr = CFStringGetCStringPtr(macPath,
158 	                      CFStringGetSystemEncoding());
159 	searchFiles << QString(pathPtr) + "/Contents/Resources" + dn; //Mac
160 	CFRelease(appUrlRef);
161 	CFRelease(macPath);
162 #endif
163 
164 	QStringList result;
165 	foreach (const QString &fn, searchFiles) {
166 		QDir fic(fn);
167 		if (fic.exists() && fic.isReadable())
168 			result << fic.entryList(QStringList(filter), QDir::Files, QDir::Name);
169 	}
170 	// sort and remove double entries
171 	result.sort();
172 
173 	QMutableStringListIterator i(result);
174 	QString old = "";
175 	while (i.hasNext()) {
176 		QString cmp = i.next();
177 		if (cmp == old) i.remove();
178 		else old = cmp;
179 	}
180 	return result;
181 }
182 
findResourceFile(const QString & fileName,bool allowOverride,QStringList additionalPreferredPaths,QStringList additionalFallbackPaths)183 QString findResourceFile(const QString &fileName, bool allowOverride, QStringList additionalPreferredPaths, QStringList additionalFallbackPaths)
184 {
185 	QStringList searchFiles;
186 
187 	if (!allowOverride) searchFiles << ":/"; //search first in included resources (much faster)
188 
189 	foreach (const QString &s, additionalPreferredPaths)
190 		if (s.endsWith('/') || s.endsWith('\\')) searchFiles << s;
191 		else searchFiles << s + "/";
192 #if defined Q_WS_X11 || defined Q_OS_LINUX || defined Q_OS_UNIX
193 	searchFiles << PREFIX"/share/texstudio/"; //X_11
194     searchFiles << QCoreApplication::applicationDirPath() + "/../share/texstudio/"; // relative path for appimage
195 	if (fileName.endsWith(".html")) searchFiles << PREFIX"/share/doc/texstudio/html/"; //for Debian package
196 #endif
197 #ifdef Q_OS_MAC
198 	searchFiles << QCoreApplication::applicationDirPath() + "/../Resources/"; //macx
199 #endif
200 	searchFiles << QCoreApplication::applicationDirPath() + "/"; //windows old
201 	searchFiles << QCoreApplication::applicationDirPath() + "/dictionaries/"; //windows new
202 	searchFiles << QCoreApplication::applicationDirPath() + "/translations/"; //windows new
203 	searchFiles << QCoreApplication::applicationDirPath() + "/help/"; //windows new
204 	searchFiles << QCoreApplication::applicationDirPath() + "/utilities/"; //windows new
205 	// searchFiles<<QCoreApplication::applicationDirPath() + "/data/"; //windows new
206 
207 	if (allowOverride) searchFiles << ":/"; //resource fall back
208 
209 	foreach (const QString &s, additionalFallbackPaths)
210 		if (s.endsWith('/') || s.endsWith('\\')) searchFiles << s;
211 		else searchFiles << s + "/";
212 
213 	foreach (const QString &fn, searchFiles) {
214 		QFileInfo fic(fn + fileName);
215 		if (fic.exists() && fic.isReadable())
216 			return fic.canonicalFilePath();
217 	}
218 	QString newFileName = fileName.split("/").last();
219 	if (!newFileName.isEmpty()) {
220 		foreach (const QString &fn, searchFiles) {
221 			QFileInfo fic(fn + newFileName);
222 			if (fic.exists() && fic.isReadable())
223 				return fic.canonicalFilePath();
224 		}
225 	}
226 	return "";
227 }
228 
229 /*!
230  * \brief put quotes araound strin when necessary
231  *
232  * Enclose the given string with quotes if it contains spaces.
233  * This function is useful
234  * \param s input string
235  * \return string with quotes (if s contains spaces)
236  */
quoteSpaces(const QString & s)237 QString quoteSpaces(const QString &s)
238 {
239     if (!s.contains(' ')) return s;
240     return '"' + s + '"';
241 }
242 
243 
244 int modernStyle;
245 bool darkMode;
246 bool useSystemTheme;
247 
248 /*!
249  * \brief return icon according to settings
250  *
251  * The icon is looked up in txs resources. It prefers svg over png.
252  * In case of active dark mode, icons with name icon_dm is used (if present).
253  * \param icon icon name
254  * \return found icon as file name
255  */
getRealIconFile(const QString & icon)256 QString getRealIconFile(const QString &icon)
257 {
258 	if (icon.isEmpty() || icon.startsWith(":/")) return icon;
259     QStringList suffixList{""};
260     if(darkMode)
261         suffixList=QStringList{"_dm",""};
262     QStringList iconNames = QStringList();
263     for(const QString& suffix : suffixList){
264         iconNames
265                 << ":/images-ng/" + icon + suffix + ".svg"
266                 << ":/images-ng/" + icon + suffix + ".svgz" ;
267         if (modernStyle) {
268             iconNames << ":/images-ng/modern/" + icon + suffix + ".svg"
269                       << ":/images-ng/modern/" + icon + suffix + ".svgz"
270                       << ":/modern/images/modern/" + icon + suffix + ".png";
271         } else {
272             iconNames << ":/images-ng/classic/" + icon + suffix + ".svg"
273                       << ":/images-ng/classic/" + icon + suffix + ".svgz"
274                       << ":/classic/images/classic/" + icon + suffix + ".png";
275         }
276         iconNames << ":/symbols-ng/icons/" + icon + suffix + ".svg" ;//voruebergehend
277         iconNames << ":/symbols-ng/icons/" + icon + suffix + ".png"; //voruebergehend
278         iconNames << ":/images/" + icon + ".png";
279     }
280 
281 	foreach (const QString &name, iconNames) {
282         if (QFileInfo::exists(name))
283 			return name;
284 	}
285 
286 	return icon;
287 }
288 
289 /*!
290  * \brief search icon
291  *
292  * Looks up icon by name. If settings allow, tries to use system icons, otherwise tries to find in txs resources.
293  * \param icon icon name
294  * \return icon
295  */
getRealIcon(const QString & icon)296 QIcon getRealIcon(const QString &icon)
297 {
298 	if (icon.isEmpty()) return QIcon();
299 	if (icon.startsWith(":/")) return QIcon(icon);
300 	if (useSystemTheme && QIcon::hasThemeIcon(icon)) return QIcon::fromTheme(icon);
301 	//return QIcon(getRealIconFile(icon.contains(".")?icon:(icon+".png")));
302 	QString name = getRealIconFile(icon);
303 	QIcon ic = QIcon(name);
304 	//if(ic.isNull()){
305 #if (defined(Q_OS_OSX))
306 	QPixmap pm(32, 32);
307 	pm.load(name);
308 	ic = QIcon(pm);
309 #endif
310 	return ic;
311 }
312 
313 /*!
314  * \brief search icon
315  *
316  * Looks up icon by name. If settings allow, tries to use system icons, otherwise tries to find in txs resources.
317  * The icon is cached for better reactivity.
318  * \param icon icon name
319  * \param forceReload ignore cache and reload icon from database. This is necessary if light-/dark-mode is changed.
320  * \return icon
321  */
getRealIconCached(const QString & icon,bool forceReload)322 QIcon getRealIconCached(const QString &icon, bool forceReload)
323 {
324     if (iconCache.contains(icon) && !forceReload) {
325 		return *iconCache[icon];
326 	}
327 	if (icon.isEmpty()) return QIcon();
328 
329 	if (icon.startsWith(":/")) {
330 		QIcon *icn = new QIcon(icon);
331 		iconCache.insert(icon, icn);
332 		return *icn;
333 	}
334 	if (useSystemTheme && QIcon::hasThemeIcon(icon)) {
335 		QIcon *icn = new QIcon(QIcon::fromTheme(icon));
336 		iconCache.insert(icon, icn);
337 		return *icn;
338 	}
339 
340 	//return QIcon(getRealIconFile(icon.contains(".")?icon:(icon+".png")));
341 	QIcon *icn = new QIcon(getRealIconFile(icon));
342 	iconCache.insert(icon, icn);
343 	return *icn;
344 }
345 
346 /*!
347  * \brief Tries to determine if the system uses dark mode
348  *
349  * This function tries to determine if a system uses "dark mode" by looking up general text color and converting it into gray-scale value.
350  * A value above 200 (scale is 0 .. 255 ) is considered as light text color on probably dark background, hence a dark mode is detected.
351  * This approach is independent on specific on different systems.
352  * \return true -> uses dark mode
353  */
systemUsesDarkMode(const QPalette & pal)354 bool systemUsesDarkMode(const QPalette &pal)
355 {
356     QColor clr=pal.color(QPalette::Text);
357     return qGray(clr.rgb())>200;
358 }
359 
isFileRealWritable(const QString & filename)360 bool isFileRealWritable(const QString &filename)
361 {
362     if(QFileInfo::exists(filename)){
363         return QFileInfo(filename).isWritable();
364     }
365 
366 	QFile fi(filename);
367 	bool result = false;
368 	if (fi.exists()) result = fi.open(QIODevice::ReadWrite);
369 	else {
370 		result = fi.open(QIODevice::WriteOnly);
371 		fi.remove();
372 	}
373 	return result;
374 }
375 /*!
376  * \brief checks if file exists and is writeable
377  *
378  * Convenience function
379  * \param filename
380  * \return
381  */
isExistingFileRealWritable(const QString & filename)382 bool isExistingFileRealWritable(const QString &filename)
383 {
384     return QFileInfo::exists(filename) && isFileRealWritable(filename);
385 }
386 
387 /*!
388  * \brief make sure that the dirPath is terminated with a separator (/ or \)
389  *
390  * \param dirPath
391  * \return dirPath with trailing separator
392  */
393 
ensureTrailingDirSeparator(const QString & dirPath)394 QString ensureTrailingDirSeparator(const QString &dirPath)
395 {
396 	if (dirPath.isEmpty() || dirPath.endsWith("/")) return dirPath;
397 	if (dirPath.endsWith(QDir::separator())) return dirPath;
398 #ifdef Q_OS_WIN32
399 	if (dirPath.endsWith("\\")) return dirPath; //you can create a directory named \ on linux
400 #endif
401 	return dirPath + "/";
402 }
403 /*!
404  * \brief join dirname and filename with apropriate separator
405  * \param dirname
406  * \param filename
407  * \return
408  */
joinPath(const QString & dirname,const QString & filename)409 QString joinPath(const QString &dirname, const QString &filename)
410 {
411 	return ensureTrailingDirSeparator(dirname) + filename;
412 }
413 /*!
414  * \brief join dirname, dirname2 and filename with apropriate separator
415  * \param dirname
416  * \param dirname2
417  * \param filename
418  * \return
419  */
joinPath(const QString & dirname,const QString & dirname2,const QString & filename)420 QString joinPath(const QString &dirname, const QString &dirname2, const QString &filename)
421 {
422 	return ensureTrailingDirSeparator(dirname) + ensureTrailingDirSeparator(dirname2) + filename;
423 }
424 
425 /// Removes any symbolic link inside the file path.
426 /// Does nothing on Windows.
getNonSymbolicFileInfo(const QFileInfo & info)427 QFileInfo getNonSymbolicFileInfo(const QFileInfo& info)
428 {
429 #ifdef Q_OS_UNIX
430 	const size_t MAX_DIR_DEPTH=32; //< Do not seek for symbolic links deeper than MAX_DIR_DEPTH.
431 	                               // For performance issues and if the root directory was not catched (infinite loop).
432 	// Static array might be also used to prevent heap allocation for a small amont of data. QFileInfo is shared, so the size of the array is size_of(void*)*MAX_DIR_DEPTH
433 	//QFileInfo stack[MAX_DIR_DEPTH];
434 	QStack<QFileInfo> stack;
435 	stack.reserve(MAX_DIR_DEPTH);
436 	stack.push(info);
437 	size_t depth = 0;
438 	int lastChanged = 0;
439 
440 	QFileInfo pfi ;
441 	do
442 	{
443 		QDir parent =  stack.top().dir();
444 		pfi = QFileInfo(parent.absolutePath());
445 		if (pfi.isSymLink())
446 		{
447 			pfi = QFileInfo(pfi.symLinkTarget());
448 			lastChanged = depth; // = stack.size()-1;
449 		}
450 		stack.push(pfi);
451 		depth++;
452 	} while(!pfi.isRoot()  &&  depth < MAX_DIR_DEPTH);
453 
454 	//if (Q_UNLIKELY(lastChanged != -1))
455 	//{
456 		pfi = stack[lastChanged];
457 		int i = lastChanged -1;
458 		for(; i>= 0; i-- ){
459 			QFileInfo& ci = stack[i];
460 			pfi = QFileInfo( QDir(pfi.absoluteFilePath()), ci.fileName() );
461 		}
462 		return pfi;
463 	//}
464 
465 #else
466 	// Does nothing on Windows
467 		return info;
468 #endif
469 }
470 /*!
471  * \brief replace file extension
472  * \param filename
473  * \param newExtension
474  * \param appendIfNoExt
475  * \return
476  */
replaceFileExtension(const QString & filename,const QString & newExtension,bool appendIfNoExt)477 QString replaceFileExtension(const QString &filename, const QString &newExtension, bool appendIfNoExt)
478 {
479 	QFileInfo fi(filename);
480 	QString ext = newExtension.startsWith('.') ? newExtension.mid(1) : newExtension;
481 	if (fi.suffix().isEmpty()) {
482 		if (appendIfNoExt)
483 			return filename + '.' + ext;
484 		else
485 			return QString();
486 	}
487 	// exchange the suffix explicitly instead of using fi.completeBaseName()
488 	// so that the filename stays exactly the same
489 	return filename.left(filename.length() - fi.suffix().length()) + ext;
490 }
491 
getRelativeBaseNameToPath(const QString & file,QString basepath,bool baseFile,bool keepSuffix)492 QString getRelativeBaseNameToPath(const QString &file, QString basepath, bool baseFile, bool keepSuffix)
493 {
494 	basepath.replace(QDir::separator(), "/");
495 	if (basepath.endsWith("/")) basepath = basepath.left(basepath.length() - 1);
496 
497 	QFileInfo fi(file);
498 	QString filename = fi.fileName();
499 	QString path = fi.path();
500 	if (path.endsWith("/")) path = path.left(path.length() - 1);
501 	QStringList basedirs = basepath.split("/");
502 	if (baseFile && !basedirs.isEmpty()) basedirs.removeLast();
503 	QStringList dirs = path.split("/");
504 	//QStringList basedirs = QStringList::split("/", basepath, false);
505 	//QStringList dirs = QStringList::split("/", path, false);
506 
507 	int nDirs = dirs.count();
508 
509 	while (dirs.count() > 0 && basedirs.count() > 0 &&  dirs[0] == basedirs[0]) {
510 		dirs.pop_front();
511 		basedirs.pop_front();
512 	}
513 
514 	if (nDirs != dirs.count()) {
515 		path = dirs.join("/");
516 
517 		if (basedirs.count() > 0) {
518 			for (int j = 0; j < basedirs.count(); ++j) {
519 				path = "../" + path;
520 			}
521 		}
522 
523 		//if (path.length()>0 && path.right(1) != "/") path = path + "/";
524 	} else {
525 		path = fi.path();
526 	}
527 
528 	if (path.length() > 0 && !path.endsWith("/") && !path.endsWith("\\")) path += "/"; //necessary if basepath isn't given
529 
530 	if (keepSuffix)
531 		return path + filename;
532 	return path + fi.completeBaseName();
533 }
534 
getPathfromFilename(const QString & compFile)535 QString getPathfromFilename(const QString &compFile)
536 {
537 	if (compFile.isEmpty()) return "";
538 	QString dir = QFileInfo(compFile).absolutePath();
539 	if (!dir.endsWith("/") && !dir.endsWith(QDir::separator())) dir.append(QDir::separator());
540 	return dir;
541 }
542 
findAbsoluteFilePath(const QString & relName,const QString & extension,const QStringList & searchPaths,const QString & fallbackPath)543 QString findAbsoluteFilePath(const QString &relName, const QString &extension, const QStringList &searchPaths, const QString &fallbackPath)
544 {
545 	QString s = relName;
546 	QString ext = extension;
547 	if (!ext.isEmpty() && !ext.startsWith(".")) ext = "." + ext;
548 	if (!s.endsWith(ext, Qt::CaseInsensitive)) s += ext;
549 	QFileInfo fi(s);
550 	if (!fi.isRelative()) return s;
551 	foreach (const QString &path, searchPaths) {
552 		fi.setFile(QDir(path), s);
553 		if (fi.exists()) return fi.absoluteFilePath();
554 	}
555 	QString fbp = fallbackPath;
556 	if (!fbp.isEmpty() && !fbp.endsWith('/') && !fbp.endsWith(QDir::separator())) fbp += QDir::separator();
557 	return fbp + s; // fallback
558 }
559 
560 /*!
561  * Tries to get a non-existent filename. If guess, does not exist, return it.
562  * Otherwise, try find a non-extistent filename by increasing a number at the end
563  * of the filesname. If there is already a number, start from there, e.g.
564  * test02.txt -> test03.txt. If no free filename could be determined, return fallback.
565  */
getNonextistentFilename(const QString & guess,const QString & fallback)566 QString getNonextistentFilename(const QString &guess, const QString &fallback)
567 {
568 	QFileInfo fi(guess);
569 	if (!fi.exists()) return guess;
570 	QRegExp reNumberedFilename("(.*[^\\d])(\\d*)\\.(\\w+)");
571 	if (!reNumberedFilename.exactMatch(guess)) {
572 		return fallback;
573 	}
574 	QString base = reNumberedFilename.cap(1);
575 	QString ext = reNumberedFilename.cap(3);
576 	int num = reNumberedFilename.cap(2).toInt();
577 	int numLen = reNumberedFilename.cap(2).length();
578 
579 	for (int i = num + 1; i <= 1000000; i++) {
580 		QString filename = QString("%1%2.%3").arg(base).arg(i, numLen, 10, QLatin1Char('0')).arg(ext);
581 		fi.setFile(filename);
582 		if (!fi.exists())
583 			return filename;
584 	}
585 	return fallback;
586 }
587 /*!
588  * \brief get environment path
589  *
590  * Get the content of PATH environment variable.
591  * On OSX starts a bash to get a more complete picture as programs get started without access to the bash PATH.
592  * \return path
593  */
getEnvironmentPath()594 QString getEnvironmentPath()
595 {
596 	static QString path;
597 	if (path.isNull()) {
598 #ifdef Q_OS_MAC
599 		QProcess *myProcess = new QProcess();
600         myProcess->start("/usr/libexec/path_helper");  // -n ensures there is no newline at the end
601 
602 		myProcess->waitForFinished(3000);
603 		if (myProcess->exitStatus() == QProcess::NormalExit) {
604 			QByteArray res = myProcess->readAllStandardOutput();
605             path = QString(res).split('\"').at(1);  // bash may have some initial output. path is on the last line
606 		} else {
607 			path = "";
608 		}
609 		delete myProcess;
610 #else
611 		path = QProcessEnvironment::systemEnvironment().value("PATH");
612 #endif
613 	}
614 	return path;
615 }
616 /*!
617  * \brief get environment path list
618  * Same as getEnvironmentPath(), but splits the sresult into a stringlist.
619  * \return
620  */
getEnvironmentPathList()621 QStringList getEnvironmentPathList()
622 {
623 	return getEnvironmentPath().split(getPathListSeparator());
624 }
625 /*!
626  * \brief update path settings for a process
627  * \param proc process
628  * \param additionalPaths
629  */
updatePathSettings(QProcess * proc,QString additionalPaths)630 void updatePathSettings(QProcess *proc, QString additionalPaths)
631 {
632 	QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
633 	QString path(getEnvironmentPath());
634 	if (!additionalPaths.isEmpty()) {
635 		path += getPathListSeparator() + additionalPaths;
636 	}
637 	env.insert("PATH", path);
638 	// Note: this modifies the path only for the context of the called program. It does not affect the search path for the program itself.
639 	proc->setProcessEnvironment(env);
640 }
641 
showInGraphicalShell(QWidget * parent,const QString & pathIn)642 void showInGraphicalShell(QWidget *parent, const QString &pathIn)
643 {
644 	// Mac, Windows support folder or file.
645 #if defined(Q_OS_WIN)
646 	QFileInfo fiExplorer(QProcessEnvironment::systemEnvironment().value("WINDIR"), "explorer.exe");
647 	if (!fiExplorer.exists()) {
648 		QMessageBox::warning(parent,
649 							 QApplication::translate("Texstudio",
650 													 "Launching Windows Explorer Failed"),
651 							 QApplication::translate("Texstudio",
652 													 "Could not find explorer.exe in path to launch Windows Explorer."));
653 		return;
654 	}
655 	QStringList param;
656 	if (!QFileInfo(pathIn).isDir())
657 		param += QLatin1String("/select,");
658 	param += QDir::toNativeSeparators(pathIn);
659 	QProcess::startDetached(fiExplorer.absoluteFilePath(), param);
660 #elif defined(Q_OS_MAC)
661 	QStringList scriptArgs;
662 	scriptArgs << QLatin1String("-e")
663 			   << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
664 									 .arg(pathIn);
665 	QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
666 	scriptArgs.clear();
667 	scriptArgs << QLatin1String("-e")
668 			   << QLatin1String("tell application \"Finder\" to activate");
669 	QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
670 #else
671 	// we cannot select a file here, because no file browser really supports it...
672 	using namespace Utils;
673 	const QFileInfo fileInfo(pathIn);
674 	const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.filePath();
675 	QSettings dummySettings;
676 	const QString app = UnixUtils::fileBrowser(&dummySettings);
677     QProcess browserProc;
678     const QString browserArg = UnixUtils::substituteFileBrowserParameters(app, folder);
679 #if QT_VERSION>=QT_VERSION_CHECK(5,15,0)
680     QStringList args=QProcess::splitCommand(browserArg);
681 #else
682     QStringList args=browserArg.split(" "); // this assumes that the command is not using quotes with spaces in the command path, better solution from qt5.15 ...
683 #endif
684     if(args.isEmpty())
685         return;
686     QString cmd=args.takeFirst();
687     for(QString &elem:args){
688         elem=removeQuote(elem);
689     }
690     bool success = browserProc.startDetached(cmd,args);
691 	const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
692 	success = success && error.isEmpty();
693 	if (!success)
694         QMessageBox::critical(parent, app, error);
695 #endif
696 }
697 
msgGraphicalShellAction()698 QString msgGraphicalShellAction()
699 {
700 #if defined(Q_OS_WIN)
701 	return QApplication::translate("Texstudio", "Show in Explorer");
702 #elif defined(Q_OS_MAC)
703 	return QApplication::translate("Texstudio", "Show in Finder");
704 #else
705 	return QApplication::translate("Texstudio", "Show Containing Folder");
706 #endif
707 }
708 /*!
709  * \brief determine which x11 environment is used.
710  * This is probably obsolete.
711  * \return 0 : no kde ; 3: kde ; 4 : kde4 ; 5 : kde5
712  */
x11desktop_env()713 int x11desktop_env()
714 {
715 	// 0 : no kde ; 3: kde ; 4 : kde4 ; 5 : kde5 ;
716 	QString kdesession = ::getenv("KDE_FULL_SESSION");
717 	QString kdeversion = ::getenv("KDE_SESSION_VERSION");
718 	if (!kdeversion.isEmpty()) {
719 		return kdeversion.toInt();
720 	}
721 	if (!kdesession.isEmpty()) return 3;
722 	return 0;
723 }
724 
725 // detect a retina macbook via the model identifier
726 // http://support.apple.com/kb/HT4132?viewlocale=en_US&locale=en_US
isRetinaMac()727 bool isRetinaMac()
728 {
729 #ifdef Q_OS_MAC
730 	static bool firstCall = true;
731 	static bool isRetina = false;
732 	if (firstCall) {
733 		firstCall = false;
734 		QProcess process;
735 		process.start("sysctl", QStringList() << "-n" << "hw.model");
736 		process.waitForFinished(1000);
737 		QString model(process.readAllStandardOutput()); // is something like "MacBookPro10,1"
738 		QRegExp rx("MacBookPro([0-9]*)");
739 		rx.indexIn(model);
740 		int num = rx.cap(1).toInt();
741 		if (num >= 10) // compatibility with future MacBookPros. Assume they are also retina.
742 			isRetina = true;
743 	}
744 	return isRetina;
745 #else
746 	return false;
747 #endif
748 }
749 
hasAtLeastQt(int major,int minor)750 bool hasAtLeastQt(int major, int minor)
751 {
752 	QStringList vers = QString(qVersion()).split('.');
753 	if (vers.count() < 2) return false;
754 	int ma = vers[0].toInt();
755 	int mi = vers[1].toInt();
756 	return (ma > major) || (ma == major && mi >= minor);
757 }
758 
759 /// convenience function for unique connections independent of the Qt version
connectUnique(const QObject * sender,const char * signal,const QObject * receiver,const char * method)760 bool connectUnique(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
761 {
762 	return QObject::connect(sender, signal, receiver, method, Qt::UniqueConnection);
763 }
764 
sleep(unsigned long s)765 void ThreadBreaker::sleep(unsigned long s)
766 {
767 	QThread::sleep(s);
768 }
769 
msleep(unsigned long ms)770 void ThreadBreaker::msleep(unsigned long ms)
771 {
772 	QThread::msleep(ms);
773 };
774 
forceTerminate(QThread * t)775 void ThreadBreaker::forceTerminate(QThread *t)
776 {
777 	if (!t) t = QThread::currentThread();
778 	t->setTerminationEnabled(true);
779 	t->terminate();
780 }
781 
SafeThread()782 SafeThread::SafeThread(): QThread(nullptr), crashed(false) {}
SafeThread(QObject * parent)783 SafeThread::SafeThread(QObject *parent): QThread(parent), crashed(false) {}
784 
wait(unsigned long time)785 void SafeThread::wait(unsigned long time)
786 {
787 	if (crashed) return;
788 	QThread::wait(time);
789 }
790 
791 
convertStringListtoSet(const QStringList & list)792 QSet<QString> convertStringListtoSet(const QStringList &list)
793 {
794 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
795     return QSet<QString>(list.begin(),list.end());
796 #else
797     return QSet<QString>::fromList(list);
798 #endif
799 
800 }
801