diff --git a/win-linux/defaults.pri b/win-linux/defaults.pri index f1dec351c..4e5aa89e7 100644 --- a/win-linux/defaults.pri +++ b/win-linux/defaults.pri @@ -244,7 +244,6 @@ core_windows { # RC_ICONS += ./res/icons/desktop_icons.ico HEADERS += $$PWD/src/windows/platform_win/cwindowplatform.h \ - $$PWD/src/windows/platform_win/csnap.h \ $$PWD/src/windows/platform_win/caption.h \ $$PWD/src/platform_win/singleapplication.h \ $$PWD/src/platform_win/filechooser.h \ @@ -253,7 +252,6 @@ core_windows { $$PWD/src/platform_win/resource.h SOURCES += $$PWD/src/windows/platform_win/cwindowplatform.cpp \ - $$PWD/src/windows/platform_win/csnap.cpp \ $$PWD/src/platform_win/singleapplication.cpp \ $$PWD/src/platform_win/filechooser.cpp \ $$PWD/src/platform_win/printdialog.cpp \ diff --git a/win-linux/extras/update-daemon/src/classes/csocket.h b/win-linux/extras/update-daemon/src/classes/csocket.h index 151a4901c..2a1f5e490 100644 --- a/win-linux/extras/update-daemon/src/classes/csocket.h +++ b/win-linux/extras/update-daemon/src/classes/csocket.h @@ -70,7 +70,8 @@ enum MsgCommands { MSG_OtherError, MSG_RequestContentLenght, MSG_UnzipProgress, - MSG_SetLanguage + MSG_SetLanguage, + MSG_StartReplacingService }; class CSocket diff --git a/win-linux/extras/update-daemon/src/classes/csvcmanager.cpp b/win-linux/extras/update-daemon/src/classes/csvcmanager.cpp index 0d4cfb0ce..9af304b84 100644 --- a/win-linux/extras/update-daemon/src/classes/csvcmanager.cpp +++ b/win-linux/extras/update-daemon/src/classes/csvcmanager.cpp @@ -68,6 +68,7 @@ # define APP_LAUNCH_NAME "/DesktopEditors" # define APP_HELPER "/editors_helper" # define DAEMON_NAME "/updatesvc" +# define DAEMON_NAME_OLD "/~updatesvc" # define SUBFOLDER "/desktopeditors" # define ARCHIVE_EXT _T(".tar.xz") # define ARCHIVE_PATTERN _T("*.tar.xz") @@ -274,6 +275,12 @@ void CSvcManager::init() __UNLOCK break; + case MSG_StartReplacingService: + __GLOBAL_LOCK + startReplacingService(params[2] == _T("true")); + __UNLOCK + break; + case MSG_ClearTempFiles: clearTempFiles(params[1], params[2]); break; @@ -629,3 +636,73 @@ void CSvcManager::startReplacingFiles(const tstring &packageType, const bool res restartService(); #endif } + +void CSvcManager::startReplacingService(const bool restartAfterUpdate) +{ + tstring appPath = NS_File::appPath(); + tstring updPath = NS_File::parentPath(appPath) + UPDATE_PATH; + tstring updSubPath = NS_File::fileExists(updPath + SUBFOLDER + APP_LAUNCH_NAME) ? updPath + SUBFOLDER : updPath; + if (!NS_File::dirExists(updPath)) { + NS_Logger::WriteLog(_TR("Update cancelled. Can't find folder:") + _T(" ") + updPath, true); + return; + } + +#ifdef _WIN32 +# ifndef DONT_VERIFY_SIGNATURE + // Verify the signature of executable files + if (!NS_File::verifyEmbeddedSignature(updSubPath + DAEMON_NAME)) { + NS_Logger::WriteLog(_TR("Update cancelled. The file signature is missing:") + _T(" ") + updSubPath + DAEMON_NAME, true); + return; + } +# endif +#endif + + // Wait until the main app closes + { +#ifdef _WIN32 + tstring apps[] = {APP_LAUNCH_NAME2, APP_HELPER}; +#else + tstring apps[] = {APP_LAUNCH_NAME, APP_HELPER}; +#endif + for (int i = 0; i < sizeof(apps) / sizeof(apps[0]); i++) { + int retries = 10; + tstring app(apps[i]); + app = app.substr(1); + while (NS_File::isProcessRunning(app) && retries-- > 0) + sleep(500); + + if (NS_File::isProcessRunning(app)) { + NS_Logger::WriteLog(_TR("Update cancelled. The program is not closed:") + _T(" ") + app, true); + return; + } + } + } + + // Rename updatesvc.exe to ~updatesvc.exe + if (NS_File::fileExists(appPath + DAEMON_NAME) && !NS_File::replaceFile(appPath + DAEMON_NAME, appPath + DAEMON_NAME_OLD)) { + NS_Logger::WriteLog(_TR("Update cancelled. Can't rename updatesvc.exe to ~updatesvc.exe:") + _T(" ") + NS_Utils::GetLastErrorAsString(), true); + return; + } + + // Move updatesvc.exe to app path + if (!NS_File::replaceFile(updSubPath + DAEMON_NAME, appPath + DAEMON_NAME)) { + NS_Logger::WriteLog(_TR("Update cancelled. Can't replace file updatesvc.exe to app path:") + _T(" ") + NS_Utils::GetLastErrorAsString(), true); + if (NS_File::fileExists(appPath + DAEMON_NAME_OLD) && !NS_File::replaceFile(appPath + DAEMON_NAME_OLD, appPath + DAEMON_NAME)) + NS_Logger::WriteLog(_TR("Can't restore file updatesvc.exe!"), true); + return; + } + + // Restart program + if (restartAfterUpdate) { + if (!NS_File::runProcess(appPath + APP_LAUNCH_NAME, _T(""))) + NS_Logger::WriteLog(_TR("An error occurred while restarting the program!"), true); + } + + // Remove Update dir + NS_File::removeDirRecursively(updPath); + + // Restart service +#ifdef _WIN32 + restartService(); +#endif +} diff --git a/win-linux/extras/update-daemon/src/classes/csvcmanager.h b/win-linux/extras/update-daemon/src/classes/csvcmanager.h index c2c34e01d..7f718695c 100644 --- a/win-linux/extras/update-daemon/src/classes/csvcmanager.h +++ b/win-linux/extras/update-daemon/src/classes/csvcmanager.h @@ -65,6 +65,7 @@ private: void unzipIfNeeded(const tstring &filePath, const tstring &newVersion); void clearTempFiles(const tstring &prefix, const tstring &except = tstring()); void startReplacingFiles(const tstring &packageType, const bool restartAfterUpdate); + void startReplacingService(const bool restartAfterUpdate); FnVoidVoid m_quit_callback = nullptr; tstring m_newVersion; diff --git a/win-linux/extras/update-daemon/src/platform_linux/main.cpp b/win-linux/extras/update-daemon/src/platform_linux/main.cpp index d2e4a2e8b..e764bbad4 100644 --- a/win-linux/extras/update-daemon/src/platform_linux/main.cpp +++ b/win-linux/extras/update-daemon/src/platform_linux/main.cpp @@ -35,12 +35,16 @@ #include "classes/platform_linux/ctimer.h" #include "classes/csvcmanager.h" #include "classes/translator.h" +#include "version.h" #include "../../src/defines.h" #include "../../src/prop/defines_p.h" #include #include #include +#define DECL_VERSION __attribute__((section(".version_info"), unused)) + +volatile static const char DECL_VERSION version[] = VER_STRING; void strToNum(const char *str, int &num) { @@ -52,8 +56,6 @@ void strToNum(const char *str, int &num) int main(int argc, char *argv[]) { - NS_File::setAppPath(argv[0]); - if (argc > 1) { if (strcmp(argv[1], "--run-as-app") == 0) { std::locale::global(std::locale("")); @@ -75,11 +77,9 @@ int main(int argc, char *argv[]) strToNum((const char*)buff, pid); }); - // Checking for the completion of the main application: - // updatevc needs to be terminated when the main application is using a socket - // with the same address in the SingleApplication implementation and has been terminated incorrectly. + // Termination on crash of the main application CTimer tmr; - tmr.start(5000, [&app, &pid]() { + tmr.start(30000, [&app, &pid]() { if (pid != -1 && kill(pid, 0) != 0) app.exit(0); }); diff --git a/win-linux/extras/update-daemon/src/platform_linux/utils.cpp b/win-linux/extras/update-daemon/src/platform_linux/utils.cpp index 5cd54db78..90c83cff1 100644 --- a/win-linux/extras/update-daemon/src/platform_linux/utils.cpp +++ b/win-linux/extras/update-daemon/src/platform_linux/utils.cpp @@ -175,13 +175,6 @@ namespace NS_Utils namespace NS_File { - string app_path; - - void setAppPath(const std::string &path) - { - app_path = parentPath(path); - } - bool GetFilesList(const string &path, list *lst, string &error, bool ignore_locked, bool folders_only) { DIR *dir = opendir(path.c_str()); @@ -451,7 +444,9 @@ namespace NS_File string appPath() { - return app_path; + char path[PATH_MAX] = {0}; + ssize_t count = readlink("/proc/self/exe", path, PATH_MAX); + return (count > 0) ? parentPath(string(path, count)) : ""; } // string getFileHash(const string &fileName) diff --git a/win-linux/extras/update-daemon/src/platform_linux/utils.h b/win-linux/extras/update-daemon/src/platform_linux/utils.h index 9635c9fba..23689629b 100644 --- a/win-linux/extras/update-daemon/src/platform_linux/utils.h +++ b/win-linux/extras/update-daemon/src/platform_linux/utils.h @@ -58,7 +58,6 @@ string GetAppLanguage(); namespace NS_File { -void setAppPath(const string &path); bool GetFilesList(const string &path, list *lst, string &error, bool ignore_locked = false, bool folders_only = false); bool readFile(const string &filePath, list &linesList); bool writeToFile(const string &filePath, list &linesList); diff --git a/win-linux/res/styles/editor.qss b/win-linux/res/styles/editor.qss index 8acebfda9..2400f595d 100644 --- a/win-linux/res/styles/editor.qss +++ b/win-linux/res/styles/editor.qss @@ -3,17 +3,20 @@ #box-title-tools QLabel {font-family: "Arial", "Helvetica", "Helvetica Neue", sans-serif;} #labelTitle {color: #444; font-weight: normal;} #iconuser {color: %1; background: #d9ffffff; font-size: 10px;} +QPushButton[act=tool][hovered=true], QPushButton[act=tool]:hover {background-color: rgba(0,0,0,20%);} QPushButton#toolButtonClose:hover {background-color: #d42b2b;} QPushButton#toolButtonClose:pressed {background-color: #d75050;} /* pretty */ +#mainPanel[window=pretty] QPushButton[act=tool][unix=false][hovered=true], #mainPanel[window=pretty] QPushButton[act=tool][unix=false]:hover {background-color: rgba(255,255,255,20%);} #mainPanel[window=pretty] QPushButton#toolButtonMinimize {image: url(:/minimize_light.svg);} #mainPanel[window=pretty] QPushButton#toolButtonClose {image: url(:/close_light.svg);} #mainPanel[window=pretty] QPushButton#toolButtonClose[unix=false]:hover {background-color: #d42b2b;} #mainPanel[window=pretty] QPushButton#toolButtonMaximize {image: url(:/restore_light.svg);} #mainPanel[window=pretty] QPushButton#toolButtonMaximize[class=min] {image: url(:/maximize_light.svg);} +#mainPanel[window=pretty] QPushButton#toolButtonMaximize[unix=false][pressed=true] {background-color: rgba(255,255,255,20%);} #mainPanel[window=pretty] #labelTitle {color: #fff; font-size: 12px;} /* dark style */ diff --git a/win-linux/res/styles/styles.qss b/win-linux/res/styles/styles.qss index 2a1b12334..5da6d1210 100644 --- a/win-linux/res/styles/styles.qss +++ b/win-linux/res/styles/styles.qss @@ -5,7 +5,9 @@ QPushButton {/*background-color:#d9d9d9;*/ padding:0 20px; font-weight: normal; height: 22px; font-size: 12px;} QPushButton[act=tool] {/*background-origin: content;*/ border: none; margin: 0; padding: 0; border-radius:0;} +QPushButton[act=tool][hovered=true], QPushButton[act=tool]:hover {background-color:#cecece;} +QPushButton[act=tool][pressed=true], QPushButton[act=tool]:pressed {background-color:#b7b7b7;} QPushButton#toolButtonMaximize, @@ -78,9 +80,11 @@ QPushButton#toolButtonDownload {border-left: 0px; border-right: 1px solid #dfdfd #mainPanel[uitheme=theme-dark] QPushButton#toolButtonMinimize:hover, #mainPanel[uitheme=theme-dark] QPushButton#toolButtonMaximize:hover, +#mainPanel[uitheme=theme-dark] QPushButton#toolButtonMaximize[hovered=true], #mainPanel[uitheme=theme-dark] QPushButton#toolButtonDownload:hover {background-color: #555;} #mainPanel[uitheme=theme-dark] QPushButton#toolButtonMinimize:pressed, #mainPanel[uitheme=theme-dark] QPushButton#toolButtonMaximize:pressed, +#mainPanel[uitheme=theme-dark] QPushButton#toolButtonMaximize[pressed=true], #mainPanel[uitheme=theme-dark] QPushButton#toolButtonDownload:pressed {background-color: #606060;} /* Contrast-Dark*/ @@ -98,6 +102,7 @@ QPushButton#toolButtonDownload {border-left: 0px; border-right: 1px solid #dfdfd #mainPanel[uitheme=theme-contrast-dark] QPushButton#toolButtonMinimize:hover, #mainPanel[uitheme=theme-contrast-dark] QPushButton#toolButtonMaximize:hover, +#mainPanel[uitheme=theme-contrast-dark] QPushButton#toolButtonMaximize[hovered=true], #mainPanel[uitheme=theme-contrast-dark] QPushButton#toolButtonDownload:hover {background-color: #555;} /***********************************/ diff --git a/win-linux/src/cupdatemanager.cpp b/win-linux/src/cupdatemanager.cpp index 9bc1981f7..e26de5639 100644 --- a/win-linux/src/cupdatemanager.cpp +++ b/win-linux/src/cupdatemanager.cpp @@ -51,6 +51,9 @@ #else # include # include +# include +# include +# include # include "components/cmessage.h" # include "platform_linux/updatedialog.h" # define DAEMON_NAME "/updatesvc" @@ -65,6 +68,7 @@ #define CHECK_ON_STARTUP_MS 9000 #define CMD_ARGUMENT_UPDATES_CHANNEL L"--updates-appcast-channel" #define CMD_ARGUMENT_UPDATES_INTERVAL L"--updates-interval" +#define SERVICE_NAME APP_TITLE " Update Service" #ifndef URL_APPCAST_UPDATES # define URL_APPCAST_UPDATES "" #endif @@ -210,19 +214,88 @@ auto runProcess(const tstring &fileName, const tstring &args, bool runAsAdmin = CloseHandle(shExInfo.hProcess); return true; } + return false; #else Q_UNUSED(runAsAdmin) - QStringList _args = QString::fromStdString(args).split(" "); - if (QProcess::startDetached(QString::fromStdString(fileName), _args)) - return true; + const QStringList args_list = args.empty() ? QStringList() : QString::fromStdString(args).split(" "); + char **_args = new char*[args_list.size() + 2]; + int i = 0; + _args[i++] = const_cast(fileName.c_str()); + for (const auto &arg : args_list) + _args[i++] = arg.toLocal8Bit().data(); + _args[i] = NULL; + pid_t pid; + posix_spawn_file_actions_t acts; + posix_spawn_file_actions_init(&acts); + posix_spawn_file_actions_addclosefrom_np(&acts, 0); + int res = posix_spawn(&pid, fileName.c_str(), &acts, NULL, _args, environ); + posix_spawn_file_actions_destroy(&acts); + delete[] _args; + return res == 0; #endif - return false; +} + +auto getFileVersion(const tstring &filePath)->QString +{ + QString ver; +#ifdef _WIN32 + DWORD handle, size = GetFileVersionInfoSize(filePath.c_str(), &handle); + if (size > 0) { + BYTE *data = new BYTE[size]; + if (GetFileVersionInfo(filePath.c_str(), handle, size, (LPVOID)data)) { + UINT len = 0; + VS_FIXEDFILEINFO *verInfo = NULL; + if (VerQueryValue((LPCVOID)data, L"\\", (LPVOID*)&verInfo, &len)) { + if (verInfo->dwSignature == 0xfeef04bd) { + ver = QString("%1.%2.%3.%4").arg(QString::number(HIWORD(verInfo->dwFileVersionMS)), + QString::number(LOWORD(verInfo->dwFileVersionMS)), + QString::number(HIWORD(verInfo->dwFileVersionLS)), + QString::number(LOWORD(verInfo->dwFileVersionLS))); + } + } + } + delete[] data; + } +#else + int fd = open(filePath.c_str(), O_RDONLY); + if (fd != -1) { + Elf64_Ehdr header; + if (read(fd, &header, sizeof(header)) == sizeof(header)) { + Elf64_Shdr section; + off_t ofset = header.e_shoff + header.e_shentsize * header.e_shstrndx; + if (lseek(fd, ofset, SEEK_SET) == ofset && read(fd, §ion, sizeof(section)) == sizeof(section)) { + char *shstrtab = new char[section.sh_size]; + if (lseek(fd, section.sh_offset, SEEK_SET) == (off_t)section.sh_offset && + read(fd, shstrtab, section.sh_size) == (ssize_t)section.sh_size) { + for (int i = 0; i < header.e_shnum; ++i) { + ofset = header.e_shoff + i * header.e_shentsize; + if (lseek(fd, ofset, SEEK_SET) == ofset && read(fd, §ion, sizeof(section)) == sizeof(section)) { + if (strcmp(".version_info", shstrtab + section.sh_name) == 0) { + if (lseek(fd, section.sh_offset, SEEK_SET) == (off_t)section.sh_offset) { + char *version = new char[section.sh_size]; + if (read(fd, version, section.sh_size) == (ssize_t)section.sh_size) + ver = QString(version); + delete[] version; + } + break; + } + } + } + } + delete[] shstrtab; + } + } + close(fd); + } +#endif + return ver; } struct CUpdateManager::PackageData { QString fileName, fileType, fileSize, + object, hash, version; wstring packageUrl, @@ -231,6 +304,7 @@ struct CUpdateManager::PackageData { fileName.clear(); fileType.clear(); fileSize.clear(); + object.clear(); hash.clear(); version.clear(); packageUrl.clear(); @@ -688,7 +762,8 @@ void CUpdateManager::handleAppClose() return; } #endif - if (!m_socket->sendMessage(MSG_StartReplacingFiles, IsPackage(ISS) ? _T("iss") : IsPackage(MSI) ? _T("msi") : + int cmd = (m_packageData->object == "app") ? MSG_StartReplacingFiles : MSG_StartReplacingService; + if (!m_socket->sendMessage(cmd, IsPackage(ISS) ? _T("iss") : IsPackage(MSI) ? _T("msi") : IsPackage(Portable) ? _T("portable") : _T("other"), m_restartAfterUpdate ? _T("true") : _T("false"))) { criticalMsg(nullptr, QObject::tr("An error occurred while start replacing files: Update Service not found!")); } @@ -748,19 +823,24 @@ void CUpdateManager::onLoadCheckFinished(const QString &filePath) QString version = root.value("version").toString(); QString curr_version = QString::fromLatin1(VER_FILEVERSION_STR); - - if (isVersionBHigherThanA(curr_version, version) && (version != ignoredVersion())) { - m_packageData->version = version; - // parse package - QJsonObject package = root.value("package").toObject(); + QString svc_version = root.value("serviceVersion").toString(); + QString curr_svc_version = getFileVersion(QStrToTStr(qApp->applicationDirPath()) + DAEMON_NAME); + QJsonObject package = root.value("package").toObject(); #ifdef _WIN32 # ifdef _WIN64 - QJsonObject win = package.value("win_64").toObject(); + QJsonObject win = package.value("win_64").toObject(); # else - QJsonObject win = package.value("win_32").toObject(); + QJsonObject win = package.value("win_32").toObject(); # endif - QJsonObject package_type = win.value("archive").toObject(); +#else + QJsonObject win = package.value("linux_64").toObject(); +#endif + if (isVersionBHigherThanA(curr_version, version) && (version != ignoredVersion())) { + m_packageData->object = "app"; + m_packageData->version = version; m_packageData->fileType = "archive"; + QJsonObject package_type = win.value("archive").toObject(); +#ifdef _WIN32 if (!IsPackage(Portable)) { const QString install_key = IsPackage(MSI) ? "msi" : "iss"; if (win.contains(install_key)) { @@ -775,10 +855,6 @@ void CUpdateManager::onLoadCheckFinished(const QString &filePath) } } } -#else - QJsonObject win = package.value("linux_64").toObject(); - QJsonObject package_type = win.value("archive").toObject(); - m_packageData->fileType = "archive"; #endif m_packageData->packageUrl = package_type.value("url").toString().toStdWString(); m_packageData->hash = package_type.value("md5").toString().toLower(); @@ -788,6 +864,20 @@ void CUpdateManager::onLoadCheckFinished(const QString &filePath) const QString lang = CLangater::getCurrentLangCode() == "ru-RU" ? "ru-RU" : "en-EN"; QJsonValue changelog = release_notes.value(lang); + clearTempFiles(isSavedPackageValid() ? m_savedPackageData->fileName : ""); + if (m_packageData->packageUrl.empty() || !m_socket->sendMessage(MSG_RequestContentLenght, WStrToTStr(m_packageData->packageUrl))) { + m_packageData->fileSize = "--"; + onCheckFinished(false, true, m_packageData->version, ""); + } + } else + if (isVersionBHigherThanA(curr_svc_version, svc_version)) { + m_packageData->object = "svc"; + m_packageData->version = svc_version; + m_packageData->fileType = "archive"; + QJsonObject package_type = win.value("serviceArchive").toObject(); + m_packageData->packageUrl = package_type.value("url").toString().toStdWString(); + m_packageData->hash = package_type.value("md5").toString().toLower(); + clearTempFiles(isSavedPackageValid() ? m_savedPackageData->fileName : ""); if (m_packageData->packageUrl.empty() || !m_socket->sendMessage(MSG_RequestContentLenght, WStrToTStr(m_packageData->packageUrl))) { m_packageData->fileSize = "--"; @@ -806,6 +896,11 @@ void CUpdateManager::onCheckFinished(bool error, bool updateExist, const QString { if ( !error) { if ( updateExist ) { + if (m_packageData->object == "svc") { + __UNLOCK + loadUpdates(); + return; + } switch (getUpdateMode()) { case UpdateMode::SILENT: __UNLOCK @@ -835,9 +930,12 @@ void CUpdateManager::onCheckFinished(bool error, bool updateExist, const QString } void CUpdateManager::showUpdateMessage(QWidget *parent) { + QString name = (m_packageData->object == "app") ? QString(WINDOW_NAME) : QString(SERVICE_NAME); + QString curr_version = (m_packageData->object == "app") ? QString(VER_FILEVERSION_STR) : + getFileVersion(QStrToTStr(qApp->applicationDirPath()) + DAEMON_NAME); int result = WinDlg::showDialog(parent, tr("Update is available"), - QString("%1\n%2: %3\n%4: %5\n%6 (%7 MB)").arg(QString(WINDOW_NAME), tr("Current version"), - QString(VER_FILEVERSION_STR), tr("New version"), getVersion(), + QString("%1\n%2: %3\n%4: %5\n%6 (%7 MB)").arg(name, tr("Current version"), + curr_version, tr("New version"), getVersion(), tr("Would you like to download update now?"), m_packageData->fileSize), WinDlg::DlgBtns::mbSkipRemindDownload); __UNLOCK @@ -858,9 +956,12 @@ void CUpdateManager::showUpdateMessage(QWidget *parent) { void CUpdateManager::showStartInstallMessage(QWidget *parent) { + QString name = (m_packageData->object == "app") ? QString(WINDOW_NAME) : QString(SERVICE_NAME); + QString curr_version = (m_packageData->object == "app") ? QString(VER_FILEVERSION_STR) : + getFileVersion(QStrToTStr(qApp->applicationDirPath()) + DAEMON_NAME); int result = WinDlg::showDialog(parent, tr("Update is ready to install"), - QString("%1: %2\n%3: %4\n%5").arg(tr("Current version"), - QString(VER_FILEVERSION_STR), tr("New version"), getVersion(), + QString("%1\n%2: %3\n%4: %5\n%6").arg(name, tr("Current version"), + curr_version, tr("New version"), getVersion(), tr("To finish updating, restart the app")), WinDlg::DlgBtns::mbInslaterRestart); __UNLOCK diff --git a/win-linux/src/windows/cwindowbase.cpp b/win-linux/src/windows/cwindowbase.cpp index b139e5461..5b6e00131 100644 --- a/win-linux/src/windows/cwindowbase.cpp +++ b/win-linux/src/windows/cwindowbase.cpp @@ -37,9 +37,6 @@ #include "defines.h" #ifdef _WIN32 # include "windows/platform_win/caption.h" -# ifndef __OS_WIN_XP -# include "windows/platform_win/csnap.h" -# endif #endif #include #include @@ -181,12 +178,6 @@ QWidget* CWindowBase::createTopPanel(QWidget *parent) m_pTopButtons.push_back(btn); layoutBtns->addWidget(btn); } -#if defined (_WIN32) && !defined (__OS_WIN_XP) - if (Utils::getWinVersion() >= Utils::WinVer::Win11) { - CWin11Snap *snap = new CWin11Snap(m_pTopButtons[BtnType::Btn_Maximize]); - Q_UNUSED(snap) - } -#endif } return _boxTitleBtns; } diff --git a/win-linux/src/windows/platform_win/caption.h b/win-linux/src/windows/platform_win/caption.h index c7e6bbee3..75a7da891 100644 --- a/win-linux/src/windows/platform_win/caption.h +++ b/win-linux/src/windows/platform_win/caption.h @@ -36,8 +36,11 @@ #include #include #include +#include +#include #include #include +#include "utils.h" class Caption: public QWidget @@ -45,25 +48,51 @@ class Caption: public QWidget public: Caption(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()): QWidget(parent, f) - {} + { + hwnd_root = ::GetAncestor((HWND)winId(), GA_ROOT); + snapLayoutAllowed = isArrangingAllowed(); + } private: + HWND hwnd_root; + bool snapLayoutAllowed = false; + + bool isArrangingAllowed() { + BOOL arranging = FALSE; + SystemParametersInfoA(SPI_GETWINARRANGING, 0, &arranging, 0); + return (arranging == TRUE); + } + + QPoint cursorPos() { + POINT pt; + ::GetCursorPos(&pt); + return mapFromGlobal(QPoint(pt.x, pt.y)); + } + + QPushButton* buttonAtPos(const QPoint &pos) { + QWidget *child = childAt(pos); + return child ? qobject_cast(child) : nullptr; + } + + QPushButton* buttonMaxUnderMouse() { + QPushButton *btn = buttonAtPos(cursorPos()); + return (btn && btn->objectName() == "toolButtonMaximize") ? btn : nullptr; + } + bool postMsg(DWORD cmd) { POINT pt; ::GetCursorPos(&pt); QPoint pos = mapFromGlobal(QPoint(int(pt.x), int(pt.y))); - QPushButton *pushButton = childAt(pos) ? qobject_cast(childAt(pos)) : nullptr; - if (!pushButton) { - HWND hWnd = ::GetAncestor((HWND)(window()->windowHandle()->winId()), GA_ROOT); + if (!buttonAtPos(pos)) { ::ReleaseCapture(); - ::PostMessage(hWnd, cmd, HTCAPTION, POINTTOPOINTS(pt)); + ::PostMessage(hwnd_root, cmd, HTCAPTION, POINTTOPOINTS(pt)); QCoreApplication::postEvent(parent(), new QEvent(QEvent::MouseButtonPress)); return true; } return false; } - bool nativeEvent(const QByteArray &eventType, void *message, long *result) + virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override { #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) MSG* msg = *reinterpret_cast(message); @@ -83,6 +112,54 @@ private: return true; break; } + case WM_NCLBUTTONDOWN: { + if (Utils::getWinVersion() < Utils::WinVer::Win11) + break; + if (QPushButton *btn = buttonMaxUnderMouse()) { + btn->setProperty("hovered", false); + btn->setProperty("pressed", true); + btn->style()->polish(btn); + btn->repaint(); + } + break; + } + case WM_TIMER: { + QPushButton *btn = buttonMaxUnderMouse(); + if (!btn) { + KillTimer(msg->hwnd, msg->wParam); + if (QPushButton *btn = findChild("toolButtonMaximize")) { + btn->setProperty("hovered", false); + btn->setProperty("pressed", false); + btn->style()->polish(btn); + } + } + break; + } + case WM_NCHITTEST: { + if (Utils::getWinVersion() < Utils::WinVer::Win11 || !snapLayoutAllowed) + break; + *result = 0; + if (QPushButton *btn = buttonMaxUnderMouse()) { + if (!btn->property("hovered").toBool()) { + btn->setProperty("hovered", true); + btn->style()->polish(btn); + SetTimer(msg->hwnd, 1, 200, NULL); + } + *result = HTMAXBUTTON; + } + return (*result != 0); + } + case WM_CAPTURECHANGED: { + if (Utils::getWinVersion() < Utils::WinVer::Win11) + break; + if (QPushButton *btn = buttonMaxUnderMouse()) + btn->click(); + break; + } + case WM_SETTINGCHANGE: { + snapLayoutAllowed = isArrangingAllowed(); + break; + } default: break; } diff --git a/win-linux/src/windows/platform_win/cwindowplatform.cpp b/win-linux/src/windows/platform_win/cwindowplatform.cpp index 3b7f528a1..5dec6f2ec 100644 --- a/win-linux/src/windows/platform_win/cwindowplatform.cpp +++ b/win-linux/src/windows/platform_win/cwindowplatform.cpp @@ -194,11 +194,13 @@ bool CWindowPlatform::nativeEvent(const QByteArray &eventType, void *message, lo { case WM_ACTIVATE: { #ifndef __OS_WIN_XP - MARGINS mrg; - mrg.cxLeftWidth = 4; - mrg.cxRightWidth = 4; - mrg.cyBottomHeight = 4; - mrg.cyTopHeight = 29; + MARGINS mrg = {4, 4, 29, 4}; + if (Utils::getWinVersion() > Utils::WinVer::Win10) { + mrg.cxLeftWidth = 1; + mrg.cxRightWidth = 0; + mrg.cyBottomHeight = 0; + mrg.cyTopHeight = 0; + } DwmExtendFrameIntoClientArea(m_hWnd, &mrg); #endif break; @@ -299,6 +301,10 @@ bool CWindowPlatform::nativeEvent(const QByteArray &eventType, void *message, lo } case WM_SETTINGCHANGE: { + if (msg->wParam == SPI_SETWINARRANGING) { + if (Utils::getWinVersion() > Utils::WinVer::Win10 && m_boxTitleBtns) + SendMessage((HWND)m_boxTitleBtns->winId(), WM_SETTINGCHANGE, 0, 0); + } else if (msg->wParam == SPI_SETWORKAREA) { static RECT oldWorkArea = {0,0,0,0}; RECT workArea; // Taskbar show/hide detection