1/*
2 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "mainwindow.h"
18#include "settings.h"
19#include "systemconsts.h"
20#include "utils.h"
21#include <QApplication>
22#include <QDir>
23#include <QTranslator>
24#include <QStandardPaths>
25#include <QMessageBox>
26#include <QStringList>
27#include <QAbstractNativeEventFilter>
28#include <QDesktopWidget>
29#include <QDir>
30#include <QScreen>
31#include "common.h"
32#include "colorscheme.h"
33#include "iconsmanager.h"
34#include "autolinkmanager.h"
35#include <qt_utils/charsetinfo.h>
36#include "parser/parserutils.h"
37#include "editorlist.h"
38#include "widgets/choosethemedialog.h"
39#include "thememanager.h"
40
41#ifdef Q_OS_WIN
42#include <QTemporaryFile>
43#include <windows.h>
44#include <psapi.h>
45#include <QSharedMemory>
46#include <QBuffer>
47#include <winuser.h>
48
49#include "widgets/cpudialog.h"
50#endif
51
52QString getSettingFilename(const QString& filepath, bool& firstRun);
53#ifdef Q_OS_WIN
54class WindowLogoutEventFilter : public QAbstractNativeEventFilter {
55
56 // QAbstractNativeEventFilter interface
57public:
58 bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
59};
60#define WM_USER_OPEN_FILE (WM_USER+1)
61HWND prevAppInstance = NULL;
62BOOL CALLBACK GetPreviousInstanceCallback(HWND hwnd, LPARAM param){
63 BOOL result = TRUE;
64 WCHAR buffer[4098];
65 HINSTANCE hWindowModule = (HINSTANCE)GetWindowLongPtr(hwnd,GWLP_HINSTANCE);
66
67 if (hWindowModule==0)
68 return result;
69
70 DWORD processID;
71
72 // Get the ID of the process that created this window
73 GetWindowThreadProcessId(hwnd,&processID);
74 if (processID==0)
75 return result;
76
77 // Get the process associated with the ID
78 HANDLE hWindowProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
79 if (hWindowProcess == 0)
80 return result;
81
82 // Get its module filename
83 if (GetModuleFileNameExW(hWindowProcess, hWindowModule, buffer, sizeof(buffer)) == 0)
84 return TRUE;
85
86 CloseHandle(hWindowProcess); // not needed anymore
87 WCHAR * compareFilename=(WCHAR*)param;
88 QString s1=QString::fromWCharArray(compareFilename);
89 QString s2=QString::fromWCharArray(buffer);
90
91 //Is from the "same" application?
92 if (QString::compare(s1,s2,PATH_SENSITIVITY)==0) {
93 //found, stop EnumWindows loop
94 prevAppInstance = hwnd;
95 return FALSE;
96 }
97
98 return TRUE;
99}
100
101HWND getPreviousInstance() {
102 WCHAR buffer[4098];
103 //ShowMessage('ERROR_ALREADY_EXISTS');
104 // Store our own module filename
105 if (GetModuleFileNameW(GetModuleHandle(NULL), buffer, sizeof(buffer)) == 0)
106 return NULL;
107
108 // If that's the case, walk all top level windows and find the previous instance
109 // At this point, the program that created the mutex might not have created its MainForm yet
110 if (EnumWindows(GetPreviousInstanceCallback, LPARAM(buffer))==0) {
111 return prevAppInstance;
112 } else
113 return NULL;
114}
115
116bool WindowLogoutEventFilter::nativeEventFilter(const QByteArray & /*eventType*/, void *message, long *result){
117 MSG * pMsg = static_cast<MSG *>(message);
118 switch(pMsg->message) {
119 case WM_QUERYENDSESSION:
120 if (pMsg->lParam == 0
121 || (pMsg->lParam & ENDSESSION_LOGOFF)) {
122 if (!pMainWindow->close()) {
123 *result = 0;
124 } else {
125 *result = 1;
126 }
127 return true;
128 }
129 break;
130 case WM_DPICHANGED:{
131 if (pMsg->hwnd == (HWND)pMainWindow->winId()) {
132 int oldDPI = screenDPI();
133 QEvent * dpiEvent = new QEvent(DPI_CHANGED_EVENT);
134 qApp->postEvent(pMainWindow,dpiEvent);
135 setScreenDPI(HIWORD(pMsg->wParam));
136 int newDPI = screenDPI();
137 pMainWindow->updateDPI(oldDPI,newDPI);
138 } else if (pMainWindow->cpuDialog() &&
139 (HWND)pMainWindow->cpuDialog()->winId() == pMsg->hwnd) {
140 int newDPI = HIWORD(pMsg->wParam);
141 pMainWindow->cpuDialog()->updateDPI(newDPI);
142 }
143 break;
144 }
145 case WM_USER_OPEN_FILE: {
146 QSharedMemory sharedMemory("RedPandaCpp/openfiles");
147 if (sharedMemory.attach()) {
148 QBuffer buffer;
149 QDataStream in(&buffer);
150 QStringList files;
151 sharedMemory.lock();
152 buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());
153 buffer.open(QBuffer::ReadOnly);
154 in >> files;
155 sharedMemory.unlock();
156 if (pMainWindow->isMinimized()) {
157 pMainWindow->showNormal();
158 }
159 pMainWindow->openFiles(files);
160 sharedMemory.detach();
161 }
162 return true;
163 }
164 }
165 return false;
166}
167
168bool sendFilesToInstance() {
169 HWND prevInstance = getPreviousInstance();
170 if (prevInstance != NULL) {
171 QSharedMemory sharedMemory("RedPandaCpp/openfiles");
172 QBuffer buffer;
173 buffer.open(QBuffer::ReadWrite);
174 QDataStream out(&buffer);
175 QStringList filesToOpen = qApp->arguments();
176 filesToOpen.pop_front();
177 out<<filesToOpen;
178 int size = buffer.size();
179 if (sharedMemory.create(size)) {
180 sharedMemory.lock();
181 char *to = (char*)sharedMemory.data();
182 const char *from = buffer.data().data();
183 memcpy(to, from, qMin(sharedMemory.size(), size));
184 sharedMemory.unlock();
185 SendMessage(prevInstance,WM_USER_OPEN_FILE,0,0);
186 return true;
187 }
188 }
189 return false;
190}
191#endif
192
193QString getSettingFilename(const QString& filepath, bool& firstRun) {
194 QString filename;
195 if (filepath.isEmpty()) {
196 if (isGreenEdition()) {
197 filename = includeTrailingPathDelimiter(QApplication::applicationDirPath()) +
198 "config/" + APP_SETTSINGS_FILENAME;
199 } else {
200 filename =includeTrailingPathDelimiter(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)[0])
201 + APP_SETTSINGS_FILENAME;
202 }
203 } else {
204 filename = filepath;
205 }
206
207 QFileInfo fileInfo(filename);
208 firstRun = !fileInfo.exists();
209 QDir dir(fileInfo.absoluteDir());
210 if (!dir.exists()) {
211 if (!dir.mkpath(dir.absolutePath())) {
212 QMessageBox::critical(nullptr, QObject::tr("Error"),
213 QString(QObject::tr("Can't create configuration folder %1")).arg(dir.absolutePath()));
214 return "";
215 }
216 }
217
218 if (fileInfo.exists() && !fileInfo.isWritable()) {
219 QMessageBox::critical(nullptr, QObject::tr("Error"),
220 QString(QObject::tr("Can't write to configuration file %1")).arg(filename));
221
222 return "";
223 }
224 return filename;
225}
226
227void setTheme(const QString& theme) {
228 pSettings->environment().setTheme(theme);
229 ThemeManager themeManager;
230 PAppTheme appTheme = themeManager.theme(theme);
231 if (appTheme && !appTheme->defaultColorScheme().isEmpty()) {
232 pSettings->editor().setColorScheme(appTheme->defaultColorScheme());
233 pSettings->editor().save();
234 }
235 if (appTheme && !appTheme->defaultIconSet().isEmpty()) {
236 pSettings->environment().setIconSet(appTheme->defaultIconSet());
237 }
238 pSettings->environment().save();
239
240}
241
242int main(int argc, char *argv[])
243{
244 //QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
245//#ifdef Q_OS_WIN
246// QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
247// QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
248// qputenv("QT_SCALE_FACTOR_ROUNDING_POLICY","PassThrough");
249// qputenv("QT_DEVICE_PIXEL_RATIO ","auto");
250// qputenv("QT_AUTO_SCREEN_SCALE_FACTOR","false");
251//#endif
252 QApplication app(argc, argv);
253 QFile tempFile(QDir::tempPath()+QDir::separator()+"RedPandaDevCppStartUp.lock");
254 {
255 bool firstRun;
256 QString settingFilename = getSettingFilename(QString(), firstRun);
257 bool openInSingleInstance = false;
258 if (!settingFilename.isEmpty() && !firstRun) {
259 QSettings envSetting(settingFilename,QSettings::IniFormat);
260 envSetting.beginGroup(SETTING_ENVIRONMENT);
261 openInSingleInstance = envSetting.value("open_files_in_single_instance",false).toBool();
262 } else if (!settingFilename.isEmpty() && firstRun)
263 openInSingleInstance = false;
264 if (openInSingleInstance) {
265 int openCount = 0;
266 while (true) {
267 if (tempFile.open(QFile::NewOnly))
268 break;
269 QThread::msleep(100);
270 openCount++;
271 if (openCount>100)
272 break;
273 }
274
275 if (app.arguments().length()>=2 && openCount<100) {
276#ifdef Q_OS_WIN
277 if (sendFilesToInstance()) {
278 tempFile.remove();
279 return 0;
280 }
281#endif
282 }
283 }
284 }
285 //Translation must be loaded first
286 QTranslator trans,transQt;
287 bool firstRun;
288 QString settingFilename = getSettingFilename(QString(), firstRun);
289 if (!isGreenEdition()) {
290 QDir::setCurrent(QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation)[0]);
291 }
292 if (settingFilename.isEmpty()) {
293 tempFile.remove();
294 return -1;
295 }
296 QString language;
297 {
298 QSettings languageSetting(settingFilename,QSettings::IniFormat);
299 languageSetting.beginGroup(SETTING_ENVIRONMENT);
300 language = languageSetting.value("language",QLocale::system().name()).toString();
301
302 if (trans.load("RedPandaIDE_"+language,":/i18n/")) {
303 app.installTranslator(&trans);
304 }
305 if (transQt.load("qt_"+language,":/translations")) {
306 app.installTranslator(&transQt);
307 }
308 }
309 qRegisterMetaType<PCompileIssue>("PCompileIssue");
310 qRegisterMetaType<PCompileIssue>("PCompileIssue&");
311 qRegisterMetaType<QVector<int>>("QVector<int>");
312 qRegisterMetaType<QHash<int,QString>>("QHash<int,QString>");
313
314 initParser();
315
316 try {
317
318 SystemConsts systemConsts;
319 pSystemConsts = &systemConsts;
320 pCharsetInfoManager = new CharsetInfoManager(language);
321 auto charsetInfoManager = std::unique_ptr<CharsetInfoManager>(pCharsetInfoManager);
322 //load settings
323 pSettings = new Settings(settingFilename);
324 if (firstRun) {
325 pSettings->compilerSets().findSets();
326 pSettings->compilerSets().saveSets();
327 }
328 pSettings->load();
329 if (firstRun) {
330 //set theme
331 ChooseThemeDialog themeDialog;
332 themeDialog.exec();
333 switch (themeDialog.theme()) {
334 case ChooseThemeDialog::Theme::Dark:
335 setTheme("dark");
336 break;
337 default:
338 setTheme("default");
339 }
340
341 pSettings->editor().setDefaultFileCpp(themeDialog.language()==ChooseThemeDialog::Language::CPlusPlus);
342 pSettings->editor().save();
343
344 //auto detect git in path
345 pSettings->vcs().detectGitInPath();
346 }
347 auto settings = std::unique_ptr<Settings>(pSettings);
348 //Color scheme settings must be loaded after translation
349 pColorManager = new ColorManager();
350 pIconsManager = new IconsManager();
351 pAutolinkManager = new AutolinkManager();
352 try {
353 pAutolinkManager->load();
354 } catch (FileError e) {
355 QMessageBox::critical(nullptr,
356 QObject::tr("Can't load autolink settings"),
357 e.reason(),
358 QMessageBox::Ok);
359 }
360
361 MainWindow mainWindow;
362 pMainWindow = &mainWindow;
363#if QT_VERSION_MAJOR==5 && QT_VERSION_MINOR < 15
364 setScreenDPI(qApp->primaryScreen()->logicalDotsPerInch());
365#else
366 if (mainWindow.screen())
367 setScreenDPI(mainWindow.screen()->logicalDotsPerInch());
368#endif
369 mainWindow.show();
370 if (app.arguments().count()>1) {
371 QStringList filesToOpen = app.arguments();
372 filesToOpen.pop_front();
373 pMainWindow->openFiles(filesToOpen);
374 } else {
375 if (pSettings->editor().autoLoadLastFiles())
376 pMainWindow->loadLastOpens();
377 if (pMainWindow->editorList()->pageCount()==0) {
378 pMainWindow->newEditor();
379 }
380 }
381
382 //reset default open folder
383 QDir::setCurrent(pSettings->environment().defaultOpenFolder());
384
385 pMainWindow->setFilesViewRoot(pSettings->environment().currentFolder());
386
387#ifdef Q_OS_WIN
388 WindowLogoutEventFilter filter;
389 app.installNativeEventFilter(&filter);
390#endif
391 if (tempFile.isOpen()) {
392 tempFile.close();
393 tempFile.remove();
394 }
395
396 int retCode = app.exec();
397 QString configDir = pSettings->dirs().config();
398 // save settings
399 // settings->compilerSets().saveSets();
400 if (mainWindow.shouldRemoveAllSettings()) {
401 settings.release();
402 delete pSettings;
403 QDir dir(configDir);
404 dir.removeRecursively();
405 }
406 return retCode;
407 } catch (BaseError e) {
408 tempFile.remove();
409 QMessageBox::critical(nullptr,QApplication::tr("Error"),e.reason());
410 return -1;
411 }
412}
413