diff --git a/.docker/desktop-apps.bake.Dockerfile b/.docker/desktop-apps.bake.Dockerfile
index 8ab80540c..1b9ef6901 100644
--- a/.docker/desktop-apps.bake.Dockerfile
+++ b/.docker/desktop-apps.bake.Dockerfile
@@ -31,6 +31,7 @@ FROM core-base AS desktop-linux
bison \
libnotify-dev \
libcups2-dev \
+ libwayland-dev \
libdbus-1-dev \
libxcb-util0-dev \
libxcb-xkb-dev \
@@ -102,7 +103,7 @@ FROM core-base AS desktop-linux
-DVCPKG_MANIFEST_FEATURES="desktop-editors" \
-DABOUT_PAGE_APP_NAME="${ABOUT_PAGE_APP_NAME}" \
/desktop-apps/win-linux/ && \
- cmake --build . && \
+ cmake --build . -- -j4 && \
cmake --install . && \
ccache --show-stats && \
cp -a desktopeditors /desktopeditors
diff --git a/common/loginpage/src/document-creation-grid.js b/common/loginpage/src/document-creation-grid.js
index a654f0ecf..d40ac57ba 100644
--- a/common/loginpage/src/document-creation-grid.js
+++ b/common/loginpage/src/document-creation-grid.js
@@ -88,6 +88,13 @@ window.DocumentCreationGrid = function (config = {}) {
`).join('')}
`;
+ // clear any previous render (e.g. a re-render with a new
+ // DocumentCreationGrid instance after the default save format
+ // changed) -- queried against the DOM rather than the local
+ // $el, since a fresh instance's own $el starts out empty
+ // regardless of what an earlier instance already rendered here
+ parentElement.find('.document-creation-grid').remove();
+
$parent = parentElement;
$el = $parent.append(_template).find('.document-creation-grid');
},
diff --git a/common/loginpage/src/locale.js b/common/loginpage/src/locale.js
index adb518edb..7e7bbf4b6 100644
--- a/common/loginpage/src/locale.js
+++ b/common/loginpage/src/locale.js
@@ -129,6 +129,10 @@ l10n.en = {
settOptLaunchMode: 'Open file',
settOptLaunchInTab: 'In its own tab',
settOptLaunchInWindow: 'In its own window',
+ settDefaultSaveFormat: 'Default save format',
+ settOptFormatOoxml: 'Compatibility (OOXML)',
+ settOptFormatOdf: 'Sovereign (ODF)',
+ settDefaultSaveFormatManaged: 'Your default save format is managed by your organization.',
settSpellcheckDetection: 'Spelling language detection',
settOptDisabled: 'Disabled',
settOptEnabled: 'Enabled',
diff --git a/common/loginpage/src/panelrecent.js b/common/loginpage/src/panelrecent.js
index ac3fe1c60..3aef7ac09 100755
--- a/common/loginpage/src/panelrecent.js
+++ b/common/loginpage/src/panelrecent.js
@@ -521,6 +521,76 @@
});
};
+ function _document_types(format) {
+ const isODF = format === 'ODF';
+ return [
+ {
+ id: 'word',
+ title: utils.Lang.newDoc,
+ langKey: 'newDoc',
+ formatLabel: {
+ value: isODF ? 'ODT' : 'DOCX',
+ gradientColorStart: '#4298C5',
+ gradientColorEnd: '#2D84B2',
+ bgColorWinXP: '#287ca9',
+ },
+ icon: '#docx-big',
+ },
+ {
+ id: 'cell',
+ title: utils.Lang.newXlsx,
+ langKey: 'newXlsx',
+ formatLabel: {
+ value: isODF ? 'ODS' : 'XLSX',
+ gradientColorStart: '#5BB514',
+ gradientColorEnd: '#318C2B',
+ bgColorWinXP: '#3aa133',
+ },
+ icon: '#xlsx-big',
+ },
+ {
+ id: 'slide',
+ title: utils.Lang.newPptx,
+ langKey: 'newPptx',
+ formatLabel: {
+ value: isODF ? 'ODP' : 'PPTX',
+ gradientColorStart: '#F4893A',
+ gradientColorEnd: '#DE7341',
+ bgColorWinXP: '#f36700',
+ },
+ icon: '#pptx-big',
+ },
+ {
+ // forms always stay DOCXF/PDF regardless of the
+ // ODF/OOXML default save format setting
+ id: 'form',
+ title: utils.Lang.newForm,
+ langKey: 'newForm',
+ formatLabel: {
+ value: 'PDF',
+ gradientColorStart: '#F36653',
+ gradientColorEnd: '#D2402D',
+ bgColorWinXP: '#e54d39',
+ },
+ icon: '#pdf-big',
+ }
+ ];
+ }
+
+ // (re)renders the "create new" tile grid for the given default save
+ // format ('ODF' or anything else, treated as OOXML). Safe to call
+ // repeatedly -- DocumentCreationGrid.render() clears its own previous
+ // output before appending.
+ function _render_document_grid(format) {
+ const docGrid = new DocumentCreationGrid({
+ documentTypes: _document_types(format),
+ onDocumentSelect: (docType) => {
+ window.sdk.command("create:new", docType);
+ }
+ });
+ docGrid.render(this.view.$panel.find("#area-document-creation-grid"));
+ }
+
function _init_ppmenu() {
if (ppmenu) {
Menu.closeAll();
@@ -657,6 +727,17 @@
}
this.appready = true;
+ } else if (/settings:init/.test(cmd)) {
+ // read directly rather than relying solely on the
+ // Settings panel's own defaultformat:changed relay,
+ // since that panel (and its settings:init parsing)
+ // isn't constructed until the user opens Settings --
+ // this fires at genuine startup regardless
+ try {
+ const settings = JSON.parse($('
').html(param).text());
+ if (settings.defaultsaveformat)
+ _render_document_grid.call(this, settings.defaultsaveformat.format);
+ } catch (e) { /* ignore malformed settings:init */ }
}
});
@@ -674,66 +755,15 @@
this.dndZone = new DnDFileZone();
this.dndZone.render(this.view.$panel.find("#area-dnd-file"));
- const docGrid = new DocumentCreationGrid({
- documentTypes: [
- {
- id: 'word',
- title: utils.Lang.newDoc,
- langKey: 'newDoc',
- formatLabel: {
- value: 'DOCX',
- gradientColorStart: '#4298C5',
- gradientColorEnd: '#2D84B2',
- bgColorWinXP: '#287ca9',
- },
- icon: '#docx-big',
- },
- {
- id: 'cell',
- title: utils.Lang.newXlsx,
- langKey: 'newXlsx',
- formatLabel: {
- value: 'XLSX',
- gradientColorStart: '#5BB514',
- gradientColorEnd: '#318C2B',
- bgColorWinXP: '#3aa133',
- },
- icon: '#xlsx-big',
- },
- {
- id: 'slide',
- title: utils.Lang.newPptx,
- langKey: 'newPptx',
- formatLabel: {
- value: 'PPTX',
- gradientColorStart: '#F4893A',
- gradientColorEnd: '#DE7341',
- bgColorWinXP: '#f36700',
- },
- icon: '#pptx-big',
- },
- {
- id: 'form',
- title: utils.Lang.newForm,
- langKey: 'newForm',
- formatLabel: {
- value: 'PDF',
- gradientColorStart: '#F36653',
- gradientColorEnd: '#D2402D',
- bgColorWinXP: '#e54d39',
- },
- icon: '#pdf-big',
- }
- ],
- onDocumentSelect: (docType) => {
- window.sdk.command("create:new", docType);
- }
- });
+ // renders with the OOXML labels first (matches the
+ // compiled-in Tier-1 default); corrected via settings:init
+ // above or defaultformat:changed below as soon as the
+ // actual resolved/chosen format is known
+ _render_document_grid.call(this, 'OOXML');
+ CommonEvents.on('defaultformat:changed', _render_document_grid.bind(this));
CommonEvents.on('lang:changed', _init_ppmenu.bind(this));
- docGrid.render(this.view.$panel.find("#area-document-creation-grid"));
-
$('#idx-recent-filter', this.view.$panel).on('input', _on_filter_recents.bind(this));
return this;
diff --git a/common/loginpage/src/panelsettings.js b/common/loginpage/src/panelsettings.js
index fc3d24a24..adeaee696 100644
--- a/common/loginpage/src/panelsettings.js
+++ b/common/loginpage/src/panelsettings.js
@@ -247,6 +247,18 @@
+
@@ -318,6 +330,7 @@
$optsUITheme,
$optsSpellcheckMode,
$optsLaunchMode,
+ $optsDefaultSaveFormat,
$optsAutoupdateMode;
let $chGpu,
$chUseAI;
@@ -456,6 +469,15 @@
$optsSpellcheckMode.selectpicker('refresh');
}
+ /* an enforced format is administrator policy: the combo is
+ disabled, so nothing is sent back for the native shell to store */
+ if ( $optsDefaultSaveFormat && !appSettings.defaultsaveformat.locked ) {
+ _new_settings.defaultsaveformat = $optsDefaultSaveFormat.val();
+ $optsDefaultSaveFormat.selectpicker('refresh');
+
+ CommonEvents.fire('defaultformat:changed', [_new_settings.defaultsaveformat]);
+ }
+
if ( $chGpu ) {
_new_settings.usegpu = $chGpu.prop("checked");
@@ -651,6 +673,20 @@
});
}
+ if ( !!appSettings.defaultsaveformat ) {
+ ($optsDefaultSaveFormat = ($('#opts-default-save-format', $panel).show().find('select')))
+ .val(appSettings.defaultsaveformat.format)
+ .prop('disabled', !!appSettings.defaultsaveformat.locked)
+ .selectpicker().on('change', e => {
+ $btnApply.isdisabled() && $btnApply.disable(false);
+ });
+
+ if ( appSettings.defaultsaveformat.locked )
+ $('#sett-save-format-managed', $panel).show();
+
+ CommonEvents.fire('defaultformat:changed', [appSettings.defaultsaveformat.format]);
+ }
+
if ( appSettings.spellcheckdetect !== undefined ) {
($optsSpellcheckMode = ($('#opts-spellcheck-mode', $panel).show().find('select')))
.val(appSettings.spellcheckdetect)
diff --git a/win-linux/CMakeLists.txt b/win-linux/CMakeLists.txt
index b256bacdf..3264a2348 100644
--- a/win-linux/CMakeLists.txt
+++ b/win-linux/CMakeLists.txt
@@ -21,7 +21,6 @@ set(BUILD_DESKTOP TRUE CACHE INTERNAL "tell common to prepare third party for de
include(${CORE_ROOT_DIR}/common.cmake)
-# 2. Find Qt Packages
if(UNIX)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS DBus)
add_definitions(-DLINUX -D_LINUX -D_WAYLAND)
@@ -115,6 +114,8 @@ if(NOT TARGET allthemesgen)
add_subdirectory( "${CORE_ROOT_DIR}/DesktopEditor/allthemesgen" allthemesgen )
endif()
+
+
# 3. Definitions & Global Config
add_definitions(-D__DONT_WRITE_IN_APP_TITLE)
add_definitions(-DAPP_ICON_PATH="./res/icons/desktopeditors-eo.ico")
@@ -279,6 +280,9 @@ elseif(UNIX AND NOT APPLE)
src/platform_linux/gtkprintdialog.cpp
src/platform_linux/gtkutils.cpp
src/platform_linux/linux_window_utils.cpp
+ src/platform_linux/x11backend.cpp
+ src/platform_linux/waylandbackend.cpp
+ src/platform_linux/platformbackendfactory.cpp
extras/update-daemon/src/classes/csocket.cpp
)
list(APPEND COMMON_HEADERS
@@ -294,11 +298,20 @@ elseif(UNIX AND NOT APPLE)
src/platform_linux/gtkprintdialog.h
src/platform_linux/gtkutils.h
src/platform_linux/linux_window_utils.h
+ src/platform_linux/iplatformbackend.h
+ src/platform_linux/x11backend.h
+ src/platform_linux/waylandbackend.h
extras/update-daemon/src/classes/csocket.h
)
find_package(PkgConfig REQUIRED)
- pkg_check_modules(GTK3 REQUIRED glib-2.0 gtk+-3.0 atk gtk+-unix-print-3.0 xcb xext libnotify)
+ pkg_check_modules(GTK3 REQUIRED glib-2.0 gtk+-3.0 atk gtk+-unix-print-3.0 libnotify)
+ pkg_check_modules(XCB_LIBS xcb xext)
+ pkg_check_modules(X11_LIBS x11 x11-xcb)
+ if(XCB_LIBS_FOUND AND X11_LIBS_FOUND)
+ add_definitions(-DHAVE_X11)
+ endif()
+ add_definitions(-DHAVE_WAYLAND)
# Custom command for glib-compile-resources
set(GRESOURCE_XML ${CMAKE_CURRENT_SOURCE_DIR}/res/gresource.xml)
@@ -310,7 +323,10 @@ elseif(UNIX AND NOT APPLE)
)
list(APPEND COMMON_SOURCES ${GRESOURCE_C})
- set(PLATFORM_LIBS ${GTK3_LIBRARIES} X11 X11-xcb cups)
+ set(PLATFORM_LIBS ${GTK3_LIBRARIES} cups)
+ if(XCB_LIBS_FOUND AND X11_LIBS_FOUND)
+ list(APPEND PLATFORM_LIBS ${X11_LIBS_LIBRARIES} ${XCB_LIBS_LIBRARIES})
+ endif()
endif()
# 7. Create Executable
@@ -362,8 +378,9 @@ target_link_libraries(DesktopEditors PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Svg
Qt${QT_VERSION_MAJOR}::PrintSupport
- Qt${QT_VERSION_MAJOR}::Multimedia
+ Qt${QT_VERSION_MAJOR}::Multimedia
Qt${QT_VERSION_MAJOR}::MultimediaWidgets
+ Qt${QT_VERSION_MAJOR}::OpenGLWidgets
${PLATFORM_LIBS}
${DBUS_LIBRARIES}
diff --git a/win-linux/res/icons/logo-dark-eo.svg b/win-linux/res/icons/logo-dark-eo.svg
index a0fac4c36..2d2195fe1 100644
--- a/win-linux/res/icons/logo-dark-eo.svg
+++ b/win-linux/res/icons/logo-dark-eo.svg
@@ -1,6 +1,6 @@
diff --git a/win-linux/res/icons/logo-light-eo.svg b/win-linux/res/icons/logo-light-eo.svg
index b9b4921a2..750a33ebe 100644
--- a/win-linux/res/icons/logo-light-eo.svg
+++ b/win-linux/res/icons/logo-light-eo.svg
@@ -1,6 +1,6 @@
-
+
-
+
Euro-Office
diff --git a/win-linux/res/icons/message_warn.svg b/win-linux/res/icons/message_warn.svg
index edfb2af43..e236e4e8e 100644
--- a/win-linux/res/icons/message_warn.svg
+++ b/win-linux/res/icons/message_warn.svg
@@ -36,7 +36,7 @@
inkscape:current-layer="svg4"
showguides="true" />
& v
}else
if ( check_param(arg, L"--new" ) ) {
open_opts.srctype = etNewFile;
- open_opts.format = arg.rfind(L"cell") != wstring::npos ? AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX :
- arg.rfind(L"slide") != wstring::npos ? AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX :
- // arg.rfind(L"draw") != wstring::npos ? AVS_OFFICESTUDIO_FILE_DRAW_VSDX :
- arg.rfind(L"form") != wstring::npos ? AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF :
- /*if ( line.rfind(L"word") != wstring::npos )*/ AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX;
+ open_opts.format = AscAppManager::newFileFormat(
+ arg.rfind(L"cell") != wstring::npos ? L"cell" :
+ arg.rfind(L"slide") != wstring::npos ? L"slide" :
+ // arg.rfind(L"draw") != wstring::npos ? L"draw" :
+ arg.rfind(L"form") != wstring::npos ? L"form" :
+ /*if ( line.rfind(L"word") != wstring::npos )*/ L"word");
open_opts.name = AscAppManager::newFileName(open_opts.format);
} else continue;
@@ -1044,7 +1045,7 @@ void CAscApplicationManagerWrapper::handleInputCmd(const std::vector& v
} else {
/* file doesn't exists */
open_opts.srctype = etNewFile;
- open_opts.format = open_opts.format = AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX;
+ open_opts.format = AscAppManager::newFileFormat(L"word");
open_opts.name = AscAppManager::newFileName(open_opts.format);
}
} else
@@ -1132,6 +1133,44 @@ void CAscApplicationManagerWrapper::handleDeeplinkActions(const std::vector"
+ "Compatibility (OOXML — .docx / .xlsx / .pptx)
"
+ "Best if you frequently exchange files with Microsoft Office users, or your "
+ "organization already standardizes on Microsoft formats.
"
+ "Open Standard (ODF — .odt / .ods / .odp)
"
+ "An open, ISO-standardized format built for long-term archival stability and "
+ "cross-editor portability — while still opening and saving cleanly to Microsoft "
+ "formats whenever you need to.
"
+ "You can change this anytime in Settings.");
+
+ int res = CMessage::showMessage(parent, msg, MsgType::MSG_BRAND, MsgBtns::mbOoxmlDefOdf);
+
+ /* closing the dialog without choosing still records that it was shown, so
+ * it never reappears, and resolution falls through to the system/compiled-in
+ * default as usual */
+ if ( res == MODAL_RESULT_ODF )
+ Utils::keepDefaultSaveFormat(SAVE_FORMAT_ODF);
+ else
+ if ( res == MODAL_RESULT_OOXML )
+ Utils::keepDefaultSaveFormat(SAVE_FORMAT_OOXML);
+ else {
+ GET_REGISTRY_USER(reg_user)
+ reg_user.setValue("FormatOnboardingShown", true);
+ }
+}
+
void CAscApplicationManagerWrapper::onDocumentReady(int uid)
{
#ifndef __OS_WIN_XP
@@ -1159,6 +1198,12 @@ void CAscApplicationManagerWrapper::onDocumentReady(int uid)
Association::instance().chekForAssociations(uid);
#endif
+ if (uid < 0) {
+ QTimer::singleShot(50, this, [=]() {
+ askForDefaultSaveFormat(mainWindow() ? mainWindow()->handle() : nullptr);
+ });
+ }
+
if (uid > -1) {
if (printData().printerCapabilitiesReady())
AscAppManager::sendCommandTo(GetViewById(uid), L"printer:config", printData().getPrinterCapabilitiesJson().toStdWString());
@@ -2012,6 +2057,14 @@ bool CAscApplicationManagerWrapper::applySettings(const wstring& wstrjson)
_reg_user.setValue("editorWindowMode", m_private->m_openEditorWindow);
}
+ /* an enforced format is administrator policy, so a value arriving from
+ * the settings panel is ignored rather than written to the user scope */
+ if ( objRoot.contains("defaultsaveformat") && !Utils::defaultSaveFormatEnforced() ) {
+ QString _format = objRoot["defaultsaveformat"].toString();
+ _reg_user.setValue("DefaultSaveFormat",
+ _format.compare(SAVE_FORMAT_ODF, Qt::CaseInsensitive) == 0 ? SAVE_FORMAT_ODF : SAVE_FORMAT_OOXML);
+ }
+
if ( objRoot.contains("usegpu") ) {
bool use_gpu = objRoot["usegpu"].toBool(true);
setUserSettings(L"disable-gpu", use_gpu ? L"0" : L"1");
@@ -2365,6 +2418,7 @@ QString CAscApplicationManagerWrapper::newFileName(int format)
switch ( format ) {
case AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTX:
case AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX: return tr("Document%1.docx").arg(++docx_count);
+ case AVS_OFFICESTUDIO_FILE_DOCUMENT_ODT: return tr("Document%1.docx").arg(++docx_count).replace("docx", "odt");
case AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF:
case AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF: {
QString docname = tr("Document%1.docx").arg(++pdf_count);
@@ -2372,21 +2426,35 @@ QString CAscApplicationManagerWrapper::newFileName(int format)
}
case AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLTX:
case AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX: return tr("Book%1.xlsx").arg(++xlsx_count);
+ case AVS_OFFICESTUDIO_FILE_SPREADSHEET_ODS: return tr("Book%1.xlsx").arg(++xlsx_count).replace("xlsx", "ods");
case AVS_OFFICESTUDIO_FILE_PRESENTATION_POTX:
case AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX: return tr("Presentation%1.pptx").arg(++pptx_count);
+ case AVS_OFFICESTUDIO_FILE_PRESENTATION_ODP: return tr("Presentation%1.pptx").arg(++pptx_count).replace("pptx", "odp");
default: return "Document.asc";
}
}
-QString CAscApplicationManagerWrapper::newFileName(const std::wstring& format)
+int CAscApplicationManagerWrapper::newFileFormat(const std::wstring& type)
{
- int _f = format == L"word" ? AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX :
- format == L"cell" ? AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX :
- format == L"form" ? AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF :
- // format == L"draw" ? AVS_OFFICESTUDIO_FILE_DRAW_VSDX :
- format == L"slide" ? AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX : AVS_OFFICESTUDIO_FILE_UNKNOWN;
+ /* forms are only defined for ooxml, so they keep their format regardless */
+ if ( type == L"form" )
+ return AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF;
- return newFileName(_f);
+ bool _prefer_odf = Utils::defaultSaveFormat() == SAVE_FORMAT_ODF;
+ if ( type == L"word" )
+ return _prefer_odf ? AVS_OFFICESTUDIO_FILE_DOCUMENT_ODT : AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX;
+ if ( type == L"cell" )
+ return _prefer_odf ? AVS_OFFICESTUDIO_FILE_SPREADSHEET_ODS : AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX;
+ // if ( type == L"draw" ) return AVS_OFFICESTUDIO_FILE_DRAW_VSDX;
+ if ( type == L"slide" )
+ return _prefer_odf ? AVS_OFFICESTUDIO_FILE_PRESENTATION_ODP : AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX;
+
+ return AVS_OFFICESTUDIO_FILE_UNKNOWN;
+}
+
+QString CAscApplicationManagerWrapper::newFileName(const std::wstring& format)
+{
+ return newFileName(newFileFormat(format));
}
wstring CAscApplicationManagerWrapper::userSettings(const wstring& name)
diff --git a/win-linux/src/cascapplicationmanagerwrapper.h b/win-linux/src/cascapplicationmanagerwrapper.h
index e32c8f310..990d32b9c 100644
--- a/win-linux/src/cascapplicationmanagerwrapper.h
+++ b/win-linux/src/cascapplicationmanagerwrapper.h
@@ -193,6 +193,7 @@ private Q_SLOTS:
static QCefView * createViewer(QWidget * parent, const QSize& size);
static QString newFileName(int format);
static QString newFileName(const std::wstring& format);
+ static int newFileFormat(const std::wstring& type);
static CThemes & themes();
static CPrintData& printData();
diff --git a/win-linux/src/cascapplicationmanagerwrapper_private.h b/win-linux/src/cascapplicationmanagerwrapper_private.h
index 07f6bd6bd..64490577d 100644
--- a/win-linux/src/cascapplicationmanagerwrapper_private.h
+++ b/win-linux/src/cascapplicationmanagerwrapper_private.h
@@ -46,6 +46,8 @@
#else
# include "platform_linux/linux_window_utils.h"
# include
+# include
+# include
# define APP_LAUNCH_NAME "/DesktopEditors"
# define RESTART_BATCH "/apprestart.sh"
#endif
@@ -116,7 +118,12 @@ class CAscApplicationManagerWrapper_Private
ts << "del \"%~f0\"&exit\n";
#else
ts << "#!/bin/bash\n";
- ts << "\"" << QString::fromStdWString(NSFile::GetProcessDirectory()) << APP_LAUNCH_NAME << "\" &\n";
+ ts << "sleep 1\n";
+ QString appDir = QString::fromStdWString(NSFile::GetProcessDirectory());
+ ts << "cd \"" << appDir << "\"\n";
+ ts << "LD_LIBRARY_PATH=\"$PWD:$PWD/converter:$LD_LIBRARY_PATH\"";
+ ts << " LD_PRELOAD=libcef.so";
+ ts << " \"" << appDir << APP_LAUNCH_NAME << "\" &\n";
ts << "rm -- \"$0\"\n";
#endif
if (!f.flush()) {
@@ -143,8 +150,17 @@ class CAscApplicationManagerWrapper_Private
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
#else
- if (!QProcess::startDetached("/bin/sh", QStringList{fileName}))
+ // Use fork/exec instead of QProcess::startDetached because
+ // this runs from a destructor after Qt's event loop has stopped,
+ // where QProcess may not work reliably.
+ pid_t pid = fork();
+ if (pid == 0) {
+ setsid();
+ execlp("/bin/sh", "/bin/sh", fileName.toLocal8Bit().constData(), (char *)nullptr);
+ _exit(1);
+ } else if (pid < 0) {
CLogger::log("An error occurred while restarting the app!");
+ }
#endif
}
@@ -351,11 +367,7 @@ class CAscApplicationManagerWrapper_Private
openDocument(opts);
}
} else {
- int _f = format == L"word" ? AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX :
- format == L"cell" ? AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX :
- format == L"form" ? AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF :
- // format == L"draw" ? AVS_OFFICESTUDIO_FILE_DRAW_VSDX :
- format == L"slide" ? AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX : AVS_OFFICESTUDIO_FILE_UNKNOWN;
+ int _f = m_appmanager.newFileFormat(format);
COpenOptions opts{m_appmanager.newFileName(_f), etNewFile};
opts.format = _f;
@@ -452,8 +464,20 @@ class CAscApplicationManagerWrapper_Private
do {
WId wid = (WId)hWnd;
#else
- std::vector winStack;
+ std::vector winStack;
LinuxWindowUtils::getWindowStack(winStack);
+ if (winStack.empty()) {
+ // Wayland fallback: use our tracked editors list
+ for (auto it = m_appmanager.m_vecEditors.rbegin(); it != m_appmanager.m_vecEditors.rend(); it++) {
+ CEditorWindow *editor = reinterpret_cast(*it);
+ if (editor && editor->editorType() == etype) {
+ rc = editor->normalGeometry();
+ rc.adjust(50, 50, 50, 50);
+ isMaximized = editor->windowState().testFlag(Qt::WindowMaximized);
+ return;
+ }
+ }
+ }
for (auto it = winStack.rbegin(); it != winStack.rend(); it++) {
WId wid = (WId)(*it);
#endif
@@ -470,8 +494,10 @@ class CAscApplicationManagerWrapper_Private
}
#ifdef _WIN32
} while ((hWnd = GetWindow(hWnd, GW_HWNDNEXT)) != nullptr);
-#endif
}
+#else
+ }
+#endif
}
GET_REGISTRY_USER(reg_user);
diff --git a/win-linux/src/components/cmenu.cpp b/win-linux/src/components/cmenu.cpp
index 9f9aa046e..9bc1f6379 100644
--- a/win-linux/src/components/cmenu.cpp
+++ b/win-linux/src/components/cmenu.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#ifdef __linux__
@@ -132,7 +133,22 @@ class CMenuWidget : public QWidget
Q_OBJECT
public:
explicit CMenuWidget(QWidget * parent = nullptr) :
- QWidget(parent, Qt::Tool | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint)
+ // Qt::Popup (not Qt::Tool) for Wayland: Qt defines
+ // Tool = Popup | Dialog, so "Qt::Tool | Qt::Popup" is a no-op --
+ // Popup's bit was already set, the resolved window type is still
+ // exactly Tool. Using Qt::Popup alone changes the actual resolved
+ // type, which is what Qt's Wayland QPA plugin needs to map this to
+ // the xdg_popup protocol (positioner-based, anchored to the parent)
+ // instead of a plain xdg_toplevel with set_parent() (which has no
+ // positioner, so the compositor defaults to centering it over its
+ // parent regardless of move() -- this widget was rendering dead
+ // center of the main window instead of at the click position).
+ // Dismiss-on-outside-click/Escape is already handled explicitly
+ // below (WindowDeactivate/MouseButtonPress/KeyRelease); Qt::Popup's
+ // own implicit pointer grab changes how those events are delivered
+ // (grabbed globally rather than only within this widget), so watch
+ // for that logic if dismiss behavior regresses.
+ QWidget(parent, Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint)
{
m_dpiRatio = CScalingWrapper::parentScalingFactor(topLevelWidget());
if (isCompositingEnabled()) {
diff --git a/win-linux/src/components/cmessage.cpp b/win-linux/src/components/cmessage.cpp
index 3a4e5ad6b..220ee0d19 100644
--- a/win-linux/src/components/cmessage.cpp
+++ b/win-linux/src/components/cmessage.cpp
@@ -80,6 +80,8 @@
#define BTN_TEXT_RESTART QObject::tr("Restart Now")
#define BTN_TEXT_SAVEANDINS QObject::tr("Save and Install Now")
#define BTN_TEXT_DOWNLOAD QObject::tr("Download update")
+#define BTN_TEXT_OOXML QObject::tr("Compatibility (OOXML)")
+#define BTN_TEXT_ODF QObject::tr("Open Standard (ODF)")
#define TEXT_CANCEL toCharPtr(BTN_TEXT_CANCEL)
#define TEXT_YES toCharPtr(BTN_TEXT_YES)
@@ -96,9 +98,11 @@
#define TEXT_RESTART toCharPtr(BTN_TEXT_RESTART)
#define TEXT_SAVEANDINS toCharPtr(BTN_TEXT_SAVEANDINS)
#define TEXT_DOWNLOAD toCharPtr(BTN_TEXT_DOWNLOAD)
+#define TEXT_OOXML toCharPtr(BTN_TEXT_OOXML)
+#define TEXT_ODF toCharPtr(BTN_TEXT_ODF)
-#define MSG_ICON_WIDTH 35
-#define MSG_ICON_HEIGHT 35
+#define MSG_ICON_WIDTH 44
+#define MSG_ICON_HEIGHT 44
#define DLG_PADDING 7
#define BTN_SPACING 5
@@ -150,6 +154,10 @@ class QtMsg : public QDialog
void setContent(const QString&);
void setCheckBox(const QString &chekBoxText, bool checkBoxState);
bool getCheckStatus();
+ // Applies every dpiRatio-dependent size/margin/stylesheet, once, from the
+ // parent-derived m_priv->dpiRatio. See the call site for why it must not
+ // be re-run off this dialog's own (Wayland-racing) devicePixelRatio.
+ void applyScaling();
QWidget *m_boxButtons = nullptr,
*m_centralWidget = nullptr;
@@ -163,11 +171,37 @@ class QtMsg : public QDialog
std::unique_ptr m_priv;
};
+namespace {
+// Returns a DPI ratio taken from a window whose devicePixelRatio has already
+// settled. A freshly-created top-level dialog must NOT use its own ratio: on
+// Wayland a brand-new surface reports the rounded default (e.g. 2.0) until the
+// compositor answers with the real fractional scale, so reading it here would
+// size the whole dialog for the wrong factor (and, with no later correction,
+// leave it that way). The parent window has been mapped long enough to carry
+// the correct value.
+double stableDpiRatioForDialog(QWidget * parent)
+{
+ QWidget * ref = parent ? parent->window() : nullptr;
+ if (!ref)
+ ref = QApplication::activeWindow();
+ if (!ref) {
+ const QWidgetList tops = QApplication::topLevelWidgets();
+ for (QWidget * w : tops) {
+ if (w->isWindow() && w->isVisible() && !w->windowFlags().testFlag(Qt::ToolTip)) {
+ ref = w;
+ break;
+ }
+ }
+ }
+ return ref ? Utils::getScreenDpiRatioByWidget(ref) : 1.0;
+}
+}
+
class QtMsg::QtMsgPrivateIntf {
public:
- explicit QtMsgPrivateIntf(QtMsg * parent)
- : m_mess(parent)
- , dpiRatio(Utils::getScreenDpiRatioByWidget(parent))
+ QtMsgPrivateIntf(QtMsg * dlg, QWidget * parentWindow)
+ : m_mess(dlg)
+ , dpiRatio(stableDpiRatioForDialog(parentWindow))
{}
auto addButton(QPushButton * b) -> void {
@@ -197,6 +231,11 @@ class QtMsg::QtMsgPrivateIntf {
double dpiRatio = 1;
QMetaObject::Connection focusConnection;
bool isWindowActive = false;
+ // Kept around so applyScaling() can re-set their margins later; every
+ // other dpiRatio-dependent widget it touches is already a QtMsg member.
+ QVBoxLayout * cLayout = nullptr;
+ QFormLayout * fLayout = nullptr;
+ QWidget * bodyWidget = nullptr;
};
int QtMsg::m_modalresult(MODAL_RESULT_CANCEL);
@@ -207,7 +246,7 @@ QtMsg::QtMsg(QWidget * p)
, m_message(new QLabel)
, m_content(new QLabel)
, m_typeIcon(new QLabel)
- , m_priv(new QtMsgPrivateIntf(this))
+ , m_priv(new QtMsgPrivateIntf(this, p))
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
//setWindowTitle(APP_TITLE);
@@ -220,45 +259,50 @@ QtMsg::QtMsg(QWidget * p)
m_centralWidget->setObjectName("messageBody");
m_centralWidget->setProperty("uitheme", QString::fromStdWString(GetCurrentTheme().originalId()));
- QVBoxLayout * _c_layout = new QVBoxLayout;
- QHBoxLayout * _h_layout2 = new QHBoxLayout;
- QHBoxLayout * _h_layout1 = new QHBoxLayout;
- _c_layout->addLayout(_h_layout2, 1);
- _c_layout->addLayout(_h_layout1, 0);
+ m_priv->cLayout = new QVBoxLayout;
- const int _body_margin = int(12 * m_priv->dpiRatio);
- _c_layout->setContentsMargins(_body_margin,_body_margin,_body_margin,_body_margin);
+ // bodyWidget groups the content row (icon + text) and the buttons row so
+ // they share the same left/right extents. The whole block is then centered
+ // within cLayout, giving symmetric margins and aligning the icon's left
+ // edge with the first button's left edge.
+ m_priv->bodyWidget = new QWidget;
+ QVBoxLayout * bodyLayout = new QVBoxLayout(m_priv->bodyWidget);
+ bodyLayout->setContentsMargins(0, 0, 0, 0);
+ bodyLayout->setSpacing(0);
+ QHBoxLayout * contentRow = new QHBoxLayout;
+ contentRow->setContentsMargins(0, 0, 0, 0);
m_typeIcon->setProperty("class", "msg-icon");
- m_typeIcon->setFixedSize(int(round(MSG_ICON_WIDTH*m_priv->dpiRatio - 0.25)),
- int(round(MSG_ICON_HEIGHT*m_priv->dpiRatio - 0.25)));
- _h_layout2->addWidget(m_typeIcon, 0, Qt::AlignTop);
+ contentRow->addWidget(m_typeIcon, 0, Qt::AlignTop);
// m_message->setWordWrap(true);
m_message->setProperty("class", "msg-report");
- m_message->setStyleSheet(QString("margin-bottom: %1px;").arg(int(8*m_priv->dpiRatio)));
- m_message->setTextFormat(Qt::PlainText);
+ m_message->setTextFormat(Qt::RichText);
+ m_message->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_content->setProperty("class", "msg-report");
- m_content->setStyleSheet(QString("margin-bottom: %1px;").arg(int(8*m_priv->dpiRatio)));
m_content->setTextFormat(Qt::RichText);
+ m_content->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_content->setOpenExternalLinks(true);
- QFormLayout * _f_layout = new QFormLayout;
- _f_layout->addWidget(m_message);
- _f_layout->addWidget(m_content);
- _f_layout->setSpacing(0);
- _f_layout->setContentsMargins(int(10*m_priv->dpiRatio),0,int(5*m_priv->dpiRatio),0);
- _h_layout2->addLayout(_f_layout, 1);
- _h_layout2->setContentsMargins(0,0,0,0);
+ m_priv->fLayout = new QFormLayout;
+ m_priv->fLayout->addWidget(m_message);
+ m_priv->fLayout->addWidget(m_content);
+ m_priv->fLayout->setSpacing(0);
+ contentRow->addLayout(m_priv->fLayout, 1);
+ bodyLayout->addLayout(contentRow, 1);
QPushButton * btn_ok = new QPushButton("&" + QObject::tr("OK"));
btn_ok->setAutoDefault(true);
m_boxButtons->setLayout(new QHBoxLayout);
m_boxButtons->layout()->addWidget(btn_ok);
- m_boxButtons->layout()->setContentsMargins(0,int(10*m_priv->dpiRatio),0,0);
- m_boxButtons->layout()->setSpacing(int(8*m_priv->dpiRatio));
- _h_layout1->addWidget(m_boxButtons, 0, Qt::AlignCenter);
+
+ QHBoxLayout * buttonsRow = new QHBoxLayout;
+ buttonsRow->setContentsMargins(0, 0, 0, 0);
+ buttonsRow->addWidget(m_boxButtons, 0, Qt::AlignCenter);
+ bodyLayout->addLayout(buttonsRow, 0);
+
+ m_priv->cLayout->addWidget(m_priv->bodyWidget, 0, Qt::AlignCenter);
m_priv->addButton(btn_ok);
@@ -269,16 +313,13 @@ QtMsg::QtMsg(QWidget * p)
}
);
- m_centralWidget->setLayout(_c_layout);
- m_centralWidget->setMinimumWidth(int(350*m_priv->dpiRatio));
+ m_centralWidget->setLayout(m_priv->cLayout);
m_centralWidget->move(0, 0);
- QString _styles(Utils::readStylesheets(":/styles/message.qss"));
- _styles.append(QString("QPushButton{min-width:%1px;}").arg(int(40*m_priv->dpiRatio)));
- m_centralWidget->setStyleSheet( _styles );
-
- QString zoom = QString::number(m_priv->dpiRatio) + "x";
- m_centralWidget->setProperty("scaling", zoom);
+ // Scale once, from the stable parent-window ratio (see
+ // stableDpiRatioForDialog). No watcher/re-derivation is needed because
+ // that ratio doesn't change under this dialog.
+ applyScaling();
m_priv->focusConnection = QObject::connect(qApp, &QApplication::focusChanged, this,
[&] (QWidget * from, QWidget *to){
@@ -294,6 +335,34 @@ QtMsg::QtMsg(QWidget * p)
});
}
+void QtMsg::applyScaling()
+{
+ const int _body_margin = int(12 * m_priv->dpiRatio);
+ m_priv->cLayout->setContentsMargins(_body_margin,_body_margin,_body_margin,_body_margin);
+
+ m_typeIcon->setFixedSize(int(round(MSG_ICON_WIDTH*m_priv->dpiRatio - 0.25)),
+ int(round(MSG_ICON_HEIGHT*m_priv->dpiRatio - 0.25)));
+
+ // Wider gap under the primary line, matching the GTK message dialog's
+ // spacing between its bold primary and regular secondary text.
+ m_message->setStyleSheet(QString("margin-bottom: %1px;").arg(int(12*m_priv->dpiRatio)));
+ m_content->setStyleSheet(QString("margin-bottom: %1px;").arg(int(8*m_priv->dpiRatio)));
+
+ m_priv->fLayout->setContentsMargins(int(10*m_priv->dpiRatio),0,int(5*m_priv->dpiRatio),0);
+
+ m_boxButtons->layout()->setContentsMargins(0,int(10*m_priv->dpiRatio),0,0);
+ m_boxButtons->layout()->setSpacing(int(8*m_priv->dpiRatio));
+
+ m_centralWidget->setMinimumWidth(m_priv->buttons.size() > 2 ? 400 : 350);
+
+ QString _styles(Utils::readStylesheets(":/styles/message.qss"));
+ _styles.append(QString("QPushButton{min-width:%1px;}").arg(int(40*m_priv->dpiRatio)));
+ m_centralWidget->setStyleSheet( _styles );
+
+ QString zoom = QString::number(m_priv->dpiRatio) + "x";
+ m_centralWidget->setProperty("scaling", zoom);
+}
+
QtMsg::~QtMsg()
{
QObject::disconnect(m_priv->focusConnection);
@@ -344,7 +413,9 @@ void QtMsg::setButtons(std::initializer_list btns)
{MODAL_RESULT_DOWNLOAD, BTN_TEXT_DOWNLOAD},
{MODAL_RESULT_INSTALL, BTN_TEXT_INSTALL},
{MODAL_RESULT_INSLATER, BTN_TEXT_INSLATER},
- {MODAL_RESULT_RESTART, BTN_TEXT_RESTART}
+ {MODAL_RESULT_RESTART, BTN_TEXT_RESTART},
+ {MODAL_RESULT_OOXML, BTN_TEXT_OOXML},
+ {MODAL_RESULT_ODF, BTN_TEXT_ODF}
};
m_boxButtons->layout()->addWidget(_btn);
@@ -356,7 +427,7 @@ void QtMsg::setButtons(std::initializer_list btns)
}
if (_btn_num > 2)
- m_centralWidget->setMinimumWidth(int(400*m_priv->dpiRatio));
+ m_centralWidget->setMinimumWidth(400);
}
void QtMsg::setButtons(MsgBtns btns)
@@ -376,6 +447,7 @@ void QtMsg::setButtons(MsgBtns btns)
case MsgBtns::mbSkipRemindInstall: setButtons({BTN_TEXT_SKIPVER, BTN_TEXT_REMIND, DEFAULT_BUTTON(BTN_TEXT_INSTALL)}); break;
case MsgBtns::mbSkipRemindSaveandinstall: setButtons({BTN_TEXT_SKIPVER, BTN_TEXT_REMIND, DEFAULT_BUTTON(BTN_TEXT_INSTALL)}); break;
case MsgBtns::mbSkipRemindDownload: setButtons({BTN_TEXT_SKIPVER, BTN_TEXT_REMIND, DEFAULT_BUTTON(BTN_TEXT_DOWNLOAD)}); break;
+ case MsgBtns::mbOoxmlDefOdf: setButtons({DEFAULT_BUTTON(BTN_TEXT_OOXML), BTN_TEXT_ODF}); break;
default: break;
}
}
@@ -414,13 +486,27 @@ int QtMsg::showMessage(QWidget *parent,
dlg.setLayoutDirection(Qt::RightToLeft);
#endif
}
- dlg.setText(QTextDocumentFragment::fromHtml(msg).toPlainText());
+ // Split the primary (first) line from any following secondary lines, like
+ // the GTK message dialog: the primary is bold/larger, the rest regular.
+ const QString plain = QTextDocumentFragment::fromHtml(msg).toPlainText();
+ QString primary = plain, secondary;
+ const int nl = plain.indexOf('\n');
+ if (nl != -1) {
+ primary = plain.left(nl);
+ secondary = plain.mid(nl + 1);
+ }
+ dlg.setText(primary);
QString content = opts.contentText;
if (!content.isEmpty() && !opts.linkText.isEmpty()) {
content.append("\n");
content.replace("\n", "
");
}
content.append(opts.linkText);
+ if (!secondary.isEmpty()) {
+ QString sec = secondary.toHtmlEscaped();
+ sec.replace("\n", "
");
+ content = content.isEmpty() ? sec : (sec + "
" + content);
+ }
if (!content.isEmpty())
dlg.setContent(content);
dlg.setIcon(msgType);
@@ -448,7 +534,12 @@ void QtMsg::setIcon(MsgType msgType)
void QtMsg::setText( const QString& t)
{
- m_message->setText(t);
+ // Primary line, styled like the GTK message dialog: bold and a little
+ // larger than the secondary text. showMessage() splits the primary line
+ // off and routes the rest to the (regular) content label.
+ const int px = int(13.5 * m_priv->dpiRatio + 0.5);
+ m_message->setText(QString("%2")
+ .arg(px).arg(t.toHtmlEscaped()));
}
void QtMsg::setContent( const QString& t)
@@ -598,6 +689,12 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
pButtons[1] = {IDNO, TEXT_REMIND};
pButtons[2] = {IDYES, TEXT_DOWNLOAD};
break;
+ case MsgBtns::mbOoxmlDefOdf:
+ cButtons = 2;
+ pButtons = new TASKDIALOG_BUTTON[cButtons];
+ pButtons[0] = {IDYES, TEXT_OOXML};
+ pButtons[1] = {IDNO, TEXT_ODF};
+ break;
default:
cButtons = 1;
pButtons = new TASKDIALOG_BUTTON[cButtons];
@@ -621,6 +718,7 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
case MsgBtns::mbSkipRemindInstall: nDefltBtn = IDYES; break;
case MsgBtns::mbSkipRemindSaveandinstall: nDefltBtn = IDYES; break;
case MsgBtns::mbSkipRemindDownload: nDefltBtn = IDYES; break;
+ case MsgBtns::mbOoxmlDefOdf: nDefltBtn = IDYES; break;
default: nDefltBtn = IDOK; break;
}
@@ -689,11 +787,13 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
(msgBtns == MsgBtns::mbActivateDefContinue) ? MODAL_RESULT_ACTIVATE :
(msgBtns == MsgBtns::mbSkipRemindInstall || msgBtns == MsgBtns::mbSkipRemindSaveandinstall) ? MODAL_RESULT_INSTALL :
(msgBtns == MsgBtns::mbSkipRemindDownload) ? MODAL_RESULT_DOWNLOAD :
+ (msgBtns == MsgBtns::mbOoxmlDefOdf) ? MODAL_RESULT_OOXML :
(msgBtns == MsgBtns::mbInslaterRestart) ? MODAL_RESULT_INSLATER : MODAL_RESULT_YES;
break;
case IDNO: result = (msgBtns == MsgBtns::mbActivateDefContinue) ? MODAL_RESULT_CONTINUE :
(msgBtns == MsgBtns::mbSkipRemindInstall || msgBtns == MsgBtns::mbSkipRemindSaveandinstall
|| msgBtns == MsgBtns::mbSkipRemindDownload) ? MODAL_RESULT_REMIND :
+ (msgBtns == MsgBtns::mbOoxmlDefOdf) ? MODAL_RESULT_ODF :
(msgBtns == MsgBtns::mbInslaterRestart) ? MODAL_RESULT_RESTART : MODAL_RESULT_NO;
break;
case IDOK: result = (msgBtns == MsgBtns::mbContinue) ? MODAL_RESULT_CONTINUE : MODAL_RESULT_OK;
@@ -857,6 +957,10 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
AddButton(TEXT_REMIND, GTK_RESPONSE_NO);
AddButton(TEXT_DOWNLOAD, GTK_RESPONSE_YES);
break;
+ case MsgBtns::mbOoxmlDefOdf:
+ AddButton(TEXT_OOXML, GTK_RESPONSE_YES);
+ AddButton(TEXT_ODF, GTK_RESPONSE_NO);
+ break;
default:
AddButton(TEXT_OK, GTK_RESPONSE_OK);
break;
@@ -877,6 +981,7 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
case MsgBtns::mbSkipRemindInstall: GrabFocus(GTK_RESPONSE_YES); break;
case MsgBtns::mbSkipRemindSaveandinstall: GrabFocus(GTK_RESPONSE_YES); break;
case MsgBtns::mbSkipRemindDownload: GrabFocus(GTK_RESPONSE_YES); break;
+ case MsgBtns::mbOoxmlDefOdf: GrabFocus(GTK_RESPONSE_YES); break;
default: GrabFocus(GTK_RESPONSE_OK); break;
}
@@ -887,11 +992,13 @@ int showMessage(QWidget *parent, const QString &msg, MsgType msgType, MsgBtns ms
(msgBtns == MsgBtns::mbActivateDefContinue) ? MODAL_RESULT_ACTIVATE :
(msgBtns == MsgBtns::mbSkipRemindInstall || msgBtns == MsgBtns::mbSkipRemindSaveandinstall) ? MODAL_RESULT_INSTALL :
(msgBtns == MsgBtns::mbSkipRemindDownload) ? MODAL_RESULT_DOWNLOAD :
+ (msgBtns == MsgBtns::mbOoxmlDefOdf) ? MODAL_RESULT_OOXML :
(msgBtns == MsgBtns::mbInslaterRestart) ? MODAL_RESULT_INSLATER : MODAL_RESULT_YES;
break;
case GTK_RESPONSE_NO: result = (msgBtns == MsgBtns::mbActivateDefContinue) ? MODAL_RESULT_CONTINUE :
(msgBtns == MsgBtns::mbSkipRemindInstall || msgBtns == MsgBtns::mbSkipRemindSaveandinstall
|| msgBtns == MsgBtns::mbSkipRemindDownload) ? MODAL_RESULT_REMIND :
+ (msgBtns == MsgBtns::mbOoxmlDefOdf) ? MODAL_RESULT_ODF :
(msgBtns == MsgBtns::mbInslaterRestart) ? MODAL_RESULT_RESTART : MODAL_RESULT_NO;
break;
case GTK_RESPONSE_OK: result = (msgBtns == MsgBtns::mbContinue) ? MODAL_RESULT_CONTINUE : MODAL_RESULT_OK;
@@ -931,8 +1038,12 @@ int CMessage::showMessage(QWidget *parent,
return WinMsg::showMessage(parent, msg, msgType, msgBtns, opts);
# endif
#else
- WindowHelper::CParentDisable oDisabler(parent);
- return GtkMsg::showMessage(parent, msg, msgType, msgBtns, opts);
+ // On Wayland, use Qt dialogs to avoid GTK falling back to Xwayland
+ // (which causes wrong scaling and misaligned click areas).
+ if (QGuiApplication::platformName() != "wayland") {
+ WindowHelper::CParentDisable oDisabler(parent);
+ return GtkMsg::showMessage(parent, msg, msgType, msgBtns, opts);
+ }
#endif
}
return QtMsg::showMessage(parent, msg, msgType, msgBtns, opts);
diff --git a/win-linux/src/components/cmessage.h b/win-linux/src/components/cmessage.h
index be8a0f0dd..49554fa89 100644
--- a/win-linux/src/components/cmessage.h
+++ b/win-linux/src/components/cmessage.h
@@ -44,7 +44,8 @@ enum class MsgBtns {
mbSkipRemindInstall,
mbSkipRemindSaveandinstall,
mbSkipRemindDownload,
- mbInslaterRestart
+ mbInslaterRestart,
+ mbOoxmlDefOdf
};
enum class MsgType {
MSG_INFO = 0,
@@ -67,7 +68,9 @@ enum MsgRes {
MODAL_RESULT_DOWNLOAD,
MODAL_RESULT_INSTALL,
MODAL_RESULT_INSLATER,
- MODAL_RESULT_RESTART
+ MODAL_RESULT_RESTART,
+ MODAL_RESULT_OOXML,
+ MODAL_RESULT_ODF
};
struct CMessageOpts {
diff --git a/win-linux/src/components/csvgpushbutton.cpp b/win-linux/src/components/csvgpushbutton.cpp
index dc754f10d..010bd4d7f 100644
--- a/win-linux/src/components/csvgpushbutton.cpp
+++ b/win-linux/src/components/csvgpushbutton.cpp
@@ -100,9 +100,13 @@ void CSVGPushButton::setFillDark(bool dark)
void CSVGPushButton::updateIcon()
{
if ( !m_svglayout.isEmpty() ) {
- QImage img(iconSize(), QImage::Format_ARGB32);
+ qreal dpr = devicePixelRatioF();
+ QSize physicalSize = iconSize() * dpr;
+ QImage img(physicalSize, QImage::Format_ARGB32);
+ img.setDevicePixelRatio(dpr);
img.fill(Qt::transparent);
QPixmap pixmap = QPixmap::fromImage(img, Qt::NoFormatConversion);
+ pixmap.setDevicePixelRatio(dpr);
QPainter painter(&pixmap);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
diff --git a/win-linux/src/components/ctabbar.cpp b/win-linux/src/components/ctabbar.cpp
index a88234468..0edecf85c 100644
--- a/win-linux/src/components/ctabbar.cpp
+++ b/win-linux/src/components/ctabbar.cpp
@@ -1283,7 +1283,12 @@ bool CTabBar::eventFilter(QObject *watched, QEvent *event)
QContextMenuEvent* cm_event = static_cast(event);
for (int i = 0; i < d->tabList.size(); i++) {
if (d->_tabRect(i).contains(cm_event->pos())) {
- QPoint pos = d->tabArea->mapToGlobal(cm_event->pos());
+ // mapToGlobal() is unreliable on Wayland (clients aren't
+ // told their absolute window position), which was
+ // rendering this menu at a bogus/centered location. The
+ // event's own global position is computed correctly by
+ // Qt's input pipeline when the event was generated.
+ QPoint pos = cm_event->globalPos();
SKIP_EVENTS_QUEUE([=]() {
emit tabMenuRequested(i, pos);
});
diff --git a/win-linux/src/components/ctabpanel.cpp b/win-linux/src/components/ctabpanel.cpp
index 35dfc43ee..8d304e413 100644
--- a/win-linux/src/components/ctabpanel.cpp
+++ b/win-linux/src/components/ctabpanel.cpp
@@ -4,6 +4,7 @@
#include "defines.h"
#include "cefview.h"
#include
+#include "cmessage.h"
using namespace NSEditorApi;
diff --git a/win-linux/src/defines.h b/win-linux/src/defines.h
index 04d40ed4a..62c67abde 100644
--- a/win-linux/src/defines.h
+++ b/win-linux/src/defines.h
@@ -58,6 +58,12 @@
#define APP_PROTOCOL "oo-office"
#define FILE_PREFIX "eurooffice_"
+/* default format new documents are saved in. resolved from the registry
+ * cascade (system scope, then user scope) by Utils::defaultSaveFormat() */
+#define SAVE_FORMAT_OOXML "OOXML"
+#define SAVE_FORMAT_ODF "ODF"
+#define APP_DEFAULT_SAVE_FORMAT SAVE_FORMAT_OOXML
+
#define URL_SITE "https://github.com/Euro-Office"
#define URL_SIGNUP "https://onlyoffice.com/registration.aspx?desktop=true"
diff --git a/win-linux/src/main.cpp b/win-linux/src/main.cpp
index 271f9e08c..32c8d2ade 100644
--- a/win-linux/src/main.cpp
+++ b/win-linux/src/main.cpp
@@ -40,10 +40,17 @@
#include "chelp.h"
#include "common/File.h"
#include
+#include
+#include
+#include
int main( int argc, char *argv[] )
{
+ bool isWayland = false;
+ int new_argc = argc;
+ char** new_argv = argv;
+ std::vector dynamic_argv;
#ifdef _WIN32
Core_SetProcessDpiAwareness();
Utils::setAppUserModelId();
@@ -53,9 +60,30 @@ int main( int argc, char *argv[] )
return 0;
}
#else
- qputenv("QT_QPA_PLATFORM", "xcb");
- qputenv("GDK_BACKEND", "x11");
- InputArgs::init(argc, argv);
+ dynamic_argv.assign(argv, argv + argc);
+ QByteArray platform = qgetenv("QT_QPA_PLATFORM");
+ if (platform.isEmpty()) {
+ QByteArray sessionType = qgetenv("XDG_SESSION_TYPE");
+ if (sessionType == "wayland") {
+ platform = "wayland";
+ } else {
+ platform = "xcb";
+ }
+ qputenv("QT_QPA_PLATFORM", platform);
+ }
+ isWayland = (platform == "wayland");
+
+ if (isWayland) {
+ qputenv("GDK_BACKEND", "wayland");
+ dynamic_argv.push_back(const_cast("--ozone-platform=wayland"));
+ } else {
+ qputenv("GDK_BACKEND", "x11");
+ }
+ dynamic_argv.push_back(nullptr);
+ new_argc = dynamic_argv.size() - 1;
+ new_argv = dynamic_argv.data();
+
+ InputArgs::init(new_argc, new_argv);
if (geteuid() == 0) {
CMessage::warning(nullptr, WARNING_LAUNCH_WITH_ADMIN_RIGHTS);
return 0;
@@ -65,12 +93,47 @@ int main( int argc, char *argv[] )
return 0;
}
#endif
+#ifdef __linux
+ char* qpaPlatform = getenv("QT_QPA_PLATFORM");
+ char* xdgSessionType = getenv("XDG_SESSION_TYPE");
+ if ((qpaPlatform && strcmp(qpaPlatform, "wayland") == 0) ||
+ (xdgSessionType && strcmp(xdgSessionType, "wayland") == 0)) {
+ isWayland = true;
+ }
+#endif
+
+ if (!isWayland) {
+ // Plasma and other environments export QT_SCREEN_SCALE_FACTORS /
+ // QT_SCALE_FACTOR on X11. In Qt 6 these activate high-DPI scaling
+ // even with QT_ENABLE_HIGHDPI_SCALING=0, which breaks this app:
+ // widgets become logical-pixel sized while native CEF child windows
+ // (SetWindowSize/XConfigureWindow) and _NET_WM_MOVERESIZE coordinates
+ // remain in device pixels. The app does its own DPI scaling on X11,
+ // so neutralize Qt's completely.
+ qunsetenv("QT_SCREEN_SCALE_FACTORS");
+ qunsetenv("QT_SCALE_FACTOR");
+ qunsetenv("QT_AUTO_SCREEN_SCALE_FACTOR");
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
+ qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
#else
- QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
+ QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
+#endif
+ } else {
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
- QCoreApplication::setAttribute(Qt::AA_Use96Dpi);
+ // Without this, Qt's Wayland platform plugin disables fractional-
+ // scale support and permanently rounds devicePixelRatio() to the
+ // nearest integer for ordinary widgets (e.g. a real 1.25 scale
+ // reports as 2) -- not a startup race, the default, permanent
+ // behavior regardless of how long you wait. Must be set before
+ // QGuiApplication is constructed.
+ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+ }
+ // On Wayland, skip AA_Use96Dpi to let the compositor's native DPI
+ // take effect — otherwise Qt overrides DPI, causing fuzzy text.
+ if (!isWayland)
+ QCoreApplication::setAttribute(Qt::AA_Use96Dpi);
#ifdef _WIN32
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); // avoid Qt's ANGLE colliding with CEF's libEGL/libGLESv2
#endif
@@ -139,7 +202,7 @@ int main( int argc, char *argv[] )
reg_user.remove("lockPortals");
}
- SingleApplication app(argc, argv);
+ SingleApplication app(new_argc, new_argv);
if ( !app.isPrimary() ) {
QString _out_args;
@@ -166,12 +229,16 @@ int main( int argc, char *argv[] )
/* the order is important */
#ifdef __linux
- gtk_init(&argc, &argv);
+ gtk_disable_setlocale();
+ gtk_init(&new_argc, &new_argv);
#endif
- CApplicationCEF::Prepare(argc, argv);
+ CApplicationCEF::Prepare(new_argc, new_argv);
+ if (QGuiApplication::platformName() == "wayland") {
+ qputenv("GDK_BACKEND", "wayland");
+ }
CApplicationCEF* application_cef = new CApplicationCEF();
setup_paths(&AscAppManager::getInstance());
- application_cef->Init_CEF(&AscAppManager::getInstance(), argc, argv);
+ application_cef->Init_CEF(&AscAppManager::getInstance(), new_argc, new_argv);
/* ********************** */
// GET_REGISTRY_SYSTEM(reg_system)
diff --git a/win-linux/src/platform_linux/gtkfilechooser.cpp b/win-linux/src/platform_linux/gtkfilechooser.cpp
index df3b372e0..85dc5dd6a 100755
--- a/win-linux/src/platform_linux/gtkfilechooser.cpp
+++ b/win-linux/src/platform_linux/gtkfilechooser.cpp
@@ -4,7 +4,9 @@
#include "gtkutils.h"
#include "gtkfilechooser.h"
#include "cascapplicationmanagerwrapper.h"
+#ifdef HAVE_X11
#include
+#endif
//static void find_children(GList **list, GtkWidget *wgt, const gchar *name)
@@ -53,7 +55,7 @@ static GSList* parseString(const char *str, const char *delim)
return list;
}
-static void nativeFileDialog(const Window &parent_xid,
+static void nativeFileDialog(ulong parent_xid,
Gtk::Mode mode,
char*** filenames,
int* files_count,
@@ -181,7 +183,7 @@ QStringList Gtk::openGtkFileChooser(QWidget *parent,
char **filenames = nullptr;
char *_sel_filter = (sel_filter) ? strdup(sel_filter->toLocal8Bit().data()) : nullptr;
int files_count = 0;
- Window parent_xid = (parent) ? (Window)parent->winId() : 0L;
+ ulong parent_xid = (parent) ? (ulong)parent->winId() : 0;
nativeFileDialog(parent_xid,
mode,
&filenames,
diff --git a/win-linux/src/platform_linux/gtkprintdialog.cpp b/win-linux/src/platform_linux/gtkprintdialog.cpp
index e48665f09..7d3a7614e 100644
--- a/win-linux/src/platform_linux/gtkprintdialog.cpp
+++ b/win-linux/src/platform_linux/gtkprintdialog.cpp
@@ -6,7 +6,9 @@
#include "cascapplicationmanagerwrapper.h"
#include
#include
+#ifdef HAVE_X11
#include
+#endif
#include
#include "components/cmessage.h"
@@ -183,7 +185,7 @@ void GtkPrintDialog::setPrintRange(PrintRange print_range)
QDialog::DialogCode GtkPrintDialog::exec()
{
QDialog::DialogCode exit_code = QDialog::DialogCode::Rejected;
- Window parent_xid = (m_parent) ? (Window)m_parent->winId() : 0L;
+ ulong parent_xid = (m_parent) ? (ulong)m_parent->winId() : 0;
auto qt_printer_name = m_printer->printerName();
auto qt_resolution = m_printer->resolution();
diff --git a/win-linux/src/platform_linux/gtkutils.cpp b/win-linux/src/platform_linux/gtkutils.cpp
index e4b0b4f7d..d129a97a1 100644
--- a/win-linux/src/platform_linux/gtkutils.cpp
+++ b/win-linux/src/platform_linux/gtkutils.cpp
@@ -24,45 +24,59 @@
*/
#include "gtkutils.h"
-#include
#include
#include "platform_linux/linux_window_utils.h"
+#ifdef HAVE_X11
+#include
+#endif
gboolean set_focus(GtkWidget *dialog)
{
- GdkWindow *gdk_dialog = gtk_widget_get_window(dialog);
- if (gdk_dialog) {
- xcb_window_t wnd = (xcb_window_t)gdk_x11_window_get_xid(gdk_dialog);
- LinuxWindowUtils::setNativeFocusTo(wnd);
+#ifdef HAVE_X11
+ GdkDisplay *gdk_display = gdk_display_get_default();
+ if (gdk_display && GDK_IS_X11_DISPLAY(gdk_display)) {
+ GdkWindow *gdk_dialog = gtk_widget_get_window(dialog);
+ if (gdk_dialog && GDK_IS_X11_WINDOW(gdk_dialog)) {
+ WId wnd = (WId)gdk_x11_window_get_xid(gdk_dialog);
+ LinuxWindowUtils::setNativeFocusTo(wnd);
+ }
}
+#endif
return FALSE;
}
gboolean focus_out(gpointer data)
{
- if (data) {
+#ifdef HAVE_X11
+ GdkDisplay *gdk_display = gdk_display_get_default();
+ if (data && gdk_display && GDK_IS_X11_DISPLAY(gdk_display)) {
DialogTag *tag = (DialogTag*)data;
GtkWidget *dialog = tag->dialog;
- xcb_window_t parent_xid = (xcb_window_t)tag->parent_xid;
- if (dialog && LinuxWindowUtils::isNativeFocus(parent_xid))
+ WId parent_xid = (WId)tag->parent_xid;
+ if (dialog && parent_xid != 0 && LinuxWindowUtils::isNativeFocus(parent_xid))
set_focus(dialog);
}
+#endif
return FALSE;
}
void set_parent(GtkWidget *dialog, gpointer data)
{
+#ifdef HAVE_X11
if (dialog && data) {
GdkDisplay *gdk_display = gdk_display_get_default();
- Window parent_xid = *(Window*)data;
- if (gdk_display && parent_xid != None) {
- GdkWindow *gdk_dialog = gtk_widget_get_window(dialog);
- GdkWindow *gdk_qtparent = gdk_x11_window_foreign_new_for_display(gdk_display, parent_xid);
- if (gdk_dialog && gdk_qtparent )
- gdk_window_set_transient_for(gdk_dialog, gdk_qtparent);
+ if (gdk_display && GDK_IS_X11_DISPLAY(gdk_display)) {
+ Window parent_xid = *(Window*)data;
+ if (parent_xid != None) {
+ GdkWindow *gdk_dialog = gtk_widget_get_window(dialog);
+ GdkWindow *gdk_qtparent = gdk_x11_window_foreign_new_for_display(gdk_display, parent_xid);
+ if (gdk_dialog && gdk_qtparent)
+ gdk_window_set_transient_for(gdk_dialog, gdk_qtparent);
+ }
}
}
+#endif
}
void add_to_recent(const gchar *uri)
diff --git a/win-linux/src/platform_linux/iplatformbackend.h b/win-linux/src/platform_linux/iplatformbackend.h
new file mode 100644
index 000000000..6037cb413
--- /dev/null
+++ b/win-linux/src/platform_linux/iplatformbackend.h
@@ -0,0 +1,71 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#ifndef IPLATFORMBACKEND_H
+#define IPLATFORMBACKEND_H
+
+#include
+#include
+#include
+
+class IPlatformBackend {
+public:
+ virtual ~IPlatformBackend() = default;
+
+ // Window management
+ virtual void moveWindow(WId window, int x, int y) = 0;
+ virtual bool isNativeFocus(WId window) = 0;
+ virtual void setNativeFocusTo(WId window) = 0;
+ virtual void setInputEnabled(WId window, bool enabled) = 0;
+ virtual void getWindowStack(std::vector &winStack) = 0;
+ virtual void findWindowAsync(const char *name, void *data,
+ uint timeout_ms,
+ void(*cb)(WId, void*)) = 0;
+
+ // Decoration / WM interaction
+ virtual void raiseWindow(QWidget *window) = 0;
+ virtual void minimizeWindow(QWidget *window) = 0;
+ virtual void setCursorPos(int x, int y) = 0;
+ virtual void sendButtonRelease(QWidget *window) = 0;
+ virtual void startInteractiveMove(QWidget *window, const QPoint &globalPos) = 0;
+ virtual void startInteractiveResize(QWidget *window, Qt::Edges edges, const QPoint &globalPos) = 0;
+
+ // Cursor management
+ virtual void setCursor(WId window, int cursorShape) = 0;
+ virtual void resetCursor(WId window) = 0;
+
+ // Queries
+ virtual bool isCompositingAvailable() = 0;
+ virtual bool checkButtonState(Qt::MouseButton b) = 0;
+
+ // XDG Portal parent window handle
+ virtual QString portalParentHandle(QWidget *parent) = 0;
+
+ // Factory
+ static IPlatformBackend* create();
+ static IPlatformBackend* instance();
+};
+
+#endif // IPLATFORMBACKEND_H
diff --git a/win-linux/src/platform_linux/linux_window_utils.cpp b/win-linux/src/platform_linux/linux_window_utils.cpp
index 7d3e5edc6..c20b67cab 100644
--- a/win-linux/src/platform_linux/linux_window_utils.cpp
+++ b/win-linux/src/platform_linux/linux_window_utils.cpp
@@ -24,236 +24,36 @@
*/
#include "linux_window_utils.h"
-#include
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
-#include
-#endif
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "iplatformbackend.h"
-
-void LinuxWindowUtils::moveWindow(xcb_window_t window, int x, int y)
-{
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- xcb_connection_t *conn = QX11Info::connection();
- if (conn && window != XCB_WINDOW_NONE) {
- uint32_t val[2];
- val[0] = x;
- val[1] = y;
- xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, val);
- xcb_flush(conn);
- }
-#else
- Display *disp = XOpenDisplay(NULL);
- if (disp) {
- xcb_connection_t *conn = XGetXCBConnection(disp);
- if (conn && window != XCB_WINDOW_NONE) {
- uint32_t val[2];
- val[0] = x;
- val[1] = y;
- xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, val);
- xcb_flush(conn);
- }
- XCloseDisplay(disp);
- }
-#endif
-}
-
-bool LinuxWindowUtils::isNativeFocus(xcb_window_t window)
+void LinuxWindowUtils::moveWindow(WId window, int x, int y)
{
- xcb_window_t win = 0;
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- xcb_connection_t *conn = QX11Info::connection();
- if (conn) {
- xcb_get_input_focus_cookie_t cookie;
- xcb_get_input_focus_reply_t *reply;
- cookie = xcb_get_input_focus(conn);
- reply = xcb_get_input_focus_reply(conn, cookie, NULL);
- if (reply) {
- win = reply->focus;
- free(reply);
- }
- xcb_flush(conn);
- }
-#else
- Display *disp = XOpenDisplay(NULL);
- if (disp) {
- xcb_connection_t *conn = XGetXCBConnection(disp);
- if (conn) {
- xcb_get_input_focus_cookie_t cookie;
- xcb_get_input_focus_reply_t *reply;
- cookie = xcb_get_input_focus(conn);
- reply = xcb_get_input_focus_reply(conn, cookie, NULL);
- if (reply) {
- win = reply->focus;
- free(reply);
- }
- xcb_flush(conn);
- }
- XCloseDisplay(disp);
- }
-#endif
- return window == win;
+ IPlatformBackend::instance()->moveWindow(window, x, y);
}
-void LinuxWindowUtils::setNativeFocusTo(xcb_window_t window)
+bool LinuxWindowUtils::isNativeFocus(WId window)
{
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- xcb_connection_t *conn = QX11Info::connection();
- if (conn && window != XCB_WINDOW_NONE) {
- xcb_void_cookie_t cookie;
- cookie = xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
- window, XCB_CURRENT_TIME);
- xcb_flush(conn);
- }
-#else
- Display *disp = XOpenDisplay(NULL);
- if (disp) {
- xcb_connection_t *conn = XGetXCBConnection(disp);
- if (conn && window != XCB_WINDOW_NONE) {
- xcb_void_cookie_t cookie;
- cookie = xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
- window, XCB_CURRENT_TIME);
- xcb_flush(conn);
- }
- XCloseDisplay(disp);
- }
-#endif
-}
-
-static void SetSkipTaskbar(Display* disp, Window win)
-{
- Atom wm_state = XInternAtom(disp, "_NET_WM_STATE", True);
- Atom wm_state_skip_taskbar = XInternAtom(disp, "_NET_WM_STATE_SKIP_TASKBAR", True);
- if (wm_state != None && wm_state_skip_taskbar != None)
- XChangeProperty(disp, win, wm_state, XA_ATOM, 32, PropModeReplace, (const unsigned char*)&wm_state_skip_taskbar, 1);
-}
-
-static void GetWindowName(Display* disp, Window win, char **name) {
- XClassHint* class_hint = NULL;
- class_hint = XAllocClassHint();
- if (class_hint) {
- Status s = XGetClassHint(disp, win, class_hint);
- if (s == 1)
- *name = strdup(class_hint->res_name);
- XFree(class_hint);
- }
-}
-
-static void GetWindowList(Display *disp, Window **list, unsigned long *len) {
- int form;
- unsigned long remain;
- unsigned char *win_list;
- Atom type;
- Atom prop = XInternAtom(disp, "_NET_CLIENT_LIST_STACKING", true);
- Window root = XDefaultRootWindow(disp);
- int res = XGetWindowProperty(disp, root, prop, 0, 1024, false, XA_WINDOW,
- &type, &form, len, &remain, &win_list);
- if (res == Success)
- *list = (Window*)win_list;
+ return IPlatformBackend::instance()->isNativeFocus(window);
}
-static bool IsVisible(Display *disp, Window wnd)
+void LinuxWindowUtils::setNativeFocusTo(WId window)
{
- xcb_connection_t *conn = XGetXCBConnection(disp);
- if (conn) {
- xcb_get_window_attributes_cookie_t cookie;
- xcb_get_window_attributes_reply_t *reply;
- cookie = xcb_get_window_attributes(conn, wnd);
- reply = xcb_get_window_attributes_reply(conn, cookie, NULL);
- if (reply) {
- uint8_t state = reply->map_state;
- free(reply);
- if (state == XCB_MAP_STATE_VIEWABLE)
- return true;
- }
- }
- return false;
+ IPlatformBackend::instance()->setNativeFocusTo(window);
}
void LinuxWindowUtils::findWindowAsync(const char *window_name, void *user_data,
uint timeout_ms,
- void(*callback)(xcb_window_t, void*))
+ void(*callback)(WId, void*))
{
- QtConcurrent::run([=]() {
- Display *disp = XOpenDisplay(NULL);
- if (!disp)
- return;
- int DELAY_MS = 50;
- int RETRIES = (int)((float)timeout_ms / DELAY_MS);
- Window win_found = None;
- do {
- std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_MS));
- Window *win_list = NULL;
- unsigned long win_list_size = 0;
- GetWindowList(disp, &win_list, &win_list_size);
- for (int i = 0; i < (int)win_list_size; i++) {
- char *name = NULL;
- GetWindowName(disp, win_list[i], &name);
- if (name) {
- if (strstr(name, window_name) != NULL) {
- if (IsVisible(disp, win_list[i])) {
- win_found = win_list[i];
- SetSkipTaskbar(disp, win_found);
- callback((xcb_window_t)win_found, user_data);
- }
- free(name);
- break;
- }
- free(name);
- }
- }
- if (win_list)
- XFree(win_list);
- } while (--RETRIES > 0 && win_found == None);
- XCloseDisplay(disp);
- });
+ IPlatformBackend::instance()->findWindowAsync(window_name, user_data, timeout_ms, callback);
}
-void LinuxWindowUtils::getWindowStack(std::vector &winStack)
+void LinuxWindowUtils::getWindowStack(std::vector &winStack)
{
- Display *disp = XOpenDisplay(NULL);
- if (!disp)
- return;
- Window *win_list = NULL;
- unsigned long win_list_size = 0;
- GetWindowList(disp, &win_list, &win_list_size);
- if (win_list) {
- for (int i = 0; i < (int)win_list_size; i++)
- winStack.push_back((xcb_window_t)win_list[i]);
- XFree(win_list);
- }
+ IPlatformBackend::instance()->getWindowStack(winStack);
}
-void LinuxWindowUtils::setInputEnabled(xcb_window_t window, bool enabled)
+void LinuxWindowUtils::setInputEnabled(WId window, bool enabled)
{
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- Display* disp = QX11Info::display();
- Window wnd = window;
- if (enabled) {
- XShapeCombineMask(disp, wnd, ShapeInput, 0, 0, None, ShapeSet);
- } else {
- XRectangle rc = {0, 0, 0, 0};
- XShapeCombineRectangles(disp, wnd, ShapeInput, 0, 0, &rc, 1, ShapeSet, YXBanded);
- }
- XFlush(disp);
-#else
- Display* disp = XOpenDisplay(NULL);
- if (disp) {
- Window wnd = window;
- if (enabled) {
- XShapeCombineMask(disp, wnd, ShapeInput, 0, 0, None, ShapeSet);
- } else {
- XRectangle rc = {0, 0, 0, 0};
- XShapeCombineRectangles(disp, wnd, ShapeInput, 0, 0, &rc, 1, ShapeSet, YXBanded);
- }
- XFlush(disp);
- XCloseDisplay(disp);
- }
-#endif
+ IPlatformBackend::instance()->setInputEnabled(window, enabled);
}
diff --git a/win-linux/src/platform_linux/linux_window_utils.h b/win-linux/src/platform_linux/linux_window_utils.h
index 4ac70c8f5..93336a414 100644
--- a/win-linux/src/platform_linux/linux_window_utils.h
+++ b/win-linux/src/platform_linux/linux_window_utils.h
@@ -26,20 +26,20 @@
#ifndef LINUX_WINDOW_UTILS_H
#define LINUX_WINDOW_UTILS_H
-#include
+#include
#include
namespace LinuxWindowUtils
{
-void moveWindow(xcb_window_t window, int x, int y);
-void setNativeFocusTo(xcb_window_t window);
-bool isNativeFocus(xcb_window_t window);
+void moveWindow(WId window, int x, int y);
+void setNativeFocusTo(WId window);
+bool isNativeFocus(WId window);
void findWindowAsync(const char *window_name, void *user_data,
uint timeout_ms,
- void(*callback)(xcb_window_t, void*));
-void getWindowStack(std::vector &winStack);
-void setInputEnabled(xcb_window_t window, bool enabled);
+ void(*callback)(WId, void*));
+void getWindowStack(std::vector &winStack);
+void setInputEnabled(WId window, bool enabled);
}
#endif // LINUX_WINDOW_UTILS_H
diff --git a/win-linux/src/platform_linux/platformbackendfactory.cpp b/win-linux/src/platform_linux/platformbackendfactory.cpp
new file mode 100644
index 000000000..dc96c8743
--- /dev/null
+++ b/win-linux/src/platform_linux/platformbackendfactory.cpp
@@ -0,0 +1,45 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#include "iplatformbackend.h"
+#include "x11backend.h"
+#include "waylandbackend.h"
+#include
+
+static IPlatformBackend* s_instance = nullptr;
+
+IPlatformBackend* IPlatformBackend::create()
+{
+ if (QGuiApplication::platformName() == "wayland")
+ return new WaylandBackend();
+ return new X11Backend();
+}
+
+IPlatformBackend* IPlatformBackend::instance()
+{
+ if (!s_instance)
+ s_instance = create();
+ return s_instance;
+}
diff --git a/win-linux/src/platform_linux/waylandbackend.cpp b/win-linux/src/platform_linux/waylandbackend.cpp
new file mode 100644
index 000000000..5a4781750
--- /dev/null
+++ b/win-linux/src/platform_linux/waylandbackend.cpp
@@ -0,0 +1,170 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#include "waylandbackend.h"
+#include
+#include
+
+
+WaylandBackend::WaylandBackend()
+{
+}
+
+WaylandBackend::~WaylandBackend()
+{
+}
+
+void WaylandBackend::moveWindow(WId window, int x, int y)
+{
+ // Wayland does not allow clients to position their own windows.
+ Q_UNUSED(window);
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+}
+
+bool WaylandBackend::isNativeFocus(WId window)
+{
+ QWindow *focusWindow = QGuiApplication::focusWindow();
+ if (!focusWindow)
+ return false;
+ return focusWindow->winId() == window;
+}
+
+void WaylandBackend::setNativeFocusTo(WId window)
+{
+ QWidget *widget = QWidget::find(window);
+ if (widget && widget->windowHandle())
+ widget->windowHandle()->requestActivate();
+}
+
+void WaylandBackend::setInputEnabled(WId window, bool enabled)
+{
+ // XShape is not available on Wayland; use Qt's setEnabled as a simplified fallback.
+ QWidget *widget = QWidget::find(window);
+ if (widget)
+ widget->setEnabled(enabled);
+}
+
+void WaylandBackend::getWindowStack(std::vector &winStack)
+{
+ // Window stacking order is not available on Wayland.
+ // Consumers should use internal tracking instead.
+ Q_UNUSED(winStack);
+}
+
+void WaylandBackend::findWindowAsync(const char *name, void *data,
+ uint timeout_ms,
+ void(*cb)(WId, void*))
+{
+ // Enumerating foreign windows is not possible on Wayland.
+ Q_UNUSED(name);
+ Q_UNUSED(data);
+ Q_UNUSED(timeout_ms);
+ Q_UNUSED(cb);
+}
+
+void WaylandBackend::raiseWindow(QWidget *window)
+{
+ // Best-effort: raise and request activation via Qt APIs.
+ window->raise();
+ window->activateWindow();
+}
+
+void WaylandBackend::minimizeWindow(QWidget *window)
+{
+ window->showMinimized();
+}
+
+void WaylandBackend::setCursorPos(int x, int y)
+{
+ // Warping the cursor is not allowed on Wayland.
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+}
+
+void WaylandBackend::sendButtonRelease(QWidget *window)
+{
+ // Synthetic input events are not possible on Wayland.
+ Q_UNUSED(window);
+}
+
+void WaylandBackend::startInteractiveMove(QWidget *window, const QPoint &globalPos)
+{
+ Q_UNUSED(globalPos);
+ if (window && window->windowHandle())
+ window->windowHandle()->startSystemMove();
+}
+
+void WaylandBackend::startInteractiveResize(QWidget *window, Qt::Edges edges, const QPoint &globalPos)
+{
+ Q_UNUSED(globalPos);
+ if (window && window->windowHandle())
+ window->windowHandle()->startSystemResize(edges);
+}
+
+void WaylandBackend::setCursor(WId window, int cursorShape)
+{
+ Qt::CursorShape qt_shape = Qt::ArrowCursor;
+ switch (cursorShape) {
+ case 0: qt_shape = Qt::SizeFDiagCursor; break; // Top-left
+ case 1: qt_shape = Qt::SizeVerCursor; break; // Top
+ case 2: qt_shape = Qt::SizeBDiagCursor; break; // Top-right
+ case 3: qt_shape = Qt::SizeHorCursor; break; // Right
+ case 4: qt_shape = Qt::SizeFDiagCursor; break; // Bottom-right
+ case 5: qt_shape = Qt::SizeVerCursor; break; // Bottom
+ case 6: qt_shape = Qt::SizeBDiagCursor; break; // Bottom-left
+ case 7: qt_shape = Qt::SizeHorCursor; break; // Left
+ default: qt_shape = Qt::CursorShape(cursorShape); break; // fallback
+ }
+ QWidget *widget = QWidget::find(window);
+ if (widget)
+ widget->setCursor(qt_shape);
+}
+
+void WaylandBackend::resetCursor(WId window)
+{
+ QWidget *widget = QWidget::find(window);
+ if (widget)
+ widget->unsetCursor();
+}
+
+bool WaylandBackend::isCompositingAvailable()
+{
+ // Wayland is inherently composited.
+ return true;
+}
+
+bool WaylandBackend::checkButtonState(Qt::MouseButton b)
+{
+ return QGuiApplication::mouseButtons().testFlag(b);
+}
+
+QString WaylandBackend::portalParentHandle(QWidget *parent)
+{
+ // XDG Desktop Portal works without a parent window handle on Wayland.
+ // Full xdg_foreign support can be added later if needed.
+ Q_UNUSED(parent);
+ return QString();
+}
diff --git a/win-linux/src/platform_linux/waylandbackend.h b/win-linux/src/platform_linux/waylandbackend.h
new file mode 100644
index 000000000..53166add2
--- /dev/null
+++ b/win-linux/src/platform_linux/waylandbackend.h
@@ -0,0 +1,66 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#ifndef WAYLANDBACKEND_H
+#define WAYLANDBACKEND_H
+
+#include "iplatformbackend.h"
+
+class WaylandBackend : public IPlatformBackend {
+public:
+ WaylandBackend();
+ ~WaylandBackend() override;
+
+ // Window management
+ void moveWindow(WId window, int x, int y) override;
+ bool isNativeFocus(WId window) override;
+ void setNativeFocusTo(WId window) override;
+ void setInputEnabled(WId window, bool enabled) override;
+ void getWindowStack(std::vector &winStack) override;
+ void findWindowAsync(const char *name, void *data,
+ uint timeout_ms,
+ void(*cb)(WId, void*)) override;
+
+ // Decoration / WM interaction
+ void raiseWindow(QWidget *window) override;
+ void minimizeWindow(QWidget *window) override;
+ void setCursorPos(int x, int y) override;
+ void sendButtonRelease(QWidget *window) override;
+ void startInteractiveMove(QWidget *window, const QPoint &globalPos) override;
+ void startInteractiveResize(QWidget *window, Qt::Edges edges, const QPoint &globalPos) override;
+
+ // Cursor management
+ void setCursor(WId window, int cursorShape) override;
+ void resetCursor(WId window) override;
+
+ // Queries
+ bool isCompositingAvailable() override;
+ bool checkButtonState(Qt::MouseButton b) override;
+
+ // XDG Portal parent window handle
+ QString portalParentHandle(QWidget *parent) override;
+};
+
+#endif // WAYLANDBACKEND_H
diff --git a/win-linux/src/platform_linux/x11backend.cpp b/win-linux/src/platform_linux/x11backend.cpp
new file mode 100644
index 000000000..89dcaf99d
--- /dev/null
+++ b/win-linux/src/platform_linux/x11backend.cpp
@@ -0,0 +1,459 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#include "x11backend.h"
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+namespace {
+
+auto getXDisplay() -> Display * {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
+ if (auto *x11 = qGuiApp ? qGuiApp->nativeInterface() : nullptr)
+ return x11->display();
+ return nullptr;
+#else
+ return QX11Info::display();
+#endif
+}
+
+void SetSkipTaskbar(Display* disp, Window win)
+{
+ Atom wm_state = XInternAtom(disp, "_NET_WM_STATE", True);
+ Atom wm_state_skip_taskbar = XInternAtom(disp, "_NET_WM_STATE_SKIP_TASKBAR", True);
+ if (wm_state != None && wm_state_skip_taskbar != None)
+ XChangeProperty(disp, win, wm_state, XA_ATOM, 32, PropModeReplace, (const unsigned char*)&wm_state_skip_taskbar, 1);
+}
+
+void GetWindowName(Display* disp, Window win, char **name) {
+ XClassHint* class_hint = NULL;
+ class_hint = XAllocClassHint();
+ if (class_hint) {
+ Status s = XGetClassHint(disp, win, class_hint);
+ if (s == 1)
+ *name = strdup(class_hint->res_name);
+ XFree(class_hint);
+ }
+}
+
+void GetWindowList(Display *disp, Window **list, unsigned long *len) {
+ int form;
+ unsigned long remain;
+ unsigned char *win_list;
+ Atom type;
+ Atom prop = XInternAtom(disp, "_NET_CLIENT_LIST_STACKING", true);
+ Window root = XDefaultRootWindow(disp);
+ int res = XGetWindowProperty(disp, root, prop, 0, 1024, false, XA_WINDOW,
+ &type, &form, len, &remain, &win_list);
+ if (res == Success)
+ *list = (Window*)win_list;
+}
+
+bool IsVisible(Display *disp, Window wnd)
+{
+ xcb_connection_t *conn = XGetXCBConnection(disp);
+ if (conn) {
+ xcb_get_window_attributes_cookie_t cookie;
+ xcb_get_window_attributes_reply_t *reply;
+ cookie = xcb_get_window_attributes(conn, wnd);
+ reply = xcb_get_window_attributes_reply(conn, cookie, NULL);
+ if (reply) {
+ uint8_t state = reply->map_state;
+ free(reply);
+ if (state == XCB_MAP_STATE_VIEWABLE)
+ return true;
+ }
+ }
+ return false;
+}
+
+int edgesToMoveResizeDirection(Qt::Edges edges)
+{
+ // _NET_WM_MOVERESIZE direction constants
+ const int SIZE_TOPLEFT = 0;
+ const int SIZE_TOP = 1;
+ const int SIZE_TOPRIGHT = 2;
+ const int SIZE_RIGHT = 3;
+ const int SIZE_BOTTOMRIGHT = 4;
+ const int SIZE_BOTTOM = 5;
+ const int SIZE_BOTTOMLEFT = 6;
+ const int SIZE_LEFT = 7;
+
+ if (edges == (Qt::TopEdge | Qt::LeftEdge)) return SIZE_TOPLEFT;
+ if (edges == Qt::TopEdge) return SIZE_TOP;
+ if (edges == (Qt::TopEdge | Qt::RightEdge)) return SIZE_TOPRIGHT;
+ if (edges == Qt::RightEdge) return SIZE_RIGHT;
+ if (edges == (Qt::BottomEdge | Qt::RightEdge)) return SIZE_BOTTOMRIGHT;
+ if (edges == Qt::BottomEdge) return SIZE_BOTTOM;
+ if (edges == (Qt::BottomEdge | Qt::LeftEdge)) return SIZE_BOTTOMLEFT;
+ if (edges == Qt::LeftEdge) return SIZE_LEFT;
+
+ return -1;
+}
+
+} // anonymous namespace
+
+
+X11Backend::X11Backend()
+{
+}
+
+X11Backend::~X11Backend()
+{
+}
+
+void X11Backend::moveWindow(WId window, int x, int y)
+{
+ Display *disp = getXDisplay();
+ if (disp) {
+ xcb_connection_t *conn = XGetXCBConnection(disp);
+ if (conn && (xcb_window_t)window != XCB_WINDOW_NONE) {
+ uint32_t val[2];
+ val[0] = x;
+ val[1] = y;
+ xcb_configure_window(conn, (xcb_window_t)window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, val);
+ xcb_flush(conn);
+ }
+ }
+}
+
+bool X11Backend::isNativeFocus(WId window)
+{
+ xcb_window_t win = 0;
+ Display *disp = getXDisplay();
+ if (disp) {
+ xcb_connection_t *conn = XGetXCBConnection(disp);
+ if (conn) {
+ xcb_get_input_focus_cookie_t cookie;
+ xcb_get_input_focus_reply_t *reply;
+ cookie = xcb_get_input_focus(conn);
+ reply = xcb_get_input_focus_reply(conn, cookie, NULL);
+ if (reply) {
+ win = reply->focus;
+ free(reply);
+ }
+ xcb_flush(conn);
+ }
+ }
+ return (xcb_window_t)window == win;
+}
+
+void X11Backend::setNativeFocusTo(WId window)
+{
+ Display *disp = getXDisplay();
+ if (disp) {
+ xcb_connection_t *conn = XGetXCBConnection(disp);
+ if (conn && (xcb_window_t)window != XCB_WINDOW_NONE) {
+ xcb_void_cookie_t cookie;
+ cookie = xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
+ (xcb_window_t)window, XCB_CURRENT_TIME);
+ xcb_flush(conn);
+ }
+ }
+}
+
+void X11Backend::setInputEnabled(WId window, bool enabled)
+{
+ Display* disp = getXDisplay();
+ if (disp) {
+ Window wnd = (Window)window;
+ if (enabled) {
+ XShapeCombineMask(disp, wnd, ShapeInput, 0, 0, None, ShapeSet);
+ } else {
+ XRectangle rc = {0, 0, 0, 0};
+ XShapeCombineRectangles(disp, wnd, ShapeInput, 0, 0, &rc, 1, ShapeSet, YXBanded);
+ }
+ XFlush(disp);
+ }
+}
+
+void X11Backend::getWindowStack(std::vector &winStack)
+{
+ Display *disp = XOpenDisplay(NULL);
+ if (!disp)
+ return;
+ Window *win_list = NULL;
+ unsigned long win_list_size = 0;
+ GetWindowList(disp, &win_list, &win_list_size);
+ if (win_list) {
+ for (int i = 0; i < (int)win_list_size; i++)
+ winStack.push_back((WId)win_list[i]);
+ XFree(win_list);
+ }
+ XCloseDisplay(disp);
+}
+
+void X11Backend::findWindowAsync(const char *name, void *data,
+ uint timeout_ms,
+ void(*cb)(WId, void*))
+{
+ QtConcurrent::run([=]() {
+ Display *disp = XOpenDisplay(NULL);
+ if (!disp)
+ return;
+ int DELAY_MS = 50;
+ int RETRIES = (int)((float)timeout_ms / DELAY_MS);
+ Window win_found = None;
+ do {
+ std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_MS));
+ Window *win_list = NULL;
+ unsigned long win_list_size = 0;
+ GetWindowList(disp, &win_list, &win_list_size);
+ for (int i = 0; i < (int)win_list_size; i++) {
+ char *wname = NULL;
+ GetWindowName(disp, win_list[i], &wname);
+ if (wname) {
+ if (strstr(wname, name) != NULL) {
+ if (IsVisible(disp, win_list[i])) {
+ win_found = win_list[i];
+ SetSkipTaskbar(disp, win_found);
+ cb((WId)win_found, data);
+ }
+ free(wname);
+ break;
+ }
+ free(wname);
+ }
+ }
+ if (win_list)
+ XFree(win_list);
+ } while (--RETRIES > 0 && win_found == None);
+ XCloseDisplay(disp);
+ });
+}
+
+void X11Backend::raiseWindow(QWidget *window)
+{
+ Display *disp = getXDisplay();
+ Atom atom_active_wnd = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
+ if (atom_active_wnd == None)
+ return;
+ Window wnd = (Window)window->winId();
+ Window root = DefaultRootWindow(disp);
+ XEvent event;
+ memset(&event, 0, sizeof(XEvent));
+ event.xclient.type = ClientMessage;
+ event.xclient.serial = 0;
+ event.xclient.send_event = True;
+ event.xclient.message_type = atom_active_wnd;
+ event.xclient.window = wnd;
+ event.xclient.format = 32;
+ XSendEvent(disp, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
+ XMapRaised(disp, wnd);
+ XFlush(disp);
+}
+
+void X11Backend::minimizeWindow(QWidget *window)
+{
+ Display * xdisplay_ = getXDisplay();
+ Atom wm_change_state = XInternAtom(xdisplay_, "WM_CHANGE_STATE", False);
+
+ XClientMessageEvent ev;
+ ev.type = ClientMessage;
+ ev.window = window->winId();
+ ev.message_type = wm_change_state;
+ ev.format = 32;
+ ev.data.l[0] = IconicState;
+
+ XSendEvent(xdisplay_, RootWindow(xdisplay_, DefaultScreen(xdisplay_)), False,
+ SubstructureRedirectMask|SubstructureNotifyMask, (XEvent *)&ev);
+
+ // Also send a button release to avoid stuck states
+ sendButtonRelease(window);
+}
+
+void X11Backend::setCursorPos(int x, int y)
+{
+ Display *xdisplay_ = getXDisplay();
+ Window root_window = DefaultRootWindow(xdisplay_);
+ XSelectInput(xdisplay_, root_window, KeyReleaseMask);
+ XWarpPointer(xdisplay_, None, root_window, 0, 0, 0, 0, x, y);
+ XFlush(xdisplay_);
+}
+
+void X11Backend::sendButtonRelease(QWidget *window)
+{
+ Display * xdisplay_ = getXDisplay();
+ Window x_root_window_ = (Window)window->effectiveWinId();
+
+ XEvent event;
+ memset(&event, 0, sizeof(XEvent));
+
+ event.type = ButtonRelease;
+ event.xbutton.button = Button1;
+ event.xbutton.same_screen = True;
+
+ XQueryPointer(xdisplay_, x_root_window_, &event.xbutton.root, &event.xbutton.window,
+ &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
+ XSendEvent(xdisplay_, PointerWindow, True, ButtonReleaseMask, &event);
+ XFlush(xdisplay_);
+}
+
+void X11Backend::startInteractiveMove(QWidget *window, const QPoint &globalPos)
+{
+ Q_UNUSED(globalPos);
+ const int MOVE = 8;
+
+ Display * xdisplay_ = getXDisplay();
+ Window x_root_window_ = DefaultRootWindow(xdisplay_);
+
+ XUngrabPointer(xdisplay_, CurrentTime);
+
+ Window root_ret, child_ret; // <-- new: query native coords
+ int rx = 0, ry = 0, wx, wy;
+ unsigned int mask;
+ XQueryPointer(xdisplay_, x_root_window_, &root_ret, &child_ret,
+ &rx, &ry, &wx, &wy, &mask);
+
+ XEvent event;
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.display = xdisplay_;
+ event.xclient.window = window->winId();
+ event.xclient.message_type = XInternAtom(xdisplay_, "_NET_WM_MOVERESIZE", False);
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = rx;
+ event.xclient.data.l[1] = ry;
+ event.xclient.data.l[2] = MOVE;
+ event.xclient.data.l[3] = Button1;
+ event.xclient.data.l[4] = 0;
+
+ XSendEvent(xdisplay_, x_root_window_, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
+ XFlush(xdisplay_);
+}
+
+void X11Backend::startInteractiveResize(QWidget *window, Qt::Edges edges, const QPoint &globalPos)
+{
+ Q_UNUSED(globalPos);
+ int direction = edgesToMoveResizeDirection(edges);
+ if (direction < 0)
+ return;
+
+ Display * xdisplay_ = getXDisplay();
+ Window x_root_window_ = DefaultRootWindow(xdisplay_);
+
+ XUngrabPointer(xdisplay_, CurrentTime);
+
+ Window root_ret, child_ret; // <-- new: query native coords
+ int rx = 0, ry = 0, wx, wy;
+ unsigned int mask;
+ XQueryPointer(xdisplay_, x_root_window_, &root_ret, &child_ret,
+ &rx, &ry, &wx, &wy, &mask);
+
+ XEvent event;
+ memset(&event, 0, sizeof(event));
+ event.xclient.type = ClientMessage;
+ event.xclient.display = xdisplay_;
+ event.xclient.window = window->winId();
+ event.xclient.message_type = XInternAtom(xdisplay_, "_NET_WM_MOVERESIZE", False);
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = rx;
+ event.xclient.data.l[1] = ry;
+ event.xclient.data.l[2] = direction;
+ event.xclient.data.l[3] = Button1;
+ event.xclient.data.l[4] = 0;
+
+ XSendEvent(xdisplay_, x_root_window_, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
+ XFlush(xdisplay_);
+}
+
+void X11Backend::setCursor(WId window, int cursorShape)
+{
+ unsigned int x11_shape = 0;
+ switch (cursorShape) {
+ case 0: x11_shape = 134; break; // XC_top_left_corner
+ case 1: x11_shape = 138; break; // XC_top_side
+ case 2: x11_shape = 136; break; // XC_top_right_corner
+ case 3: x11_shape = 96; break; // XC_right_side
+ case 4: x11_shape = 14; break; // XC_bottom_right_corner
+ case 5: x11_shape = 16; break; // XC_bottom_side
+ case 6: x11_shape = 12; break; // XC_bottom_left_corner
+ case 7: x11_shape = 70; break; // XC_left_side
+ default: x11_shape = cursorShape; break; // fallback
+ }
+ Display * _display = getXDisplay();
+ if (_display) {
+ Cursor cursor = XCreateFontCursor(_display, x11_shape);
+ XDefineCursor(_display, (Window)window, cursor);
+ XFlush(_display);
+ XFreeCursor(_display, cursor);
+ }
+}
+
+void X11Backend::resetCursor(WId window)
+{
+ Display * _display = getXDisplay();
+ XUndefineCursor(_display, (Window)window);
+ XFlush(_display);
+}
+
+bool X11Backend::isCompositingAvailable()
+{
+ static const bool active = [] {
+ Display *d = getXDisplay();
+ if (!d) return true;
+ char name[32];
+ snprintf(name, sizeof(name), "_NET_WM_CM_S%d", DefaultScreen(d));
+ Atom a = XInternAtom(d, name, False);
+ return XGetSelectionOwner(d, a) != None;
+ }();
+ return active;
+}
+
+bool X11Backend::checkButtonState(Qt::MouseButton b)
+{
+ Display * xdisplay_ = getXDisplay();
+ Window x_root_window_ = DefaultRootWindow(xdisplay_);
+
+ Window root_, child_;
+ int root_x, root_y, child_x, child_y;
+ uint mask;
+
+ Bool res = XQueryPointer(xdisplay_, x_root_window_, &root_, &child_,
+ &root_x, &root_y, &child_x, &child_y, &mask);
+
+ if ( res ) {
+ if ( b == Qt::LeftButton)
+ return mask & Button1MotionMask;
+ }
+
+ return false;
+}
+
+QString X11Backend::portalParentHandle(QWidget *parent)
+{
+ return "x11:" + QString::number((long)parent->winId(), 16);
+}
diff --git a/win-linux/src/platform_linux/x11backend.h b/win-linux/src/platform_linux/x11backend.h
new file mode 100644
index 000000000..98cad2adc
--- /dev/null
+++ b/win-linux/src/platform_linux/x11backend.h
@@ -0,0 +1,66 @@
+/*
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+ */
+
+#ifndef X11BACKEND_H
+#define X11BACKEND_H
+
+#include "iplatformbackend.h"
+
+class X11Backend : public IPlatformBackend {
+public:
+ X11Backend();
+ ~X11Backend() override;
+
+ // Window management
+ void moveWindow(WId window, int x, int y) override;
+ bool isNativeFocus(WId window) override;
+ void setNativeFocusTo(WId window) override;
+ void setInputEnabled(WId window, bool enabled) override;
+ void getWindowStack(std::vector &winStack) override;
+ void findWindowAsync(const char *name, void *data,
+ uint timeout_ms,
+ void(*cb)(WId, void*)) override;
+
+ // Decoration / WM interaction
+ void raiseWindow(QWidget *window) override;
+ void minimizeWindow(QWidget *window) override;
+ void setCursorPos(int x, int y) override;
+ void sendButtonRelease(QWidget *window) override;
+ void startInteractiveMove(QWidget *window, const QPoint &globalPos) override;
+ void startInteractiveResize(QWidget *window, Qt::Edges edges, const QPoint &globalPos) override;
+
+ // Cursor management
+ void setCursor(WId window, int cursorShape) override;
+ void resetCursor(WId window) override;
+
+ // Queries
+ bool isCompositingAvailable() override;
+ bool checkButtonState(Qt::MouseButton b) override;
+
+ // XDG Portal parent window handle
+ QString portalParentHandle(QWidget *parent) override;
+};
+
+#endif // X11BACKEND_H
diff --git a/win-linux/src/platform_linux/xdgdesktopportal.cpp b/win-linux/src/platform_linux/xdgdesktopportal.cpp
index 1a1941745..0c0962c3b 100644
--- a/win-linux/src/platform_linux/xdgdesktopportal.cpp
+++ b/win-linux/src/platform_linux/xdgdesktopportal.cpp
@@ -2,6 +2,7 @@
#include "xdgdesktopportal.h"
#include "components/cmessage.h"
#include "platform_linux/linux_window_utils.h"
+#include "iplatformbackend.h"
#include
#include
#include
@@ -13,7 +14,9 @@
#include
#include
#include
+#ifdef HAVE_X11
#include
+#endif
#if defined(__x86_64__)
# define GETRANDOM_NR 318
@@ -804,7 +807,7 @@ Result allocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, char*
}
#endif
-Result callXdgPortal(Window parent, Xdg::Mode mode, const char* title,
+Result callXdgPortal(QWidget* parent, Xdg::Mode mode, const char* title,
DBusMessage* &outMsg,
const FilterItem* filterList,
uint filterCount,
@@ -831,8 +834,9 @@ Result callXdgPortal(Window parent, Xdg::Mode mode, const char* title,
DBusMessageIter iter;
dbus_message_iter_init_append(methd, &iter);
- QString parent_window_qstr = "x11:" + QString::number((long)parent, 16);
- char* parent_window = parent_window_qstr.toUtf8().data();
+ QString parent_window_qstr = parent ? IPlatformBackend::instance()->portalParentHandle(parent) : QString();
+ QByteArray parent_window_ba = parent_window_qstr.toUtf8();
+ char* parent_window = parent_window_ba.data();
__dbusAppend(&iter, DBUS_TYPE_STRING, &parent_window);
__dbusAppend(&iter, DBUS_TYPE_STRING, &title);
@@ -936,7 +940,7 @@ void freePath(char* filePath) {
Free(filePath);
}
-Result openDialog(Window parent, Xdg::Mode mode, const char* title,
+Result openDialog(QWidget* parent, Xdg::Mode mode, const char* title,
char** outPaths,
const FilterItem* filterList,
uint filterCount,
@@ -1076,7 +1080,7 @@ void Free(void* p) {
}
}
-void onWindowFound(xcb_window_t w, void *user_data)
+void onWindowFound(WId w, void *user_data)
{
if (QWidget *p = (QWidget*)user_data)
LinuxWindowUtils::moveWindow(w, p->x() + 20, p->y() + 80);
@@ -1107,7 +1111,6 @@ QStringList Xdg::openXdgPortal(QWidget *parent,
bool sel_multiple)
{
initDBus();
- Window parentWid = (parent) ? (Window)parent->winId() : 0L;
const int pos = file_name.lastIndexOf('/');
const QString _file_name = (pos != -1) ? file_name.mid(pos + 1) : file_name;
const QString _path = (path.isEmpty() && pos != -1) ? file_name.mid(0, pos) : path;
@@ -1135,7 +1138,7 @@ QStringList Xdg::openXdgPortal(QWidget *parent,
char* outPaths;
LinuxWindowUtils::findWindowAsync("xdg-desktop-portal", (void*)parent, 3000, onWindowFound);
Result result;
- result = openDialog(parentWid, mode, title.toUtf8().data(),
+ result = openDialog(parent, mode, title.toUtf8().data(),
&outPaths,
filterItem,
filterSize,
diff --git a/win-linux/src/prop/cmainwindowimpl.cpp b/win-linux/src/prop/cmainwindowimpl.cpp
index 3b5150273..ee12a376c 100644
--- a/win-linux/src/prop/cmainwindowimpl.cpp
+++ b/win-linux/src/prop/cmainwindowimpl.cpp
@@ -150,6 +150,10 @@ void CMainWindowImpl::refreshAboutVersion()
GET_REGISTRY_USER(reg_user);
_json_obj["editorwindowmode"] = reg_user.value("editorWindowMode",false).toBool();
+ _json_obj["defaultsaveformat"] = QJsonObject({
+ {"format", Utils::defaultSaveFormat()},
+ {"locked", Utils::defaultSaveFormatEnforced()}
+ });
_json_obj["usegpu"] = !(AscAppManager::userSettings(L"disable-gpu") == L"1");
_json_obj["useai"] = !(AscAppManager::userSettings(L"disable-ai") == L"1");
diff --git a/win-linux/src/utils.cpp b/win-linux/src/utils.cpp
index b085c854c..641253e65 100644
--- a/win-linux/src/utils.cpp
+++ b/win-linux/src/utils.cpp
@@ -33,6 +33,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -550,14 +551,6 @@ inline double choose_scaling(double s)
s > 1 ? 1.25 : 1;
}
-double Utils::getScreenDpiRatio(int scrnum)
-{
- unsigned int _dpi_x = 0;
- unsigned int _dpi_y = 0;
- double nScale = AscAppManager::getInstance().GetMonitorScaleByIndex(scrnum, _dpi_x, _dpi_y);
- return choose_scaling(nScale);
-}
-
double Utils::getScreenDpiRatio(const QPoint& pt)
{
QWidget _w;
@@ -580,6 +573,14 @@ double Utils::getScreenDpiRatioByHWND(int hwnd)
double Utils::getScreenDpiRatioByWidget(QWidget* wid)
{
+ // Manual DPI scaling here (multiplying sizes by dpiRatio) computes a
+ // logical/DIP size for widgets that don't rely purely on Qt's layout
+ // system (e.g. cplatformdecoration.cpp's CUSTOM_BORDER_WIDTH * ratio).
+ // That's independent of Qt's own automatic HiDPI backing-store
+ // scaling (which just renders whatever DIP size is chosen more
+ // crisply) -- it's needed on Wayland exactly the same way it's
+ // needed on X11, so this used to (incorrectly) skip it with a flat
+ // 1.0 return on Wayland, leaving those widgets sized as if unscaled.
if (!wid)
return 1;
@@ -601,6 +602,36 @@ double Utils::getScreenDpiRatioByWidget(QWidget* wid)
return wid->devicePixelRatio();
}
+namespace {
+class CDpiChangeWatcher : public QObject
+{
+public:
+ CDpiChangeWatcher(QWidget* w, std::function onChange)
+ : QObject(w), m_onChange(std::move(onChange))
+ {
+ w->installEventFilter(this);
+ }
+
+protected:
+ bool eventFilter(QObject* watched, QEvent* event) override
+ {
+ if (event->type() == QEvent::DevicePixelRatioChange)
+ m_onChange();
+ return QObject::eventFilter(watched, event);
+ }
+
+private:
+ std::function m_onChange;
+};
+}
+
+void Utils::WatchForDpiChange(QWidget* w, std::function onChange)
+{
+ if (!w)
+ return;
+ new CDpiChangeWatcher(w, std::move(onChange));
+}
+
QScreen * Utils::screenAt(const QPoint& pt)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
@@ -717,6 +748,58 @@ bool Utils::updatesAllowed()
return false;
}
+bool Utils::defaultSaveFormatEnforced()
+{
+ GET_REGISTRY_SYSTEM(reg_system)
+ return reg_system.value("EnforceDefaultFormat", false).toBool();
+}
+
+bool Utils::defaultSaveFormatManaged()
+{
+ GET_REGISTRY_SYSTEM(reg_system)
+ return reg_system.value("EnforceDefaultFormat", false).toBool() ||
+ !reg_system.value("DefaultSaveFormat").toString().isEmpty();
+}
+
+QString Utils::defaultSaveFormat()
+{
+ GET_REGISTRY_SYSTEM(reg_system)
+ QString _format;
+
+ if ( reg_system.value("EnforceDefaultFormat", false).toBool() ) {
+ _format = reg_system.value("DefaultSaveFormat").toString();
+ } else {
+ /* the user scope falls back to the system scope by default, which would
+ * make an administrator's value indistinguishable from one the user has
+ * picked for themselves. the cascade must tell those two apart. */
+ GET_REGISTRY_USER(reg_user)
+ reg_user.setFallbacksEnabled(false);
+
+ _format = reg_user.value("DefaultSaveFormat").toString();
+ if ( _format.isEmpty() )
+ _format = reg_system.value("DefaultSaveFormat").toString();
+ }
+
+ return _format.compare(SAVE_FORMAT_ODF, Qt::CaseInsensitive) == 0 ?
+ SAVE_FORMAT_ODF : APP_DEFAULT_SAVE_FORMAT;
+}
+
+bool Utils::defaultSaveFormatChosen()
+{
+ GET_REGISTRY_USER(reg_user)
+ reg_user.setFallbacksEnabled(false);
+
+ return reg_user.value("FormatOnboardingShown", false).toBool() ||
+ !reg_user.value("DefaultSaveFormat").toString().isEmpty();
+}
+
+void Utils::keepDefaultSaveFormat(const QString& format)
+{
+ GET_REGISTRY_USER(reg_user)
+ reg_user.setValue("DefaultSaveFormat", format);
+ reg_user.setValue("FormatOnboardingShown", true);
+}
+
void Utils::addToRecent(const std::wstring &path)
{
#ifdef _WIN32
@@ -980,6 +1063,11 @@ namespace WindowHelper {
}
auto useGtkDialog() -> bool {
+ // On Wayland, prefer XDG Desktop Portal over GTK to avoid
+ // GTK dialogs falling back to Xwayland (wrong scale / click offset).
+ if (QGuiApplication::platformName() == "wayland")
+ return false;
+
GET_REGISTRY_USER(reg_user)
bool use_gtk_dialog = true;
bool saved_flag = reg_user.value("--xdg-desktop-portal", false).toBool();
diff --git a/win-linux/src/utils.h b/win-linux/src/utils.h
index 523e8ce55..6fec0d1ef 100644
--- a/win-linux/src/utils.h
+++ b/win-linux/src/utils.h
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#ifdef Q_OS_WIN
# include
#endif
@@ -90,10 +91,18 @@ class Utils {
static void openUrl(const QString&);
static void openFileLocation(const QString&);
static QString getPortalName(const QString&);
- static double getScreenDpiRatio(int);
static double getScreenDpiRatio(const QPoint&);
static double getScreenDpiRatioByHWND(int);
static double getScreenDpiRatioByWidget(QWidget*);
+
+ // Installs a watcher (child of w, cleaned up automatically with it) that
+ // calls onChange whenever w's own effective devicePixelRatio() changes
+ // (QEvent::DevicePixelRatioChange) -- e.g. once Qt receives the async
+ // Wayland fractional-scale compositor answer for w's surface, which can
+ // arrive after the widget's initial DPI-dependent sizing was already
+ // computed from a transiently-wrong reading. Safe to call multiple
+ // times on the same widget; each call adds an independent watcher.
+ static void WatchForDpiChange(QWidget* w, std::function onChange);
static QScreen * screenAt(const QPoint&);
static QString replaceBackslash(const QString&);
static std::wstring normalizeAppProtocolUrl(const std::wstring &url);
@@ -113,6 +122,11 @@ class Utils {
static QJsonObject parseJsonString(const std::wstring&);
static QJsonObject parseJsonFile(const QString&);
static bool updatesAllowed();
+ static QString defaultSaveFormat();
+ static bool defaultSaveFormatEnforced();
+ static bool defaultSaveFormatManaged();
+ static bool defaultSaveFormatChosen();
+ static void keepDefaultSaveFormat(const QString&);
static void addToRecent(const std::wstring&);
static void processMoreEvents(uint timeout = 60);
diff --git a/win-linux/src/windows/ceditorwindow.cpp b/win-linux/src/windows/ceditorwindow.cpp
index 9e8628d74..5348ec5ee 100644
--- a/win-linux/src/windows/ceditorwindow.cpp
+++ b/win-linux/src/windows/ceditorwindow.cpp
@@ -28,6 +28,7 @@
#include "iconfactory.h"
#include "defines.h"
#include
+#include
#include
#define CAPTURED_WINDOW_OFFSET_X (6*TOOLBTN_WIDTH + 10) * m_dpiRatio
@@ -176,7 +177,9 @@ void CEditorWindow::undock(bool maximized)
if (isCustomWindowStyle()) {
m_restoreMaximized = false;
CWindowPlatform::show(false);
- captureMouse();
+ if (QGuiApplication::platformName() != "wayland") {
+ captureMouse();
+ }
} else {
CWindowPlatform::show(false);
}
diff --git a/win-linux/src/windows/cmainwindow.cpp b/win-linux/src/windows/cmainwindow.cpp
index c2dc25786..b0a4dad57 100644
--- a/win-linux/src/windows/cmainwindow.cpp
+++ b/win-linux/src/windows/cmainwindow.cpp
@@ -39,6 +39,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/win-linux/src/windows/cpresenterwindow.cpp b/win-linux/src/windows/cpresenterwindow.cpp
index 15c9f33fa..6fe2ec533 100644
--- a/win-linux/src/windows/cpresenterwindow.cpp
+++ b/win-linux/src/windows/cpresenterwindow.cpp
@@ -29,6 +29,7 @@
#include "defines.h"
#include "utils.h"
#include
+#include
#include
#include
diff --git a/win-linux/src/windows/cwindowbase.cpp b/win-linux/src/windows/cwindowbase.cpp
index 8d125efe6..021270698 100644
--- a/win-linux/src/windows/cwindowbase.cpp
+++ b/win-linux/src/windows/cwindowbase.cpp
@@ -33,6 +33,7 @@
# include "windows/platform_win/caption.h"
#endif
#include
+#include
#include
#include
#include
@@ -67,9 +68,16 @@ CWindowBase::CWindowBase(const QRect& rect)
{
setWindowIcon(Utils::appIcon());
m_window_rect = startRect(rect, m_dpiRatio);
+
setMinimumSize(WINDOW_MIN_WIDTH * m_dpiRatio, WINDOW_MIN_HEIGHT * m_dpiRatio);
#ifdef __linux__
setGeometry(m_window_rect); // for Windows is set in CWindowPlatform
+
+ // m_dpiRatio above may have been computed from a transiently-wrong
+ // devicePixelRatio() (e.g. the startup Wayland compositor round-trip
+ // hasn't completed yet) -- re-derive and re-apply once Qt tells us
+ // this window's own ratio actually changed.
+ Utils::WatchForDpiChange(this, [this]() { updateScaling(); });
#endif
}
@@ -190,7 +198,13 @@ void CWindowBase::saveWindowState(const QString &baseKey)
{
if (!windowState().testFlag(Qt::WindowFullScreen)) {
GET_REGISTRY_USER(reg_user)
- reg_user.setValue(baseKey + "position", normalGeometry());
+ if (QGuiApplication::platformName() == "wayland") {
+ QRect rect = normalGeometry();
+ rect.moveTo(0, 0);
+ reg_user.setValue(baseKey + "position", rect);
+ } else {
+ reg_user.setValue(baseKey + "position", normalGeometry());
+ }
if (windowState().testFlag(Qt::WindowMaximized)) {
reg_user.setValue(baseKey + "maximized", true);
} else {
diff --git a/win-linux/src/windows/platform_linux/cplatformdecoration.cpp b/win-linux/src/windows/platform_linux/cplatformdecoration.cpp
index 57938ec58..861a190c8 100644
--- a/win-linux/src/windows/platform_linux/cplatformdecoration.cpp
+++ b/win-linux/src/windows/platform_linux/cplatformdecoration.cpp
@@ -32,10 +32,13 @@
#endif
#include
#include
+#ifdef HAVE_X11
#include "X11/Xlib.h"
#include "X11/cursorfont.h"
#include
+#endif
#include "platform_linux/linux_window_utils.h"
+#include "platform_linux/iplatformbackend.h"
#define CUSTOM_BORDER_WIDTH MAIN_WINDOW_BORDER_WIDTH
#define MOTION_TIMER_MS 250
@@ -50,6 +53,7 @@ const int k_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6;
const int k_NET_WM_MOVERESIZE_SIZE_LEFT = 7;
const int k_NET_WM_MOVERESIZE_MOVE = 8;
+#ifdef HAVE_X11
#define MWM_HINTS_DECORATIONS 2
typedef struct {
unsigned long flags;
@@ -209,7 +213,7 @@ namespace {
return false;
}
- auto supports_ewmh() -> bool {
+ bool supports_ewmh() {
static bool supports_ewmh = false;
static bool supports_ewmh_cached = false;
if (!supports_ewmh_cached) {
@@ -229,7 +233,7 @@ namespace {
return supports_ewmh;
}
- auto get_window_manager_name(std::string* wm_name) -> bool {
+ bool get_window_manager_name(std::string* wm_name) {
if ( supports_ewmh() ) {
int wm_window = 0;
if (get_int_property(getRootWindow(), "_NET_SUPPORTING_WM_CHECK", &wm_window)) {
@@ -240,7 +244,7 @@ namespace {
return false;
}
- auto guess_window_manager() -> Platform_WindowManagerName {
+ Platform_WindowManagerName guess_window_manager() {
std::string name;
if (!get_window_manager_name(&name)) return WM_UNNAMED;
if (name == "awesome") return WM_AWESOME;
@@ -267,27 +271,12 @@ namespace {
if (name == "xmonad") return WM_XMONAD;
return Platform_WindowManagerName::WM_OTHER;
}
-
}
+#endif
namespace WindowHelper {
auto check_button_state(Qt::MouseButton b) -> bool {
- Display * xdisplay_ = getXDisplay();
- Window x_root_window_ = DefaultRootWindow(xdisplay_);
-
- Window root_, child_;
- int root_x, root_y, child_x, child_y;
- uint mask;
-
- Bool res = XQueryPointer(xdisplay_, x_root_window_, &root_, &child_,
- &root_x, &root_y, &child_x, &child_y, &mask);
-
- if ( res ) {
- if ( b == Qt::LeftButton)
- return mask & Button1MotionMask;
- }
-
- return false;
+ return IPlatformBackend::instance()->checkButtonState(b);
}
}
@@ -295,23 +284,25 @@ CPlatformDecoration::CPlatformDecoration(QWidget * w)
: m_window(w)
, m_title(NULL)
, m_motionTimer(nullptr)
- , m_currentCursor(0)
+ , m_currentCursor(-1)
, m_decoration(true)
, m_nBorderSize(CUSTOM_BORDER_WIDTH)
, m_bIsMaximized(false)
, m_startSize(QSize())
{
- createCursors();
m_nDirection = -1;
- need_to_check_motion = guess_window_manager() == WM_KWIN;
+#ifdef HAVE_X11
+ need_to_check_motion = (QGuiApplication::platformName() == "xcb") && (guess_window_manager() == WM_KWIN);
+#else
+ need_to_check_motion = false;
+#endif
dpi_ratio = Utils::getScreenDpiRatioByWidget(w);
m_nBorderSize = CUSTOM_BORDER_WIDTH * dpi_ratio;
}
CPlatformDecoration::~CPlatformDecoration()
{
- freeCursors();
if ( m_motionTimer ) {
m_motionTimer->stop();
m_motionTimer->deleteLater();
@@ -325,29 +316,6 @@ void CPlatformDecoration::setTitleWidget(QWidget * w)
m_title->setMouseTracking(true);
}
-void CPlatformDecoration::createCursors()
-{
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_TOPLEFT] = XCreateFontCursor(getXDisplay(), XC_top_left_corner);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_TOP] = XCreateFontCursor(getXDisplay(), XC_top_side);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_TOPRIGHT] = XCreateFontCursor(getXDisplay(), XC_top_right_corner);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_RIGHT] = XCreateFontCursor(getXDisplay(), XC_right_side);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT] = XCreateFontCursor(getXDisplay(), XC_bottom_right_corner);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_BOTTOM] = XCreateFontCursor(getXDisplay(), XC_bottom_side);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT] = XCreateFontCursor(getXDisplay(), XC_bottom_left_corner);
- m_cursors[k_NET_WM_MOVERESIZE_SIZE_LEFT] = XCreateFontCursor(getXDisplay(), XC_left_side);
-}
-
-void CPlatformDecoration::freeCursors()
-{
- Display * _display = getXDisplay();
- std::for_each(m_cursors.begin(), m_cursors.end(),
- [_display](std::pair i) {
- if (_display)
- XFreeCursor(_display, i.second);
- }
- );
-}
-
int CPlatformDecoration::hitTest(int x, int y) const
{
if (m_bIsMaximized)
@@ -399,25 +367,16 @@ void CPlatformDecoration::checkCursor(QPoint & p)
{
int _hit_test = hitTest(p.x(), p.y());
- Cursor _cursor = 0;
- if (!(_hit_test < 0)) {
- _cursor = m_cursors[_hit_test];
- }
-
- Display * _display = getXDisplay();
- if (_cursor) {
- if (m_currentCursor == 0 || m_currentCursor != _cursor) {
- m_currentCursor = _cursor;
- XDefineCursor(_display, m_window->winId(), _cursor);
-
- XFlush(_display);
+ if (_hit_test >= 0) {
+ if (m_currentCursor != _hit_test) {
+ m_currentCursor = _hit_test;
+ IPlatformBackend::instance()->setCursor(m_window->winId(), _hit_test);
+ }
+ } else {
+ if (m_currentCursor != -1) {
+ m_currentCursor = -1;
+ IPlatformBackend::instance()->resetCursor(m_window->winId());
}
- } else
- if (m_currentCursor) {
- m_currentCursor = 0;
- XUndefineCursor(_display, m_window->winId());
-
- XFlush(_display);
}
}
@@ -451,46 +410,34 @@ void CPlatformDecoration::dispatchMouseMove(QMouseEvent *e)
}
} else {
m_motionTimer->stop();
- sendButtonRelease();
+ IPlatformBackend::instance()->sendButtonRelease(m_window);
QApplication::postEvent(m_window, new QEvent(static_cast(UM_ENDMOVE)));
m_window->activateWindow();
-// QTimer::singleShot(25, [=]() {
-// if (m_window->size() == m_startSize)
-// QApplication::postEvent(m_window, new QEvent(QEvent::User));
-// });
}
});
}
if (m_nDirection >= 0 && e->buttons() == Qt::LeftButton)
{
- Display * xdisplay_ = getXDisplay();
- Window x_root_window_ = DefaultRootWindow(xdisplay_);
-
- XUngrabPointer(xdisplay_, CurrentTime);
if ( !m_motionTimer->isActive() ) m_motionTimer->start(MOTION_TIMER_MS);
- XEvent event;
- memset(&event, 0, sizeof(event));
- event.xclient.type = ClientMessage;
- event.xclient.display = xdisplay_;
- event.xclient.window = m_window->winId();
-// event.xclient.message_type = XInternAtom(xdisplay_, "_NET_WM_MOVERESIZE", false);
- event.xclient.message_type = GetAtom("_NET_WM_MOVERESIZE");
- event.xclient.format = 32;
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- event.xclient.data.l[0] = e->globalPosition().toPoint().x();
- event.xclient.data.l[1] = e->globalPosition().toPoint().y();
-#else
- event.xclient.data.l[0] = e->globalPos().x();
- event.xclient.data.l[1] = e->globalPos().y();
-#endif
- event.xclient.data.l[2] = m_nDirection;
- event.xclient.data.l[3] = Button1;
- event.xclient.data.l[4] = 0;
-
- XSendEvent(xdisplay_, x_root_window_, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
- XFlush(xdisplay_);
+ QPoint globalPos = e->globalPosition().toPoint();
+ if (m_nDirection == k_NET_WM_MOVERESIZE_MOVE) {
+ IPlatformBackend::instance()->startInteractiveMove(m_window, globalPos);
+ } else {
+ Qt::Edges edges = Qt::Edges();
+ switch (m_nDirection) {
+ case k_NET_WM_MOVERESIZE_SIZE_TOPLEFT: edges = Qt::TopEdge | Qt::LeftEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_TOP: edges = Qt::TopEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_TOPRIGHT: edges = Qt::TopEdge | Qt::RightEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_RIGHT: edges = Qt::RightEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: edges = Qt::BottomEdge | Qt::RightEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_BOTTOM: edges = Qt::BottomEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: edges = Qt::BottomEdge | Qt::LeftEdge; break;
+ case k_NET_WM_MOVERESIZE_SIZE_LEFT: edges = Qt::LeftEdge; break;
+ }
+ IPlatformBackend::instance()->startInteractiveResize(m_window, edges, globalPos);
+ }
m_startSize = m_window->size();
m_nDirection = -1;
}
@@ -526,6 +473,7 @@ void CPlatformDecoration::turnOff()
m_decoration = false;
}
+#ifdef HAVE_X11
void CPlatformDecoration::switchDecoration(bool on)
{
if (m_decoration != on) {
@@ -548,6 +496,11 @@ void CPlatformDecoration::switchDecoration(bool on)
}
}
}
+#else
+void CPlatformDecoration::switchDecoration(bool)
+{
+}
+#endif
bool CPlatformDecoration::isDecorated()
{
@@ -577,66 +530,20 @@ int CPlatformDecoration::customWindowBorderWith()
void CPlatformDecoration::raiseWindow()
{
- Display *disp = getXDisplay();
- Atom atom_active_wnd = XInternAtom(disp, "_NET_ACTIVE_WINDOW", False);
- if (atom_active_wnd == None)
- return;
- Window wnd = (Window)m_window->winId();
- Window root = DefaultRootWindow(disp);
- XEvent event;
- memset(&event, 0, sizeof(XEvent));
- event.xclient.type = ClientMessage;
- event.xclient.serial = 0;
- event.xclient.send_event = True;
- event.xclient.message_type = atom_active_wnd;
- event.xclient.window = wnd;
- event.xclient.format = 32;
- XSendEvent(disp, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
- XMapRaised(disp, wnd);
- XFlush(disp);
+ IPlatformBackend::instance()->raiseWindow(m_window);
}
void CPlatformDecoration::sendButtonRelease()
{
- Display * xdisplay_ = getXDisplay();
- Window x_root_window_ = (Window)m_window->effectiveWinId();
-
- XEvent event;
- memset(&event, 0, sizeof(XEvent));
-
- event.type = ButtonRelease;
- event.xbutton.button = Button1;
- event.xbutton.same_screen = True;
-
-// event.xbutton.root = x_root_window_;
-// event.xbutton.window = m_window->winId();
-
- XQueryPointer(xdisplay_, x_root_window_, &event.xbutton.root, &event.xbutton.window,
- &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
- XSendEvent(xdisplay_, PointerWindow, True, ButtonReleaseMask, &event);
- XFlush(xdisplay_);
+ IPlatformBackend::instance()->sendButtonRelease(m_window);
}
void CPlatformDecoration::setCursorPos(int x, int y)
{
- Display *xdisplay_= getXDisplay();
- Window root_window = DefaultRootWindow(xdisplay_);
- XSelectInput(xdisplay_, root_window, KeyReleaseMask);
- XWarpPointer(xdisplay_, None, root_window, 0, 0, 0, 0, x, y);
- XFlush(xdisplay_);
+ IPlatformBackend::instance()->setCursorPos(x, y);
}
void CPlatformDecoration::setMinimized()
{
- XClientMessageEvent ev;
- ev.type = ClientMessage;
- ev.window = m_window->winId();
- ev.message_type = GetAtom("WM_CHANGE_STATE");
- ev.format = 32;
- ev.data.l[0] = IconicState;
-
- Display * xdisplay_ = getXDisplay();
- XSendEvent(xdisplay_, RootWindow(xdisplay_, DefaultScreen(xdisplay_)), False,
- SubstructureRedirectMask|SubstructureNotifyMask, (XEvent *)&ev);
- sendButtonRelease();
+ IPlatformBackend::instance()->minimizeWindow(m_window);
}
diff --git a/win-linux/src/windows/platform_linux/cplatformdecoration.h b/win-linux/src/windows/platform_linux/cplatformdecoration.h
index 61f3dc409..f9a510a56 100644
--- a/win-linux/src/windows/platform_linux/cplatformdecoration.h
+++ b/win-linux/src/windows/platform_linux/cplatformdecoration.h
@@ -66,17 +66,13 @@ class CPlatformDecoration
QWidget * m_window;
QWidget * m_title;
QTimer * m_motionTimer;
- ulong m_currentCursor;
+ int m_currentCursor;
bool m_decoration;
int m_nBorderSize;
bool m_bIsMaximized;
bool need_to_check_motion = false;
QSize m_startSize;
- std::map m_cursors;
-
- void createCursors();
- void freeCursors();
int hitTest(int x, int y) const;
void checkCursor(QPoint & p);
void switchDecoration(bool);
diff --git a/win-linux/src/windows/platform_linux/cwindowplatform.cpp b/win-linux/src/windows/platform_linux/cwindowplatform.cpp
index 9ea49d040..f941c1f9b 100644
--- a/win-linux/src/windows/platform_linux/cwindowplatform.cpp
+++ b/win-linux/src/windows/platform_linux/cwindowplatform.cpp
@@ -33,7 +33,11 @@
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include
#endif
+#ifdef HAVE_X11
#include
+#endif
+#include
+#include "platform_linux/iplatformbackend.h"
#ifdef DOCUMENTSCORE_OPENSSL_SUPPORT
# include "platform_linux/cdialogopenssl.h"
@@ -48,11 +52,7 @@ CWindowPlatform::CWindowPlatform(const QRect &rect) :
if (AscAppManager::isRtlEnabled())
setLayoutDirection(Qt::RightToLeft);
if (isCustomWindowStyle()) {
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- if (QX11Info::isCompositingManagerRunning())
-#else
- if (true)
-#endif
+ if (IPlatformBackend::instance()->isCompositingAvailable())
setAttribute(Qt::WA_TranslucentBackground);
CPlatformDecoration::turnOff();
}
@@ -140,6 +140,13 @@ bool CWindowPlatform::event(QEvent * event)
} else
if (event->type() == QEvent::WindowActivate) {
onWindowActivate(true);
+ if (QGuiApplication::platformName() == "wayland") {
+ focus();
+ m_propertyTimer->stop();
+ if (property("stabilized").toBool())
+ setProperty("stabilized", false);
+ m_propertyTimer->start();
+ }
}
else
if (event->type() == QEvent::WindowDeactivate) {
@@ -154,6 +161,7 @@ bool CWindowPlatform::nativeEvent(const QByteArray &ev_type, void *msg, qintptr
bool CWindowPlatform::nativeEvent(const QByteArray &ev_type, void *msg, long *res)
#endif
{
+#ifdef HAVE_X11
if (ev_type == "xcb_generic_event_t") {
xcb_generic_event_t *ev = static_cast(msg);
switch (ev->response_type & ~0x80) {
@@ -170,6 +178,7 @@ bool CWindowPlatform::nativeEvent(const QByteArray &ev_type, void *msg, long *re
break;
}
}
+#endif
return CWindowBase::nativeEvent(ev_type, msg, res);
}
@@ -181,11 +190,7 @@ void CWindowPlatform::setScreenScalingFactor(double factor, bool resize)
void CWindowPlatform::paintEvent(QPaintEvent *event)
{
-#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- if (!QX11Info::isCompositingManagerRunning()) {
-#else
- if (false) {
-#endif
+ if (!IPlatformBackend::instance()->isCompositingAvailable()) {
CWindowBase::paintEvent(event);
return;
}