Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f09b91ebb5 | ||
|
|
57b147e0c8 | ||
|
|
551ea7d879 | ||
|
|
d3c9bb0bc6 | ||
|
|
c711b1f1df | ||
|
|
af7ea90246 | ||
|
|
348cf4829c | ||
|
|
4753a57091 | ||
|
|
1a93f4fa4c | ||
|
|
118fd187e3 | ||
|
|
baa47bde7f | ||
|
|
18b48df9ce | ||
|
|
e71fc60d22 | ||
|
|
148af59615 | ||
|
|
5b2db4112f | ||
|
|
7cedc1f7a5 | ||
|
|
6cea7d4a52 | ||
|
|
bd93aed393 | ||
|
|
348666de6d | ||
|
|
47e32bebe4 | ||
|
|
0b21c04489 | ||
|
|
85f013ebdb | ||
|
|
832cc6ac69 | ||
|
|
30ce049f51 | ||
|
|
d42fb6d1b9 | ||
|
|
cade53aa0a | ||
|
|
2fdcda7536 | ||
|
|
7e6439e4f8 | ||
|
|
f07ee7f590 | ||
|
|
02db4e01fa | ||
|
|
8d75078a42 | ||
|
|
0e25ef7524 | ||
|
|
8608d8aa4d | ||
|
|
c1a7332a5e | ||
|
|
c3fb392906 | ||
|
|
a59bfdb2f8 | ||
|
|
79f96480c2 |
3
.github/workflows/linux.yml
vendored
3
.github/workflows/linux.yml
vendored
@@ -93,6 +93,9 @@ jobs:
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then
|
||||
DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON"
|
||||
fi
|
||||
echo Define from matrix: $DEFINE
|
||||
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
|
||||
else
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -76,9 +76,6 @@
|
||||
[submodule "Telegram/ThirdParty/hime"]
|
||||
path = Telegram/ThirdParty/hime
|
||||
url = https://github.com/hime-ime/hime.git
|
||||
[submodule "Telegram/ThirdParty/qt5ct"]
|
||||
path = Telegram/ThirdParty/qt5ct
|
||||
url = https://github.com/desktop-app/qt5ct.git
|
||||
[submodule "Telegram/ThirdParty/fcitx5-qt"]
|
||||
path = Telegram/ThirdParty/fcitx5-qt
|
||||
url = https://github.com/fcitx/fcitx5-qt.git
|
||||
@@ -91,9 +88,6 @@
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
[submodule "Telegram/ThirdParty/mallocng"]
|
||||
path = Telegram/ThirdParty/mallocng
|
||||
url = https://github.com/desktop-app/mallocng.git
|
||||
[submodule "Telegram/lib_waylandshells"]
|
||||
path = Telegram/lib_waylandshells
|
||||
url = https://github.com/desktop-app/lib_waylandshells.git
|
||||
|
||||
@@ -87,14 +87,12 @@ if (LINUX)
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
desktop-app::external_mallocng
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -114,6 +112,7 @@ if (LINUX)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
target_link_libraries(Telegram PRIVATE rt)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY)
|
||||
@@ -395,6 +394,7 @@ PRIVATE
|
||||
data/stickers/data_stickers_set.h
|
||||
data/stickers/data_stickers.cpp
|
||||
data/stickers/data_stickers.h
|
||||
data/data_abstract_sparse_ids.h
|
||||
data/data_abstract_structure.cpp
|
||||
data/data_abstract_structure.h
|
||||
data/data_auto_download.cpp
|
||||
@@ -863,8 +863,6 @@ PRIVATE
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_dummy.cpp
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
@@ -1196,8 +1194,6 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
platform/linux/linux_gdk_helper.h
|
||||
platform/linux/linux_gtk_file_dialog.cpp
|
||||
platform/linux/linux_gtk_file_dialog.h
|
||||
platform/linux/linux_gtk_integration_p.h
|
||||
platform/linux/linux_gtk_integration.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
@@ -1362,6 +1358,32 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32)
|
||||
target_link_options(Telegram
|
||||
PRIVATE
|
||||
/DELAYLOAD:secur32.dll
|
||||
/DELAYLOAD:winmm.dll
|
||||
/DELAYLOAD:ws2_32.dll
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:gdi32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:oleaut32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
/DELAYLOAD:iphlpapi.dll
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
|
||||
add_executable(Updater WIN32)
|
||||
init_target(Updater)
|
||||
@@ -1378,8 +1400,26 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
|
||||
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
if (WIN32)
|
||||
get_filename_component(lib_base_loc lib_base REALPATH)
|
||||
nice_target_sources(Updater ${lib_base_loc}
|
||||
PRIVATE
|
||||
base/platform/win/base_windows_safe_library.cpp
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater
|
||||
PRIVATE
|
||||
/DELAYLOAD:user32.dll
|
||||
/DELAYLOAD:advapi32.dll
|
||||
/DELAYLOAD:shell32.dll
|
||||
/DELAYLOAD:ole32.dll
|
||||
/DELAYLOAD:shlwapi.dll
|
||||
)
|
||||
else()
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
|
||||
@@ -452,6 +452,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_performance" = "Performance";
|
||||
"lng_settings_enable_animations" = "Enable animations";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
|
||||
"lng_settings_angle_backend" = "ANGLE graphics backend";
|
||||
"lng_settings_angle_backend_auto" = "Auto";
|
||||
"lng_settings_angle_backend_d3d9" = "Direct3D 9";
|
||||
"lng_settings_angle_backend_d3d11" = "Direct3D 11";
|
||||
"lng_settings_angle_backend_d3d11on12" = "D3D11on12";
|
||||
"lng_settings_angle_backend_opengl" = "OpenGL";
|
||||
"lng_settings_angle_backend_disabled" = "Disabled";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.8.3.0" />
|
||||
Version="2.8.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,8,3,0
|
||||
PRODUCTVERSION 2,8,3,0
|
||||
FILEVERSION 2,8,5,0
|
||||
PRODUCTVERSION 2,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.8.3.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.8.3.0"
|
||||
VALUE "ProductVersion", "2.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,8,3,0
|
||||
PRODUCTVERSION 2,8,3,0
|
||||
FILEVERSION 2,8,5,0
|
||||
PRODUCTVERSION 2,8,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.8.3.0"
|
||||
VALUE "FileVersion", "2.8.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.8.3.0"
|
||||
VALUE "ProductVersion", "2.8.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "updater.h"
|
||||
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
|
||||
@@ -329,6 +331,8 @@ void updateRegistry() {
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdParamarg, int cmdShow) {
|
||||
base::Platform::InitDynamicLibraries();
|
||||
|
||||
openLog();
|
||||
|
||||
_oldWndExceptionFilter = SetUnhandledExceptionFilter(_exceptionFilter);
|
||||
|
||||
@@ -223,6 +223,7 @@ void Panel::Incoming::RendererGL::paint(
|
||||
Assert(data.format == Webrtc::FrameFormat::YUV420);
|
||||
Assert(!data.yuv420->size.isEmpty());
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(f, 1);
|
||||
@@ -230,8 +231,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
@@ -243,8 +244,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -255,8 +256,8 @@ void Panel::Incoming::RendererGL::paint(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
|
||||
@@ -988,6 +988,7 @@ void Viewport::RendererGL::bindFrame(
|
||||
program.argb32->setUniformValue("s_texture", GLint(0));
|
||||
} else {
|
||||
const auto yuv = data.yuv420;
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
program.yuv420->bind();
|
||||
f.glActiveTexture(GL_TEXTURE0);
|
||||
tileData.textures.bind(f, 0);
|
||||
@@ -995,8 +996,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
tileData.textureSize,
|
||||
yuv->y.stride,
|
||||
@@ -1009,8 +1010,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -1021,8 +1022,8 @@ void Viewport::RendererGL::bindFrame(
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
f,
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
tileData.textureChromaSize,
|
||||
yuv->v.stride,
|
||||
@@ -1368,16 +1369,17 @@ void Viewport::RendererGL::validateNoiseTexture(
|
||||
if (_noiseTexture.created()) {
|
||||
return;
|
||||
}
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
_noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT);
|
||||
_noiseTexture.bind(f, 0);
|
||||
f.glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
kNoiseTextureSize,
|
||||
kNoiseTextureSize,
|
||||
0,
|
||||
GL_RED,
|
||||
format,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_lock_widgets.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/emoji_config.h"
|
||||
@@ -281,8 +282,7 @@ void Application::run() {
|
||||
LOG(("Shortcuts Error: %1").arg(error));
|
||||
}
|
||||
|
||||
if (!Platform::IsMac()
|
||||
&& Ui::Integration::Instance().openglLastCheckFailed()) {
|
||||
if (!Platform::IsMac() && Ui::GL::LastCrashCheckFailed()) {
|
||||
showOpenGLCrashNotification();
|
||||
}
|
||||
|
||||
@@ -297,14 +297,14 @@ void Application::run() {
|
||||
void Application::showOpenGLCrashNotification() {
|
||||
const auto enable = [=] {
|
||||
Ui::GL::ForceDisable(false);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(false);
|
||||
Local::writeSettings();
|
||||
App::restart();
|
||||
};
|
||||
const auto keepDisabled = [=] {
|
||||
Ui::GL::ForceDisable(true);
|
||||
Ui::Integration::Instance().openglCheckFinish();
|
||||
Ui::GL::CrashCheckFinish();
|
||||
Core::App().settings().setDisableOpenGL(true);
|
||||
Local::writeSettings();
|
||||
};
|
||||
|
||||
@@ -549,7 +549,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_disableOpenGL = (disableOpenGL == 1);
|
||||
if (!Platform::IsMac()) {
|
||||
Ui::GL::ForceDisable(_disableOpenGL
|
||||
|| Ui::Integration::Instance().openglLastCheckFailed());
|
||||
|| Ui::GL::LastCrashCheckFailed());
|
||||
}
|
||||
_groupCallNoiseSuppression = (groupCallNoiseSuppression == 1);
|
||||
const auto uncheckedWorkMode = static_cast<WorkMode>(workMode);
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
|
||||
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
virtual int exec();
|
||||
|
||||
QString argumentsString() const;
|
||||
bool customWorkingDir() const;
|
||||
|
||||
@@ -88,6 +88,10 @@ const auto kBadPrefix = u"http://"_q;
|
||||
return cWorkingDir() + "tdata/opengl_crash_check";
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ANGLEBackendFilePath() {
|
||||
return cWorkingDir() + "tdata/angle_backend";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void UiIntegration::postponeCall(FnMut<void()> &&callable) {
|
||||
@@ -106,20 +110,12 @@ QString UiIntegration::emojiCacheFolder() {
|
||||
return cWorkingDir() + "tdata/emoji";
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckStart() {
|
||||
auto f = QFile(OpenGLCheckFilePath());
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write("1", 1);
|
||||
f.close();
|
||||
}
|
||||
QString UiIntegration::openglCheckFilePath() {
|
||||
return OpenGLCheckFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::openglCheckFinish() {
|
||||
QFile::remove(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
bool UiIntegration::openglLastCheckFailed() {
|
||||
return OpenGLLastCheckFailed();
|
||||
QString UiIntegration::angleBackendFilePath() {
|
||||
return ANGLEBackendFilePath();
|
||||
}
|
||||
|
||||
void UiIntegration::textActionsUpdated() {
|
||||
|
||||
@@ -37,10 +37,8 @@ public:
|
||||
void unregisterLeaveSubscription(not_null<QWidget*> widget) override;
|
||||
|
||||
QString emojiCacheFolder() override;
|
||||
|
||||
void openglCheckStart() override;
|
||||
void openglCheckFinish() override;
|
||||
bool openglLastCheckFailed() override;
|
||||
QString openglCheckFilePath() override;
|
||||
QString angleBackendFilePath() override;
|
||||
|
||||
void textActionsUpdated() override;
|
||||
void activationFromTopPanel() override;
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 2008003;
|
||||
constexpr auto AppVersionStr = "2.8.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppVersion = 2008005;
|
||||
constexpr auto AppVersionStr = "2.8.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
78
Telegram/SourceFiles/data/data_abstract_sparse_ids.h
Normal file
78
Telegram/SourceFiles/data/data_abstract_sparse_ids.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
template <typename IdsContainer>
|
||||
class AbstractSparseIds {
|
||||
public:
|
||||
using Id = typename IdsContainer::value_type;
|
||||
|
||||
AbstractSparseIds() = default;
|
||||
AbstractSparseIds(
|
||||
const IdsContainer &ids,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> fullCount() const {
|
||||
return _fullCount;
|
||||
}
|
||||
std::optional<int> skippedBefore() const {
|
||||
return _skippedBefore;
|
||||
}
|
||||
std::optional<int> skippedAfter() const {
|
||||
return _skippedAfter;
|
||||
}
|
||||
std::optional<int> indexOf(Id id) const {
|
||||
const auto it = ranges::find(_ids, id);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
int size() const {
|
||||
return _ids.size();
|
||||
}
|
||||
Id operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
std::optional<int> distance(Id a, Id b) const {
|
||||
if (const auto i = indexOf(a)) {
|
||||
if (const auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<Id> nearest(Id id) const {
|
||||
if (const auto it = ranges::lower_bound(_ids, id); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
void reverse() {
|
||||
ranges::reverse(_ids);
|
||||
std::swap(_skippedBefore, _skippedAfter);
|
||||
}
|
||||
|
||||
private:
|
||||
IdsContainer _ids;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
};
|
||||
@@ -16,8 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_session.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -30,6 +32,51 @@ namespace {
|
||||
|
||||
using Type = Storage::SharedMediaType;
|
||||
|
||||
bool IsItemGoodForType(const not_null<HistoryItem*> item, Type type) {
|
||||
const auto media = item->media();
|
||||
if (!media || media->webpage()) {
|
||||
return false;
|
||||
}
|
||||
const auto photo = media->photo();
|
||||
const auto photoType = (type == Type::Photo);
|
||||
const auto photoVideoType = (type == Type::PhotoVideo);
|
||||
if ((photoType || photoVideoType) && photo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto document = media->document();
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
const auto voiceType = (type == Type::VoiceFile);
|
||||
const auto voiceDoc = document->isVoiceMessage();
|
||||
|
||||
const auto roundType = (type == Type::RoundFile);
|
||||
const auto roundDoc = document->isVideoMessage();
|
||||
|
||||
const auto audioType = (type == Type::MusicFile);
|
||||
const auto audioDoc = document->isAudioFile();
|
||||
|
||||
const auto gifType = (type == Type::GIF);
|
||||
const auto gifDoc = document->isGifv();
|
||||
|
||||
const auto videoType = (type == Type::Video);
|
||||
const auto videoDoc = document->isVideoFile();
|
||||
|
||||
const auto voiceRoundType = (type == Type::RoundVoiceFile);
|
||||
const auto fileType = (type == Type::File);
|
||||
|
||||
return (audioType && audioDoc)
|
||||
|| (voiceType && voiceDoc)
|
||||
|| (roundType && roundDoc)
|
||||
|| (voiceRoundType && (roundDoc || voiceDoc))
|
||||
|| (gifType && gifDoc)
|
||||
|| ((videoType || photoVideoType) && videoDoc)
|
||||
|| (fileType && (document->isTheme()
|
||||
|| document->isImage()
|
||||
|| !document->canBeStreamed()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
||||
@@ -153,6 +200,55 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(!IsServerMsgId(key.mergedKey.universalId));
|
||||
Expects((key.mergedKey.universalId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
|
||||
const auto history = session->data().history(key.mergedKey.peerId);
|
||||
|
||||
return rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
session->data().scheduledMessages().updates(history)
|
||||
) | rpl::map([=] {
|
||||
const auto list = session->data().scheduledMessages().list(history);
|
||||
|
||||
auto items = ranges::views::all(
|
||||
list.ids
|
||||
) | ranges::views::transform([=](const FullMsgId &fullId) {
|
||||
return session->data().message(fullId);
|
||||
}) | ranges::views::filter([=](HistoryItem *item) {
|
||||
return item
|
||||
? IsItemGoodForType(item, key.type)
|
||||
: false;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
ranges::sort(items, ranges::less(), &HistoryItem::position);
|
||||
|
||||
auto finishMsgIds = ranges::views::all(
|
||||
items
|
||||
) | ranges::views::transform([=](not_null<HistoryItem*> item) {
|
||||
return item->fullId().msg;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
const auto fullCount = finishMsgIds.size();
|
||||
|
||||
auto unsorted = SparseUnsortedIdsSlice(
|
||||
std::move(finishMsgIds),
|
||||
fullCount,
|
||||
list.skippedBefore,
|
||||
list.skippedAfter);
|
||||
return SparseIdsMergedSlice(
|
||||
key.mergedKey,
|
||||
std::move(unsorted));
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -374,12 +470,29 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return [=](auto consumer) {
|
||||
auto viewerKey = SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type);
|
||||
|
||||
if (std::get_if<not_null<PhotoData*>>(&key.universalId)) {
|
||||
return SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
consumer.put_next(SharedMediaWithLastSlice(
|
||||
session,
|
||||
key,
|
||||
std::move(update),
|
||||
std::nullopt));
|
||||
});
|
||||
}
|
||||
|
||||
if (key.scheduled) {
|
||||
return SharedScheduledMediaViewer(
|
||||
session,
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
|
||||
@@ -393,9 +506,7 @@ rpl::producer<SharedMediaWithLastSlice> SharedMediaWithLastViewer(
|
||||
return rpl::combine(
|
||||
SharedMediaMergedViewer(
|
||||
session,
|
||||
SharedMediaMergedKey(
|
||||
SharedMediaWithLastSlice::ViewerKey(key),
|
||||
key.type),
|
||||
std::move(viewerKey),
|
||||
limitBefore,
|
||||
limitAfter),
|
||||
SharedMediaMergedViewer(
|
||||
|
||||
@@ -50,6 +50,12 @@ struct SharedMediaMergedKey {
|
||||
|
||||
};
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
||||
not_null<Main::Session*> session,
|
||||
SharedMediaMergedKey key,
|
||||
@@ -71,11 +77,13 @@ public:
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
Type type,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, type(type)
|
||||
, universalId(universalId) {
|
||||
, universalId(universalId)
|
||||
, scheduled(scheduled) {
|
||||
Expects(v::is<MessageId>(universalId) || type == Type::ChatPhoto);
|
||||
}
|
||||
|
||||
@@ -93,6 +101,7 @@ public:
|
||||
PeerId migratedPeerId = 0;
|
||||
Type type = Type::kCount;
|
||||
UniversalMsgId universalId;
|
||||
bool scheduled = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -10,53 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <rpl/combine.h>
|
||||
#include "storage/storage_sparse_ids_list.h"
|
||||
|
||||
SparseIdsSlice::SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _ids(ids)
|
||||
, _range(range)
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::indexOf(MsgId msgId) const {
|
||||
auto it = _ids.find(msgId);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MsgId SparseIdsSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsSlice::distance(
|
||||
MsgId a,
|
||||
MsgId b) const {
|
||||
if (auto i = indexOf(a)) {
|
||||
if (auto j = indexOf(b)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<MsgId> SparseIdsSlice::nearest(MsgId msgId) const {
|
||||
if (auto it = ranges::lower_bound(_ids, msgId); it != _ids.end()) {
|
||||
return *it;
|
||||
} else if (_ids.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return _ids.back();
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(Key key)
|
||||
: SparseIdsMergedSlice(
|
||||
key,
|
||||
@@ -73,33 +26,48 @@ SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
, _migrated(std::move(migrated)) {
|
||||
}
|
||||
|
||||
SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled)
|
||||
: _key(key)
|
||||
, _scheduled(std::move(scheduled)) {
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::fullCount() const {
|
||||
return Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
return _scheduled
|
||||
? _scheduled->fullCount()
|
||||
: Add(
|
||||
_part.fullCount(),
|
||||
_migrated ? _migrated->fullCount() : 0);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedBefore()
|
||||
: Add(
|
||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||
_migrated
|
||||
? (isolatedInPart()
|
||||
? _migrated->fullCount()
|
||||
: _migrated->skippedBefore())
|
||||
: 0
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
||||
return Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
return _scheduled
|
||||
? _scheduled->skippedAfter()
|
||||
: Add(
|
||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
FullMsgId fullId) const {
|
||||
return isFromPart(fullId)
|
||||
return _scheduled
|
||||
? _scheduled->indexOf(fullId.msg)
|
||||
: isFromPart(fullId)
|
||||
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
|
||||
: isolatedInPart()
|
||||
? std::nullopt
|
||||
@@ -109,14 +77,20 @@ std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||
}
|
||||
|
||||
int SparseIdsMergedSlice::size() const {
|
||||
return (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
return _scheduled
|
||||
? _scheduled->size()
|
||||
: (isolatedInPart() ? 0 : migratedSize())
|
||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||
}
|
||||
|
||||
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
if (auto size = migratedSize()) {
|
||||
if (_scheduled) {
|
||||
return ComputeId(_key.peerId, (*_scheduled)[index]);
|
||||
}
|
||||
|
||||
if (const auto size = migratedSize()) {
|
||||
if (index < size) {
|
||||
return ComputeId(_key.migratedPeerId, (*_migrated)[index]);
|
||||
}
|
||||
@@ -128,8 +102,8 @@ FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||
std::optional<int> SparseIdsMergedSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
if (auto i = indexOf(ComputeId(a))) {
|
||||
if (auto j = indexOf(ComputeId(b))) {
|
||||
if (const auto i = indexOf(ComputeId(a))) {
|
||||
if (const auto j = indexOf(ComputeId(b))) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
@@ -138,10 +112,15 @@ std::optional<int> SparseIdsMergedSlice::distance(
|
||||
|
||||
auto SparseIdsMergedSlice::nearest(
|
||||
UniversalMsgId id) const -> std::optional<FullMsgId> {
|
||||
auto convertFromPartNearest = [&](MsgId result) {
|
||||
if (_scheduled) {
|
||||
if (const auto nearestId = _scheduled->nearest(id)) {
|
||||
return ComputeId(_key.peerId, *nearestId);
|
||||
}
|
||||
}
|
||||
const auto convertFromPartNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.peerId, result);
|
||||
};
|
||||
auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
const auto convertFromMigratedNearest = [&](MsgId result) {
|
||||
return ComputeId(_key.migratedPeerId, result);
|
||||
};
|
||||
if (IsServerMsgId(id)) {
|
||||
@@ -245,7 +224,6 @@ bool SparseIdsSliceBuilder::removeOne(MsgId messageId) {
|
||||
|
||||
bool SparseIdsSliceBuilder::removeAll() {
|
||||
_ids = {};
|
||||
_range = { 0, ServerMaxMsgId };
|
||||
_fullCount = 0;
|
||||
_skippedBefore = 0;
|
||||
_skippedAfter = 0;
|
||||
@@ -254,9 +232,6 @@ bool SparseIdsSliceBuilder::removeAll() {
|
||||
|
||||
bool SparseIdsSliceBuilder::invalidateBottom() {
|
||||
_fullCount = _skippedAfter = std::nullopt;
|
||||
if (_range.till == ServerMaxMsgId) {
|
||||
_range.till = _ids.empty() ? _range.from : _ids.back();
|
||||
}
|
||||
checkInsufficient();
|
||||
return true;
|
||||
}
|
||||
@@ -389,7 +364,6 @@ void SparseIdsSliceBuilder::requestMessagesCount() {
|
||||
SparseIdsSlice SparseIdsSliceBuilder::snapshot() const {
|
||||
return SparseIdsSlice(
|
||||
_ids,
|
||||
_range,
|
||||
_fullCount,
|
||||
_skippedBefore,
|
||||
_skippedAfter);
|
||||
@@ -440,4 +414,4 @@ rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
std::move(migrated)));
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_abstract_sparse_ids.h"
|
||||
#include "data/data_messages.h"
|
||||
|
||||
namespace Storage {
|
||||
@@ -14,36 +15,15 @@ struct SparseIdsListResult;
|
||||
struct SparseIdsSliceUpdate;
|
||||
} // namespace Storage
|
||||
|
||||
class SparseIdsSlice {
|
||||
class SparseIdsSlice final : public AbstractSparseIds<base::flat_set<MsgId>> {
|
||||
public:
|
||||
using Key = MsgId;
|
||||
|
||||
SparseIdsSlice() = default;
|
||||
SparseIdsSlice(
|
||||
const base::flat_set<MsgId> &ids,
|
||||
MsgRange range,
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter);
|
||||
|
||||
std::optional<int> fullCount() const { return _fullCount; }
|
||||
std::optional<int> skippedBefore() const { return _skippedBefore; }
|
||||
std::optional<int> skippedAfter() const { return _skippedAfter; }
|
||||
std::optional<int> indexOf(MsgId msgId) const;
|
||||
int size() const { return _ids.size(); }
|
||||
MsgId operator[](int index) const;
|
||||
std::optional<int> distance(MsgId a, MsgId b) const;
|
||||
std::optional<MsgId> nearest(MsgId msgId) const;
|
||||
|
||||
private:
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
using AbstractSparseIds<base::flat_set<MsgId>>::AbstractSparseIds;
|
||||
|
||||
};
|
||||
|
||||
using SparseUnsortedIdsSlice = AbstractSparseIds<std::vector<MsgId>>;
|
||||
|
||||
class SparseIdsMergedSlice {
|
||||
public:
|
||||
using UniversalMsgId = MsgId;
|
||||
@@ -51,9 +31,11 @@ public:
|
||||
Key(
|
||||
PeerId peerId,
|
||||
PeerId migratedPeerId,
|
||||
UniversalMsgId universalId)
|
||||
UniversalMsgId universalId,
|
||||
bool scheduled = false)
|
||||
: peerId(peerId)
|
||||
, migratedPeerId(migratedPeerId)
|
||||
, scheduled(scheduled)
|
||||
, migratedPeerId(scheduled ? 0 : migratedPeerId)
|
||||
, universalId(universalId) {
|
||||
}
|
||||
|
||||
@@ -67,6 +49,7 @@ public:
|
||||
}
|
||||
|
||||
PeerId peerId = 0;
|
||||
bool scheduled = false;
|
||||
PeerId migratedPeerId = 0;
|
||||
UniversalMsgId universalId = 0;
|
||||
|
||||
@@ -77,6 +60,9 @@ public:
|
||||
Key key,
|
||||
SparseIdsSlice part,
|
||||
std::optional<SparseIdsSlice> migrated);
|
||||
SparseIdsMergedSlice(
|
||||
Key key,
|
||||
SparseUnsortedIdsSlice scheduled);
|
||||
|
||||
std::optional<int> fullCount() const;
|
||||
std::optional<int> skippedBefore() const;
|
||||
@@ -155,6 +141,7 @@ private:
|
||||
Key _key;
|
||||
SparseIdsSlice _part;
|
||||
std::optional<SparseIdsSlice> _migrated;
|
||||
std::optional<SparseUnsortedIdsSlice> _scheduled;
|
||||
|
||||
};
|
||||
|
||||
@@ -205,7 +192,6 @@ private:
|
||||
|
||||
Key _key;
|
||||
base::flat_set<MsgId> _ids;
|
||||
MsgRange _range;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
@@ -64,39 +64,23 @@ UserPhotosSlice::UserPhotosSlice(
|
||||
std::optional<int> fullCount,
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter)
|
||||
: _key(key)
|
||||
, _ids(std::move(ids))
|
||||
, _fullCount(fullCount)
|
||||
, _skippedBefore(skippedBefore)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
: AbstractSparseIds<std::deque<PhotoId>>(
|
||||
ids,
|
||||
fullCount,
|
||||
skippedBefore,
|
||||
skippedAfter)
|
||||
, _key(key) {
|
||||
}
|
||||
|
||||
void UserPhotosSlice::reverse() {
|
||||
ranges::reverse(_ids);
|
||||
std::swap(_skippedBefore, _skippedAfter);
|
||||
}
|
||||
|
||||
std::optional<int> UserPhotosSlice::indexOf(PhotoId photoId) const {
|
||||
auto it = ranges::find(_ids, photoId);
|
||||
if (it != _ids.end()) {
|
||||
return (it - _ids.begin());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
PhotoId UserPhotosSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return *(_ids.begin() + index);
|
||||
}
|
||||
|
||||
std::optional<int> UserPhotosSlice::distance(const Key &a, const Key &b) const {
|
||||
std::optional<int> UserPhotosSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
if (a.userId != _key.userId
|
||||
|| b.userId != _key.userId) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (auto i = indexOf(a.photoId)) {
|
||||
if (auto j = indexOf(b.photoId)) {
|
||||
if (const auto i = indexOf(a.photoId)) {
|
||||
if (const auto j = indexOf(b.photoId)) {
|
||||
return *j - *i;
|
||||
}
|
||||
}
|
||||
@@ -248,4 +232,4 @@ rpl::producer<UserPhotosSlice> UserPhotosReversedViewer(
|
||||
slice.reverse();
|
||||
return std::move(slice);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_abstract_sparse_ids.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
@@ -14,7 +15,7 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class UserPhotosSlice {
|
||||
class UserPhotosSlice final : public AbstractSparseIds<std::deque<PhotoId>> {
|
||||
public:
|
||||
using Key = Storage::UserPhotosKey;
|
||||
|
||||
@@ -26,24 +27,11 @@ public:
|
||||
std::optional<int> skippedBefore,
|
||||
std::optional<int> skippedAfter);
|
||||
|
||||
void reverse();
|
||||
|
||||
const Key &key() const { return _key; }
|
||||
|
||||
std::optional<int> fullCount() const { return _fullCount; }
|
||||
std::optional<int> skippedBefore() const { return _skippedBefore; }
|
||||
std::optional<int> skippedAfter() const { return _skippedAfter; }
|
||||
std::optional<int> indexOf(PhotoId msgId) const;
|
||||
int size() const { return _ids.size(); }
|
||||
PhotoId operator[](int index) const;
|
||||
std::optional<int> distance(const Key &a, const Key &b) const;
|
||||
const Key &key() const { return _key; }
|
||||
|
||||
private:
|
||||
Key _key;
|
||||
std::deque<PhotoId> _ids;
|
||||
std::optional<int> _fullCount;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
|
||||
friend class UserPhotosSliceBuilder;
|
||||
|
||||
|
||||
@@ -70,7 +70,22 @@ rpl::producer<SparseIdsMergedSlice> AbstractController::mediaSource(
|
||||
int limitAfter) const {
|
||||
Expects(peer() != nullptr);
|
||||
|
||||
return SharedMediaMergedViewer(
|
||||
const auto isScheduled = [&] {
|
||||
if (IsServerMsgId(aroundId)) {
|
||||
return false;
|
||||
}
|
||||
const auto channelId = peerToChannel(peer()->id);
|
||||
if (const auto item = session().data().message(channelId, aroundId)) {
|
||||
return item->isScheduled();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
auto mediaViewer = isScheduled
|
||||
? SharedScheduledMediaViewer
|
||||
: SharedMediaMergedViewer;
|
||||
|
||||
return mediaViewer(
|
||||
&session(),
|
||||
SharedMediaMergedKey(
|
||||
SparseIdsMergedSlice::Key(
|
||||
|
||||
@@ -913,7 +913,7 @@ SparseIdsMergedSlice::Key ListWidget::sliceKey(
|
||||
|
||||
void ListWidget::refreshViewer() {
|
||||
_viewerLifetime.destroy();
|
||||
auto idForViewer = sliceKey(_universalAroundId).universalId;
|
||||
const auto idForViewer = sliceKey(_universalAroundId).universalId;
|
||||
_controller->mediaSource(
|
||||
idForViewer,
|
||||
_idsLimit,
|
||||
|
||||
@@ -269,7 +269,8 @@ bool Instance::validPlaylist(not_null<Data*> data) {
|
||||
using Key = SliceKey;
|
||||
const auto inSameDomain = [](const Key &a, const Key &b) {
|
||||
return (a.peerId == b.peerId)
|
||||
&& (a.migratedPeerId == b.migratedPeerId);
|
||||
&& (a.migratedPeerId == b.migratedPeerId)
|
||||
&& (a.scheduled == b.scheduled);
|
||||
};
|
||||
const auto countDistanceInData = [&](const Key &a, const Key &b) {
|
||||
return [&](const SparseIdsMergedSlice &data) {
|
||||
@@ -300,7 +301,11 @@ void Instance::validatePlaylist(not_null<Data*> data) {
|
||||
data->playlistLifetime.destroy();
|
||||
if (const auto key = playlistKey(data)) {
|
||||
data->playlistRequestedKey = key;
|
||||
SharedMediaMergedViewer(
|
||||
|
||||
const auto sharedMediaViewer = key->scheduled
|
||||
? SharedScheduledMediaViewer
|
||||
: SharedMediaMergedViewer;
|
||||
sharedMediaViewer(
|
||||
&data->history->session(),
|
||||
SharedMediaMergedKey(*key, data->overview),
|
||||
kIdsLimit,
|
||||
@@ -321,7 +326,11 @@ auto Instance::playlistKey(not_null<Data*> data) const
|
||||
-> std::optional<SliceKey> {
|
||||
const auto contextId = data->current.contextId();
|
||||
const auto history = data->history;
|
||||
if (!contextId || !history || !IsServerMsgId(contextId.msg)) {
|
||||
if (!contextId || !history) {
|
||||
return {};
|
||||
}
|
||||
const auto item = data->history->owner().message(contextId);
|
||||
if (!item || (!IsServerMsgId(contextId.msg) && !item->isScheduled())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -331,7 +340,8 @@ auto Instance::playlistKey(not_null<Data*> data) const
|
||||
return SliceKey(
|
||||
data->history->peer->id,
|
||||
data->migrated ? data->migrated->peer->id : 0,
|
||||
universalId);
|
||||
universalId,
|
||||
item->isScheduled());
|
||||
}
|
||||
|
||||
HistoryItem *Instance::itemByIndex(not_null<Data*> data, int index) {
|
||||
|
||||
@@ -244,7 +244,7 @@ void Panel::refreshList() {
|
||||
const auto document = media ? media->document() : nullptr;
|
||||
if (!document
|
||||
|| !document->isSharedMediaMusic()
|
||||
|| !IsServerMsgId(item->id)) {
|
||||
|| (!IsServerMsgId(item->id) && !item->isScheduled())) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto result = item->history()->peer;
|
||||
|
||||
@@ -193,6 +193,7 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
||||
|
||||
const auto upload = (_trackFrameIndex != data.index)
|
||||
|| (_streamedIndex != _owner->streamedIndex());
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
_trackFrameIndex = data.index;
|
||||
_streamedIndex = _owner->streamedIndex();
|
||||
|
||||
@@ -201,8 +202,8 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
||||
if (upload) {
|
||||
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
@@ -213,8 +214,8 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
||||
_textures.bind(*_f, 2);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -224,8 +225,8 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
||||
_textures.bind(*_f, 3);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
|
||||
@@ -508,8 +508,16 @@ void OverlayWidget::updateGeometry() {
|
||||
? window()->screen()
|
||||
: QApplication::primaryScreen();
|
||||
const auto available = screen->geometry();
|
||||
const auto useSizeHack = _opengl && Platform::IsWindows();
|
||||
const auto use = available.marginsAdded({ 0, 0, 0, 1 });
|
||||
const auto openglWidget = _opengl
|
||||
? static_cast<QOpenGLWidget*>(_widget.get())
|
||||
: nullptr;
|
||||
const auto useSizeHack = Platform::IsWindows()
|
||||
&& openglWidget
|
||||
&& (openglWidget->format().renderableType()
|
||||
!= QSurfaceFormat::OpenGLES);
|
||||
const auto use = useSizeHack
|
||||
? available.marginsAdded({ 0, 0, 0, 1 })
|
||||
: available;
|
||||
const auto mask = useSizeHack
|
||||
? QRegion(QRect(QPoint(), available.size()))
|
||||
: QRegion();
|
||||
@@ -1814,19 +1822,31 @@ auto OverlayWidget::sharedMediaKey() const -> std::optional<SharedMediaKey> {
|
||||
_photo
|
||||
};
|
||||
}
|
||||
if (!IsServerMsgId(_msgid.msg)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto keyForType = [this](SharedMediaType type) -> SharedMediaKey {
|
||||
const auto isServerMsgId = IsServerMsgId(_msgid.msg);
|
||||
const auto isScheduled = [&] {
|
||||
if (isServerMsgId) {
|
||||
return false;
|
||||
}
|
||||
if (const auto item = _session->data().message(_msgid)) {
|
||||
return item->isScheduled();
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
const auto keyForType = [&](SharedMediaType type) -> SharedMediaKey {
|
||||
return {
|
||||
_history->peer->id,
|
||||
_migrated ? _migrated->peer->id : 0,
|
||||
type,
|
||||
(_msgid.channel == _history->channelId()) ? _msgid.msg : (_msgid.msg - ServerMaxMsgId) };
|
||||
(_msgid.channel == _history->channelId())
|
||||
? _msgid.msg
|
||||
: (_msgid.msg - ServerMaxMsgId),
|
||||
isScheduled
|
||||
};
|
||||
};
|
||||
return
|
||||
sharedMediaType()
|
||||
| keyForType;
|
||||
if (!isServerMsgId && !isScheduled) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return sharedMediaType() | keyForType;
|
||||
}
|
||||
|
||||
Data::FileOrigin OverlayWidget::fileOrigin() const {
|
||||
@@ -1864,7 +1884,8 @@ bool OverlayWidget::validSharedMedia() const {
|
||||
auto inSameDomain = [](const Key &a, const Key &b) {
|
||||
return (a.type == b.type)
|
||||
&& (a.peerId == b.peerId)
|
||||
&& (a.migratedPeerId == b.migratedPeerId);
|
||||
&& (a.migratedPeerId == b.migratedPeerId)
|
||||
&& (a.scheduled == b.scheduled);
|
||||
};
|
||||
auto countDistanceInData = [&](const Key &a, const Key &b) {
|
||||
return [&](const SharedMediaWithLastSlice &data) {
|
||||
|
||||
@@ -297,13 +297,14 @@ void Pip::RendererGL::paintTransformedVideoFrame(
|
||||
const auto upload = (_trackFrameIndex != data.index);
|
||||
_trackFrameIndex = data.index;
|
||||
|
||||
const auto format = Ui::GL::CurrentSingleComponentFormat();
|
||||
_f->glActiveTexture(GL_TEXTURE0);
|
||||
_textures.bind(*_f, 1);
|
||||
if (upload) {
|
||||
_f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->size,
|
||||
_lumaSize,
|
||||
yuv->y.stride,
|
||||
@@ -314,8 +315,8 @@ void Pip::RendererGL::paintTransformedVideoFrame(
|
||||
_textures.bind(*_f, 2);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->u.stride,
|
||||
@@ -325,8 +326,8 @@ void Pip::RendererGL::paintTransformedVideoFrame(
|
||||
_textures.bind(*_f, 3);
|
||||
if (upload) {
|
||||
uploadTexture(
|
||||
GL_RED,
|
||||
GL_RED,
|
||||
format,
|
||||
format,
|
||||
yuv->chromaSize,
|
||||
_chromaSize,
|
||||
yuv->v.stride,
|
||||
|
||||
@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/90.0.4430.85 Safari/537.36");
|
||||
"Chrome/91.0.4472.114 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,21 @@ struct Panel::Progress {
|
||||
rpl::lifetime geometryLifetime;
|
||||
};
|
||||
|
||||
struct Panel::WebviewWithLifetime {
|
||||
WebviewWithLifetime(
|
||||
QWidget *parent = nullptr,
|
||||
Webview::WindowConfig config = Webview::WindowConfig());
|
||||
|
||||
Webview::Window window;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
Panel::WebviewWithLifetime::WebviewWithLifetime(
|
||||
QWidget *parent,
|
||||
Webview::WindowConfig config)
|
||||
: window(parent, std::move(config)) {
|
||||
}
|
||||
|
||||
Panel::Progress::Progress(QWidget *parent, Fn<QRect()> rect)
|
||||
: widget(parent)
|
||||
, animation(
|
||||
@@ -68,7 +83,7 @@ Panel::Panel(not_null<PanelDelegate*> delegate)
|
||||
}
|
||||
|
||||
Panel::~Panel() {
|
||||
// Destroy _widget before _webview.
|
||||
_webview = nullptr;
|
||||
_progress = nullptr;
|
||||
_widget = nullptr;
|
||||
}
|
||||
@@ -451,7 +466,7 @@ bool Panel::showWebview(
|
||||
}
|
||||
showWebviewProgress();
|
||||
_widget->destroyLayer();
|
||||
_webview->navigate(url);
|
||||
_webview->window.navigate(url);
|
||||
_widget->setBackAllowed(allowBack);
|
||||
if (bottomText) {
|
||||
const auto &padding = st::paymentsPanelPadding;
|
||||
@@ -490,14 +505,14 @@ bool Panel::createWebview() {
|
||||
}, bottom->lifetime());
|
||||
container->show();
|
||||
|
||||
_webview = std::make_unique<Webview::Window>(
|
||||
_webview = std::make_unique<WebviewWithLifetime>(
|
||||
container.get(),
|
||||
Webview::WindowConfig{
|
||||
.userDataPath = _delegate->panelWebviewDataPath(),
|
||||
});
|
||||
const auto raw = _webview.get();
|
||||
const auto raw = &_webview->window;
|
||||
QObject::connect(container.get(), &QObject::destroyed, [=] {
|
||||
if (_webview.get() == raw) {
|
||||
if (_webview && &_webview->window == raw) {
|
||||
_webview = nullptr;
|
||||
if (_webviewProgress) {
|
||||
hideWebviewProgress();
|
||||
@@ -517,7 +532,7 @@ bool Panel::createWebview() {
|
||||
container->geometryValue(
|
||||
) | rpl::start_with_next([=](QRect geometry) {
|
||||
raw->widget()->setGeometry(geometry);
|
||||
}, container->lifetime());
|
||||
}, _webview->lifetime);
|
||||
|
||||
raw->setMessageHandler([=](const QJsonDocument &message) {
|
||||
const auto save = _saveWebviewInformation
|
||||
|
||||
@@ -17,7 +17,6 @@ class Checkbox;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Webview {
|
||||
class Window;
|
||||
struct Available;
|
||||
} // namespace Webview
|
||||
|
||||
@@ -88,6 +87,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Progress;
|
||||
struct WebviewWithLifetime;
|
||||
|
||||
bool createWebview();
|
||||
void showWebviewProgress();
|
||||
@@ -103,7 +103,7 @@ private:
|
||||
|
||||
const not_null<PanelDelegate*> _delegate;
|
||||
std::unique_ptr<SeparatePanel> _widget;
|
||||
std::unique_ptr<Webview::Window> _webview;
|
||||
std::unique_ptr<WebviewWithLifetime> _webview;
|
||||
std::unique_ptr<RpWidget> _webviewBottom;
|
||||
std::unique_ptr<Progress> _progress;
|
||||
QPointer<Checkbox> _saveWebviewInformation;
|
||||
|
||||
@@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "storage/localstorage.h"
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "platform/linux/linux_xdp_file_dialog.h"
|
||||
@@ -18,7 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <QtCore/QProcess>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
@@ -91,73 +89,6 @@ void UnsafeLaunch(const QString &filepath) {
|
||||
} // namespace File
|
||||
|
||||
namespace FileDialog {
|
||||
namespace {
|
||||
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
bool GetQt(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
if (cDialogLastPath().isEmpty()) {
|
||||
InitLastPath();
|
||||
}
|
||||
|
||||
QFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setOptions(QFileDialog::DontUseNativeDialog);
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles)
|
||||
? QFileDialog::ExistingFiles
|
||||
: QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.setDirectory(QFileInfo(startFile).absoluteDir().absolutePath());
|
||||
if (type == Type::WriteFile) {
|
||||
dialog.selectFile(startFile);
|
||||
}
|
||||
|
||||
const auto res = dialog.exec();
|
||||
|
||||
if (type != Type::ReadFolder) {
|
||||
// Save last used directory for all queries except directory choosing.
|
||||
const auto path = dialog.directory().absolutePath();
|
||||
if (!path.isEmpty() && path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (type == Type::ReadFiles) {
|
||||
files = dialog.selectedFiles();
|
||||
} else {
|
||||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
@@ -186,32 +117,6 @@ bool Get(
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
const auto result = integration->getFileDialog(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
|
||||
if (result.has_value()) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
// avoid situation when portals don't work
|
||||
// and Qt tries to use portals as well
|
||||
if (InFlatpak() || InSnap()) {
|
||||
return GetQt(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
return ::FileDialog::internal::GetDefault(
|
||||
parent,
|
||||
files,
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
@@ -23,6 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
using Platform::internal::GtkIntegration;
|
||||
|
||||
class Arguments {
|
||||
public:
|
||||
void push(QByteArray argument) {
|
||||
@@ -45,7 +48,32 @@ private:
|
||||
} // namespace
|
||||
|
||||
Launcher::Launcher(int argc, char *argv[])
|
||||
: Core::Launcher(argc, argv) {
|
||||
: Core::Launcher(argc, argv)
|
||||
, _arguments(argv, argv + argc) {
|
||||
}
|
||||
|
||||
int Launcher::exec() {
|
||||
for (auto i = begin(_arguments), e = end(_arguments); i != e; ++i) {
|
||||
if (*i == "-basegtkintegration" && std::distance(i, e) > 2) {
|
||||
return GtkIntegration::Exec(
|
||||
GtkIntegration::Type::Base,
|
||||
QString::fromStdString(*(i + 1)),
|
||||
std::stoi(*(i + 2)));
|
||||
} else if (*i == "-webviewhelper" && std::distance(i, e) > 3) {
|
||||
return GtkIntegration::Exec(
|
||||
GtkIntegration::Type::Webview,
|
||||
QString::fromStdString(*(i + 1)),
|
||||
std::stoi(*(i + 2)),
|
||||
std::stoi(*(i + 3)));
|
||||
} else if (*i == "-gtkintegration" && std::distance(i, e) > 2) {
|
||||
return GtkIntegration::Exec(
|
||||
GtkIntegration::Type::TDesktop,
|
||||
QString::fromStdString(*(i + 1)),
|
||||
std::stoi(*(i + 2)));
|
||||
}
|
||||
}
|
||||
|
||||
return Core::Launcher::exec();
|
||||
}
|
||||
|
||||
void Launcher::initHook() {
|
||||
|
||||
@@ -15,10 +15,14 @@ class Launcher : public Core::Launcher {
|
||||
public:
|
||||
Launcher(int argc, char *argv[]);
|
||||
|
||||
int exec();
|
||||
|
||||
private:
|
||||
void initHook() override;
|
||||
bool launchUpdater(UpdaterLaunch action) override;
|
||||
|
||||
std::vector<std::string> _arguments;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
@@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/linux/base_linux_gtk_integration.h"
|
||||
#include "base/platform/linux/base_linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_wayland_integration.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
extern "C" {
|
||||
@@ -98,21 +95,17 @@ void GdkHelperLoad(QLibrary &lib) {
|
||||
}
|
||||
}
|
||||
|
||||
void GdkSetTransientFor(GdkWindow *window, QWindow *parent) {
|
||||
void GdkSetTransientFor(GdkWindow *window, const QString &parent) {
|
||||
#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
if (gdk_wayland_window_get_type != nullptr
|
||||
&& gdk_wayland_window_set_transient_for_exported != nullptr
|
||||
&& GDK_IS_WAYLAND_WINDOW(window)) {
|
||||
if (const auto integration = WaylandIntegration::Instance()) {
|
||||
if (const auto handle = integration->nativeHandle(parent)
|
||||
; !handle.isEmpty()) {
|
||||
auto handleUtf8 = handle.toUtf8();
|
||||
gdk_wayland_window_set_transient_for_exported(
|
||||
window,
|
||||
handleUtf8.data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
&& GDK_IS_WAYLAND_WINDOW(window)
|
||||
&& parent.startsWith("wayland:")) {
|
||||
auto handle = parent.mid(8).toUtf8();
|
||||
gdk_wayland_window_set_transient_for_exported(
|
||||
window,
|
||||
handle.data());
|
||||
return;
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
|
||||
@@ -121,23 +114,33 @@ void GdkSetTransientFor(GdkWindow *window, QWindow *parent) {
|
||||
&& gdk_x11_display_get_xdisplay != nullptr
|
||||
&& gdk_x11_window_get_xid != nullptr
|
||||
&& gdk_window_get_display != nullptr
|
||||
&& GDK_IS_X11_WINDOW(window)) {
|
||||
XSetTransientForHint(
|
||||
gdk_x11_display_get_xdisplay(gdk_window_get_display(window)),
|
||||
gdk_x11_window_get_xid(window),
|
||||
parent->winId());
|
||||
return;
|
||||
&& GDK_IS_X11_WINDOW(window)
|
||||
&& parent.startsWith("x11:")) {
|
||||
auto ok = false;
|
||||
const auto winId = parent.mid(4).toInt(&ok, 16);
|
||||
if (ok) {
|
||||
XSetTransientForHint(
|
||||
gdk_x11_display_get_xdisplay(gdk_window_get_display(window)),
|
||||
gdk_x11_window_get_xid(window),
|
||||
winId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
if (gdk_x11_drawable_get_xdisplay != nullptr
|
||||
&& gdk_x11_drawable_get_xid != nullptr) {
|
||||
XSetTransientForHint(
|
||||
gdk_x11_drawable_get_xdisplay(window),
|
||||
gdk_x11_drawable_get_xid(window),
|
||||
parent->winId());
|
||||
return;
|
||||
&& gdk_x11_drawable_get_xid != nullptr
|
||||
&& parent.startsWith("x11:")) {
|
||||
auto ok = false;
|
||||
const auto winId = parent.mid(4).toInt(&ok, 16);
|
||||
if (ok) {
|
||||
XSetTransientForHint(
|
||||
gdk_x11_drawable_get_xdisplay(window),
|
||||
gdk_x11_drawable_get_xid(window),
|
||||
winId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
class QLibrary;
|
||||
class QWindow;
|
||||
|
||||
extern "C" {
|
||||
#include <gtk/gtk.h>
|
||||
@@ -19,7 +18,7 @@ namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
void GdkHelperLoad(QLibrary &lib);
|
||||
void GdkSetTransientFor(GdkWindow *window, QWindow *parent);
|
||||
void GdkSetTransientFor(GdkWindow *window, const QString &parent);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
|
||||
@@ -1,708 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_gtk_file_dialog.h"
|
||||
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_desktop_environment.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qt_adapters.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <private/qguiapplication_p.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace Gtk {
|
||||
namespace {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
// GTK file chooser image preview: thanks to Chromium
|
||||
|
||||
// The size of the preview we display for selected image files. We set height
|
||||
// larger than width because generally there is more free space vertically
|
||||
// than horiztonally (setting the preview image will alway expand the width of
|
||||
// the dialog, but usually not the height). The image's aspect ratio will always
|
||||
// be preserved.
|
||||
constexpr auto kPreviewWidth = 256;
|
||||
constexpr auto kPreviewHeight = 512;
|
||||
|
||||
const char *filterRegExp =
|
||||
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
|
||||
|
||||
QStringList makeFilterList(const QString &filter) {
|
||||
QString f(filter);
|
||||
|
||||
if (f.isEmpty())
|
||||
return QStringList();
|
||||
|
||||
QString sep(QLatin1String(";;"));
|
||||
int i = f.indexOf(sep, 0);
|
||||
if (i == -1) {
|
||||
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
|
||||
sep = QLatin1Char('\n');
|
||||
i = f.indexOf(sep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return f.split(sep);
|
||||
}
|
||||
|
||||
// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)"
|
||||
QStringList cleanFilterList(const QString &filter) {
|
||||
QRegExp regexp(QString::fromLatin1(filterRegExp));
|
||||
Assert(regexp.isValid());
|
||||
QString f = filter;
|
||||
int i = regexp.indexIn(f);
|
||||
if (i >= 0)
|
||||
f = regexp.cap(2);
|
||||
return f.split(QLatin1Char(' '), base::QStringSkipEmptyParts);
|
||||
}
|
||||
|
||||
bool Supported() {
|
||||
return (gtk_widget_hide_on_delete != nullptr)
|
||||
&& (gtk_clipboard_store != nullptr)
|
||||
&& (gtk_clipboard_get != nullptr)
|
||||
&& (gtk_widget_destroy != nullptr)
|
||||
&& (gtk_dialog_get_type != nullptr)
|
||||
&& (gtk_dialog_run != nullptr)
|
||||
&& (gtk_widget_realize != nullptr)
|
||||
&& (gdk_window_set_modal_hint != nullptr)
|
||||
&& (gtk_widget_show != nullptr)
|
||||
&& (gdk_window_focus != nullptr)
|
||||
&& (gtk_widget_hide != nullptr)
|
||||
&& (gtk_widget_hide_on_delete != nullptr)
|
||||
&& (gtk_file_chooser_dialog_new != nullptr)
|
||||
&& (gtk_file_chooser_get_type != nullptr)
|
||||
&& (gtk_file_chooser_set_current_folder != nullptr)
|
||||
&& (gtk_file_chooser_get_current_folder != nullptr)
|
||||
&& (gtk_file_chooser_set_current_name != nullptr)
|
||||
&& (gtk_file_chooser_select_filename != nullptr)
|
||||
&& (gtk_file_chooser_get_filenames != nullptr)
|
||||
&& (gtk_file_chooser_set_filter != nullptr)
|
||||
&& (gtk_file_chooser_get_filter != nullptr)
|
||||
&& (gtk_window_get_type != nullptr)
|
||||
&& (gtk_window_set_title != nullptr)
|
||||
&& (gtk_file_chooser_set_local_only != nullptr)
|
||||
&& (gtk_file_chooser_set_action != nullptr)
|
||||
&& (gtk_file_chooser_set_select_multiple != nullptr)
|
||||
&& (gtk_file_chooser_set_do_overwrite_confirmation != nullptr)
|
||||
&& (gtk_file_chooser_remove_filter != nullptr)
|
||||
&& (gtk_file_filter_set_name != nullptr)
|
||||
&& (gtk_file_filter_add_pattern != nullptr)
|
||||
&& (gtk_file_chooser_add_filter != nullptr)
|
||||
&& (gtk_file_filter_new != nullptr);
|
||||
}
|
||||
|
||||
bool PreviewSupported() {
|
||||
return (gdk_pixbuf_new_from_file_at_size != nullptr);
|
||||
}
|
||||
|
||||
bool CustomButtonsSupported() {
|
||||
return (gtk_dialog_get_widget_for_response != nullptr)
|
||||
&& (gtk_button_set_label != nullptr)
|
||||
&& (gtk_button_get_type != nullptr);
|
||||
}
|
||||
|
||||
// This is a patched copy of qgtk2 theme plugin.
|
||||
// We need to use our own gtk file dialog instead of
|
||||
// styling Qt file dialog, because Qt only works with gtk2.
|
||||
// We need to be able to work with gtk2 and gtk3, because
|
||||
// we use gtk3 to work with appindicator3.
|
||||
class QGtkDialog : public QWindow {
|
||||
public:
|
||||
QGtkDialog(GtkWidget *gtkWidget);
|
||||
~QGtkDialog();
|
||||
|
||||
GtkDialog *gtkDialog() const;
|
||||
|
||||
void exec();
|
||||
void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hide();
|
||||
|
||||
rpl::producer<> accept();
|
||||
rpl::producer<> reject();
|
||||
|
||||
protected:
|
||||
static void onResponse(QGtkDialog *dialog, int response);
|
||||
|
||||
private:
|
||||
void onParentWindowDestroyed();
|
||||
|
||||
GtkWidget *gtkWidget = nullptr;
|
||||
|
||||
rpl::event_stream<> _accept;
|
||||
rpl::event_stream<> _reject;
|
||||
|
||||
bool _destroyedConnected = false;
|
||||
|
||||
};
|
||||
|
||||
class GtkFileDialog : public QDialog {
|
||||
public:
|
||||
GtkFileDialog(
|
||||
QWidget *parent = nullptr,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString(),
|
||||
const QString &filter = QString());
|
||||
~GtkFileDialog();
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
void setWindowTitle(const QString &windowTitle) {
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
|
||||
_acceptMode = acceptMode;
|
||||
}
|
||||
void setFileMode(QFileDialog::FileMode fileMode) {
|
||||
_fileMode = fileMode;
|
||||
}
|
||||
void setOption(QFileDialog::Option option, bool on = true) {
|
||||
if (on) {
|
||||
_options |= option;
|
||||
} else {
|
||||
_options &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
int exec() override;
|
||||
|
||||
bool defaultNameFilterDisables() const;
|
||||
void setDirectory(const QString &directory);
|
||||
QDir directory() const;
|
||||
void selectFile(const QString &filename);
|
||||
QStringList selectedFiles() const;
|
||||
void setFilter();
|
||||
void selectNameFilter(const QString &filter);
|
||||
QString selectedNameFilter() const;
|
||||
|
||||
private:
|
||||
static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper);
|
||||
static void onCurrentFolderChanged(GtkFileDialog *helper);
|
||||
static void onUpdatePreview(GtkDialog *gtkDialog, GtkFileDialog *helper);
|
||||
void applyOptions();
|
||||
void setNameFilters(const QStringList &filters);
|
||||
|
||||
void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
|
||||
void hideHelper();
|
||||
|
||||
void onAccepted();
|
||||
void onRejected();
|
||||
|
||||
// Options
|
||||
QFileDialog::Options _options;
|
||||
QString _windowTitle = "Choose file";
|
||||
QString _initialDirectory;
|
||||
QStringList _initialFiles;
|
||||
QStringList _nameFilters;
|
||||
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
|
||||
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
|
||||
|
||||
QString _dir;
|
||||
QStringList _selection;
|
||||
QHash<QString, GtkFileFilter*> _filters;
|
||||
QHash<GtkFileFilter*, QString> _filterNames;
|
||||
QScopedPointer<QGtkDialog> d;
|
||||
GtkWidget *_preview = nullptr;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
};
|
||||
|
||||
QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) {
|
||||
g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
|
||||
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
|
||||
}
|
||||
|
||||
QGtkDialog::~QGtkDialog() {
|
||||
gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
||||
gtk_widget_destroy(gtkWidget);
|
||||
}
|
||||
|
||||
GtkDialog *QGtkDialog::gtkDialog() const {
|
||||
return GTK_DIALOG(gtkWidget);
|
||||
}
|
||||
|
||||
void QGtkDialog::exec() {
|
||||
if (modality() == Qt::ApplicationModal) {
|
||||
// block input to the whole app, including other GTK dialogs
|
||||
gtk_dialog_run(gtkDialog());
|
||||
} else {
|
||||
// block input to the window, allow input to other GTK dialogs
|
||||
QEventLoop loop;
|
||||
rpl::lifetime lifetime;
|
||||
|
||||
accept(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
}, lifetime);
|
||||
|
||||
reject(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
}, lifetime);
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
if (!std::exchange(_destroyedConnected, true)) {
|
||||
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); });
|
||||
}
|
||||
setParent(parent);
|
||||
setFlags(flags);
|
||||
setModality(modality);
|
||||
|
||||
gtk_widget_realize(gtkWidget); // creates X window
|
||||
|
||||
if (parent) {
|
||||
internal::GdkSetTransientFor(gtk_widget_get_window(gtkWidget), parent);
|
||||
}
|
||||
|
||||
if (modality != Qt::NonModal) {
|
||||
gdk_window_set_modal_hint(gtk_widget_get_window(gtkWidget), true);
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
}
|
||||
|
||||
gtk_widget_show(gtkWidget);
|
||||
gdk_window_focus(gtk_widget_get_window(gtkWidget), 0);
|
||||
}
|
||||
|
||||
void QGtkDialog::hide() {
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
gtk_widget_hide(gtkWidget);
|
||||
}
|
||||
|
||||
rpl::producer<> QGtkDialog::accept() {
|
||||
return _accept.events();
|
||||
}
|
||||
|
||||
rpl::producer<> QGtkDialog::reject() {
|
||||
return _reject.events();
|
||||
}
|
||||
|
||||
void QGtkDialog::onResponse(QGtkDialog *dialog, int response) {
|
||||
if (response == GTK_RESPONSE_OK)
|
||||
dialog->_accept.fire({});
|
||||
else
|
||||
dialog->_reject.fire({});
|
||||
}
|
||||
|
||||
void QGtkDialog::onParentWindowDestroyed() {
|
||||
// The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it.
|
||||
setParent(nullptr);
|
||||
}
|
||||
|
||||
GtkFileDialog::GtkFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) : QDialog(parent)
|
||||
, _windowTitle(caption)
|
||||
, _initialDirectory(directory) {
|
||||
auto filters = makeFilterList(filter);
|
||||
const int numFilters = filters.count();
|
||||
_nameFilters.reserve(numFilters);
|
||||
for (int i = 0; i < numFilters; ++i) {
|
||||
_nameFilters << filters[i].simplified();
|
||||
}
|
||||
|
||||
d.reset(new QGtkDialog(gtk_file_chooser_dialog_new("", nullptr,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
tr::lng_cancel(tr::now).toUtf8().constData(), GTK_RESPONSE_CANCEL,
|
||||
tr::lng_box_ok(tr::now).toUtf8().constData(), GTK_RESPONSE_OK, nullptr)));
|
||||
|
||||
d.data()->accept(
|
||||
) | rpl::start_with_next([=] {
|
||||
onAccepted();
|
||||
}, _lifetime);
|
||||
|
||||
d.data()->reject(
|
||||
) | rpl::start_with_next([=] {
|
||||
onRejected();
|
||||
}, _lifetime);
|
||||
|
||||
g_signal_connect(GTK_FILE_CHOOSER(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
|
||||
g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
|
||||
|
||||
if (PreviewSupported()) {
|
||||
_preview = gtk_image_new();
|
||||
g_signal_connect(G_OBJECT(d->gtkDialog()), "update-preview", G_CALLBACK(onUpdatePreview), this);
|
||||
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(d->gtkDialog()), _preview);
|
||||
}
|
||||
}
|
||||
|
||||
GtkFileDialog::~GtkFileDialog() {
|
||||
}
|
||||
|
||||
void GtkFileDialog::showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
|
||||
_dir.clear();
|
||||
_selection.clear();
|
||||
|
||||
applyOptions();
|
||||
return d->show(flags, modality, parent);
|
||||
}
|
||||
|
||||
void GtkFileDialog::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
|
||||
} else {
|
||||
hideHelper();
|
||||
}
|
||||
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
int GtkFileDialog::exec() {
|
||||
d->setModality(windowModality());
|
||||
|
||||
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
|
||||
setAttribute(Qt::WA_ShowModal, true);
|
||||
setResult(0);
|
||||
|
||||
show();
|
||||
|
||||
QPointer<QDialog> guard = this;
|
||||
d->exec();
|
||||
if (guard.isNull())
|
||||
return QDialog::Rejected;
|
||||
|
||||
setAttribute(Qt::WA_ShowModal, wasShowModal);
|
||||
|
||||
return result();
|
||||
}
|
||||
|
||||
void GtkFileDialog::hideHelper() {
|
||||
// After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder()
|
||||
// & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual
|
||||
// values before hiding the dialog
|
||||
_dir = directory().absolutePath();
|
||||
_selection = selectedFiles();
|
||||
|
||||
d->hide();
|
||||
}
|
||||
|
||||
bool GtkFileDialog::defaultNameFilterDisables() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setDirectory(const QString &directory) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), directory.toUtf8().constData());
|
||||
}
|
||||
|
||||
QDir GtkFileDialog::directory() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_dir.isEmpty())
|
||||
return _dir;
|
||||
|
||||
QString ret;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(gtkDialog));
|
||||
if (folder) {
|
||||
ret = QString::fromUtf8(folder);
|
||||
g_free(folder);
|
||||
}
|
||||
return QDir(ret);
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectFile(const QString &filename) {
|
||||
_initialFiles.clear();
|
||||
_initialFiles.append(filename);
|
||||
}
|
||||
|
||||
QStringList GtkFileDialog::selectedFiles() const {
|
||||
// While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames()
|
||||
// returns a bogus value -> return the cached value before hiding
|
||||
if (!_selection.isEmpty())
|
||||
return _selection;
|
||||
|
||||
QStringList selection;
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(gtkDialog));
|
||||
for (GSList *it = filenames; it; it = it->next)
|
||||
selection += QString::fromUtf8((const char*)it->data);
|
||||
g_slist_free(filenames);
|
||||
return selection;
|
||||
}
|
||||
|
||||
void GtkFileDialog::setFilter() {
|
||||
applyOptions();
|
||||
}
|
||||
|
||||
void GtkFileDialog::selectNameFilter(const QString &filter) {
|
||||
GtkFileFilter *gtkFilter = _filters.value(filter);
|
||||
if (gtkFilter) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(gtkDialog), gtkFilter);
|
||||
}
|
||||
}
|
||||
|
||||
QString GtkFileDialog::selectedNameFilter() const {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(gtkDialog));
|
||||
return _filterNames.value(gtkFilter);
|
||||
}
|
||||
|
||||
void GtkFileDialog::onAccepted() {
|
||||
accept();
|
||||
|
||||
// QString filter = selectedNameFilter();
|
||||
// if (filter.isEmpty())
|
||||
// emit filterSelected(filter);
|
||||
|
||||
// QList<QUrl> files = selectedFiles();
|
||||
// emit filesSelected(files);
|
||||
// if (files.count() == 1)
|
||||
// emit fileSelected(files.first());
|
||||
}
|
||||
|
||||
void GtkFileDialog::onRejected() {
|
||||
reject();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
void GtkFileDialog::onSelectionChanged(GtkDialog *gtkDialog, GtkFileDialog *helper) {
|
||||
// QString selection;
|
||||
// gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog));
|
||||
// if (filename) {
|
||||
// selection = QString::fromUtf8(filename);
|
||||
// g_free(filename);
|
||||
// }
|
||||
// emit helper->currentChanged(QUrl::fromLocalFile(selection));
|
||||
}
|
||||
|
||||
void GtkFileDialog::onCurrentFolderChanged(GtkFileDialog *dialog) {
|
||||
// emit dialog->directoryEntered(dialog->directory());
|
||||
}
|
||||
|
||||
void GtkFileDialog::onUpdatePreview(GtkDialog *gtkDialog, GtkFileDialog *helper) {
|
||||
auto filename = gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(gtkDialog));
|
||||
if (!filename) {
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't attempt to open anything which isn't a regular file. If a named pipe,
|
||||
// this may hang. See https://crbug.com/534754.
|
||||
struct stat stat_buf;
|
||||
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
|
||||
g_free(filename);
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), false);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will preserve the image's aspect ratio.
|
||||
auto pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr);
|
||||
g_free(filename);
|
||||
if (pixbuf) {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(helper->_preview), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), pixbuf ? true : false);
|
||||
}
|
||||
|
||||
GtkFileChooserAction gtkFileChooserAction(QFileDialog::FileMode fileMode, QFileDialog::AcceptMode acceptMode) {
|
||||
switch (fileMode) {
|
||||
case QFileDialog::AnyFile:
|
||||
case QFileDialog::ExistingFile:
|
||||
case QFileDialog::ExistingFiles:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
case QFileDialog::Directory:
|
||||
default:
|
||||
if (acceptMode == QFileDialog::AcceptOpen)
|
||||
return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
else
|
||||
return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::applyOptions() {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
|
||||
gtk_window_set_title(GTK_WINDOW(gtkDialog), _windowTitle.toUtf8().constData());
|
||||
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(gtkDialog), true);
|
||||
|
||||
const GtkFileChooserAction action = gtkFileChooserAction(_fileMode, _acceptMode);
|
||||
gtk_file_chooser_set_action(GTK_FILE_CHOOSER(gtkDialog), action);
|
||||
|
||||
const bool selectMultiple = (_fileMode == QFileDialog::ExistingFiles);
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(gtkDialog), selectMultiple);
|
||||
|
||||
const bool confirmOverwrite = !_options.testFlag(QFileDialog::DontConfirmOverwrite);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(gtkDialog), confirmOverwrite);
|
||||
|
||||
if (!_nameFilters.isEmpty())
|
||||
setNameFilters(_nameFilters);
|
||||
|
||||
if (!_initialDirectory.isEmpty())
|
||||
setDirectory(_initialDirectory);
|
||||
|
||||
for_const (const auto &filename, _initialFiles) {
|
||||
if (_acceptMode == QFileDialog::AcceptSave) {
|
||||
QFileInfo fi(filename);
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), fi.path().toUtf8().constData());
|
||||
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(gtkDialog), fi.fileName().toUtf8().constData());
|
||||
} else if (filename.endsWith('/')) {
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), filename.toUtf8().constData());
|
||||
} else {
|
||||
gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(gtkDialog), filename.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
const QString initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
|
||||
if (!initialNameFilter.isEmpty())
|
||||
selectNameFilter(initialNameFilter);
|
||||
|
||||
if (CustomButtonsSupported()) {
|
||||
GtkWidget *acceptButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_OK);
|
||||
if (acceptButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept))
|
||||
gtk_button_set_label(GTK_BUTTON(acceptButton), opts->labelText(QFileDialogOptions::Accept).toUtf8().constData());
|
||||
else*/ if (_acceptMode == QFileDialog::AcceptOpen)
|
||||
gtk_button_set_label(GTK_BUTTON(acceptButton), tr::lng_open_link(tr::now).toUtf8().constData());
|
||||
else
|
||||
gtk_button_set_label(GTK_BUTTON(acceptButton), tr::lng_settings_save(tr::now).toUtf8().constData());
|
||||
}
|
||||
|
||||
GtkWidget *rejectButton = gtk_dialog_get_widget_for_response(gtkDialog, GTK_RESPONSE_CANCEL);
|
||||
if (rejectButton) {
|
||||
/*if (opts->isLabelExplicitlySet(QFileDialogOptions::Reject))
|
||||
gtk_button_set_label(GTK_BUTTON(rejectButton), opts->labelText(QFileDialogOptions::Reject).toUtf8().constData());
|
||||
else*/
|
||||
gtk_button_set_label(GTK_BUTTON(rejectButton), tr::lng_cancel(tr::now).toUtf8().constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GtkFileDialog::setNameFilters(const QStringList &filters) {
|
||||
GtkDialog *gtkDialog = d->gtkDialog();
|
||||
Q_FOREACH (GtkFileFilter *filter, _filters)
|
||||
gtk_file_chooser_remove_filter(GTK_FILE_CHOOSER(gtkDialog), filter);
|
||||
|
||||
_filters.clear();
|
||||
_filterNames.clear();
|
||||
|
||||
for_const (auto &filter, filters) {
|
||||
GtkFileFilter *gtkFilter = gtk_file_filter_new();
|
||||
auto name = filter;//.left(filter.indexOf(QLatin1Char('(')));
|
||||
auto extensions = cleanFilterList(filter);
|
||||
|
||||
gtk_file_filter_set_name(gtkFilter, name.isEmpty() ? extensions.join(QStringLiteral(", ")).toUtf8().constData() : name.toUtf8().constData());
|
||||
for_const (auto &ext, extensions) {
|
||||
auto caseInsensitiveExt = QString();
|
||||
caseInsensitiveExt.reserve(4 * ext.size());
|
||||
for_const (auto ch, ext) {
|
||||
auto chLower = ch.toLower();
|
||||
auto chUpper = ch.toUpper();
|
||||
if (chLower != chUpper) {
|
||||
caseInsensitiveExt.append('[').append(chLower).append(chUpper).append(']');
|
||||
} else {
|
||||
caseInsensitiveExt.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_file_filter_add_pattern(gtkFilter, caseInsensitiveExt.toUtf8().constData());
|
||||
}
|
||||
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(gtkDialog), gtkFilter);
|
||||
|
||||
_filters.insert(filter, gtkFilter);
|
||||
_filterNames.insert(gtkFilter, filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<bool> Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
if (!Supported()
|
||||
|| (!qEnvironmentVariableIsSet("TDESKTOP_USE_GTK_FILE_DIALOG")
|
||||
&& !DesktopEnvironment::IsGtkBased())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (cDialogLastPath().isEmpty()) {
|
||||
InitLastPath();
|
||||
}
|
||||
|
||||
GtkFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.selectFile(startFile);
|
||||
|
||||
const auto res = dialog.exec();
|
||||
|
||||
if (type != Type::ReadFolder) {
|
||||
// Save last used directory for all queries except directory choosing.
|
||||
const auto path = dialog.directory().absolutePath();
|
||||
if (!path.isEmpty() && path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (type == Type::ReadFiles) {
|
||||
files = dialog.selectedFiles();
|
||||
} else {
|
||||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Gtk
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace Gtk {
|
||||
|
||||
std::optional<bool> Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile);
|
||||
|
||||
} // namespace Gtk
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
@@ -7,12 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_gtk_integration.h"
|
||||
|
||||
#ifdef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#error "GTK integration depends on D-Bus integration."
|
||||
#endif // DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
#include "base/platform/linux/base_linux_gtk_integration.h"
|
||||
#include "base/platform/linux/base_linux_gtk_integration_p.h"
|
||||
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
||||
#include "base/platform/linux/base_linux_dbus_utilities.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "platform/linux/linux_gtk_file_dialog.h"
|
||||
#include "platform/linux/linux_gtk_open_with_dialog.h"
|
||||
#include "platform/linux/linux_wayland_integration.h"
|
||||
#include "webview/webview_interface.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include <QtCore/QProcess>
|
||||
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include <giomm.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
@@ -22,6 +41,31 @@ using BaseGtkIntegration = base::Platform::GtkIntegration;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kService = "org.telegram.desktop.GtkIntegration-%1"_cs;
|
||||
constexpr auto kObjectPath = "/org/telegram/desktop/GtkIntegration"_cs;
|
||||
constexpr auto kInterface = "org.telegram.desktop.GtkIntegration"_cs;
|
||||
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||
constexpr auto kGifcShmId = "tdesktop-gtk-gifc"_cs;
|
||||
|
||||
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
|
||||
<interface name='org.telegram.desktop.GtkIntegration'>
|
||||
<method name='Load'>
|
||||
<arg type='s' name='allowed-backends' direction='in'/>
|
||||
</method>
|
||||
<method name='ShowOpenWithDialog'>
|
||||
<arg type='s' name='parent' direction='in'/>
|
||||
<arg type='s' name='filepath' direction='in'/>
|
||||
</method>
|
||||
<method name='GetImageFromClipboard'>
|
||||
<arg type='h' name='shm-descriptor' direction='out'/>
|
||||
<arg type='i' name='shm-size' direction='out'/>
|
||||
</method>
|
||||
<signal name='OpenWithDialogResponse'>
|
||||
<arg type='b' name='result' direction='out'/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>)INTROSPECTION"_cs;
|
||||
|
||||
bool GetImageFromClipboardSupported() {
|
||||
return (gtk_clipboard_get != nullptr)
|
||||
&& (gtk_clipboard_wait_for_contents != nullptr)
|
||||
@@ -38,7 +82,182 @@ bool GetImageFromClipboardSupported() {
|
||||
|
||||
} // namespace
|
||||
|
||||
GtkIntegration::GtkIntegration() {
|
||||
class GtkIntegration::Private {
|
||||
public:
|
||||
Private()
|
||||
: dbusConnection([] {
|
||||
try {
|
||||
return Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
} catch (...) {
|
||||
return Glib::RefPtr<Gio::DBus::Connection>();
|
||||
}
|
||||
}())
|
||||
, interfaceVTable(sigc::mem_fun(this, &Private::handleMethodCall))
|
||||
, serviceName(kService.utf16().arg(getpid()).toStdString()) {
|
||||
}
|
||||
|
||||
void handleMethodCall(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &method_name,
|
||||
const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation);
|
||||
|
||||
const Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
|
||||
const Gio::DBus::InterfaceVTable interfaceVTable;
|
||||
Glib::RefPtr<Gio::DBus::NodeInfo> introspectionData;
|
||||
Glib::ustring serviceName;
|
||||
Glib::ustring parentDBusName;
|
||||
bool remoting = true;
|
||||
uint registerId = 0;
|
||||
uint parentServiceWatcherId = 0;
|
||||
};
|
||||
|
||||
void GtkIntegration::Private::handleMethodCall(
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &method_name,
|
||||
const Glib::VariantContainerBase ¶meters,
|
||||
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation) {
|
||||
if (sender != parentDBusName) {
|
||||
Gio::DBus::Error error(
|
||||
Gio::DBus::Error::ACCESS_DENIED,
|
||||
"Access denied.");
|
||||
|
||||
invocation->return_error(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const auto integration = Instance();
|
||||
if (!integration) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
auto parametersCopy = parameters;
|
||||
|
||||
if (method_name == "Load") {
|
||||
const auto allowedBackends = base::Platform::GlibVariantCast<
|
||||
Glib::ustring>(parametersCopy.get_child(0));
|
||||
|
||||
integration->load(QString::fromStdString(allowedBackends));
|
||||
invocation->return_value({});
|
||||
return;
|
||||
} else if (method_name == "ShowOpenWithDialog") {
|
||||
const auto parent = base::Platform::GlibVariantCast<
|
||||
Glib::ustring>(parametersCopy.get_child(0));
|
||||
|
||||
const auto filepath = base::Platform::GlibVariantCast<
|
||||
Glib::ustring>(parametersCopy.get_child(1));
|
||||
|
||||
const auto result = File::internal::ShowGtkOpenWithDialog(
|
||||
QString::fromStdString(parent),
|
||||
QString::fromStdString(filepath));
|
||||
|
||||
if (result) {
|
||||
invocation->return_value({});
|
||||
return;
|
||||
}
|
||||
} else if (method_name == "GetImageFromClipboard") {
|
||||
const auto image = integration->getImageFromClipboard();
|
||||
if (!image.isNull()) {
|
||||
const auto bitsPerSample = 8;
|
||||
const auto channels = image.hasAlphaChannel() ? 4 : 3;
|
||||
|
||||
QVector<uchar> dataVector(
|
||||
image.constBits(),
|
||||
image.constBits() + image.sizeInBytes());
|
||||
|
||||
QByteArray streamData;
|
||||
QDataStream stream(&streamData, QIODevice::WriteOnly);
|
||||
stream
|
||||
<< image.width()
|
||||
<< image.height()
|
||||
<< image.bytesPerLine()
|
||||
<< image.hasAlphaChannel()
|
||||
<< bitsPerSample
|
||||
<< channels
|
||||
<< dataVector;
|
||||
|
||||
const auto fd = shm_open(
|
||||
kGifcShmId.utf8().constData(),
|
||||
O_RDWR | O_CREAT,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
if (fd == -1) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
const auto fdGuard = gsl::finally([&] {
|
||||
close(fd);
|
||||
shm_unlink(kGifcShmId.utf8().constData());
|
||||
});
|
||||
|
||||
if (ftruncate(fd, streamData.size())) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
const auto mapped = mmap(
|
||||
nullptr,
|
||||
streamData.size(),
|
||||
PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0);
|
||||
|
||||
if (mapped == MAP_FAILED) {
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
const auto mappedGuard = gsl::finally([&] {
|
||||
munmap(mapped, streamData.size());
|
||||
});
|
||||
|
||||
memcpy(mapped, streamData.constData(), streamData.size());
|
||||
|
||||
const auto fdList = Gio::UnixFDList::create();
|
||||
fdList->append(fd);
|
||||
|
||||
invocation->return_value(
|
||||
Glib::VariantContainerBase::create_tuple({
|
||||
Glib::wrap(g_variant_new_handle(0)),
|
||||
Glib::Variant<int>::create(streamData.size()),
|
||||
}),
|
||||
fdList);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
Gio::DBus::Error error(
|
||||
Gio::DBus::Error::UNKNOWN_METHOD,
|
||||
"Method does not exist.");
|
||||
|
||||
invocation->return_error(error);
|
||||
}
|
||||
|
||||
GtkIntegration::GtkIntegration()
|
||||
: _private(std::make_unique<Private>()) {
|
||||
}
|
||||
|
||||
GtkIntegration::~GtkIntegration() {
|
||||
if (_private->dbusConnection) {
|
||||
if (_private->parentServiceWatcherId != 0) {
|
||||
_private->dbusConnection->signal_unsubscribe(
|
||||
_private->parentServiceWatcherId);
|
||||
}
|
||||
|
||||
if (_private->registerId != 0) {
|
||||
_private->dbusConnection->unregister_object(
|
||||
_private->registerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GtkIntegration *GtkIntegration::Instance() {
|
||||
@@ -50,10 +269,31 @@ GtkIntegration *GtkIntegration::Instance() {
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void GtkIntegration::load() {
|
||||
void GtkIntegration::load(const QString &allowedBackends) {
|
||||
static bool Loaded = false;
|
||||
Expects(!Loaded);
|
||||
|
||||
if (_private->remoting) {
|
||||
if (!_private->dbusConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto reply = _private->dbusConnection->call_sync(
|
||||
std::string(kObjectPath),
|
||||
std::string(kInterface),
|
||||
"Load",
|
||||
base::Platform::MakeGlibVariant(std::tuple{
|
||||
Glib::ustring(allowedBackends.toStdString()),
|
||||
}),
|
||||
_private->serviceName);
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
BaseGtkIntegration::Instance()->load(allowedBackends, true);
|
||||
if (!BaseGtkIntegration::Instance()->loaded()) {
|
||||
return;
|
||||
}
|
||||
@@ -61,57 +301,17 @@ void GtkIntegration::load() {
|
||||
auto &lib = BaseGtkIntegration::Instance()->library();
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_show);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_hide);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_get_window);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_realize);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_hide_on_delete);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_widget_destroy);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_clipboard_get);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_clipboard_store);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_clipboard_wait_for_contents);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_clipboard_wait_for_image);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_selection_data_targets_include_image);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_selection_data_free);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_dialog_new);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_get_type);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_image_get_type);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_current_folder);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_get_current_folder);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_current_name);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_select_filename);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_get_filenames);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_filter);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_get_filter);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_window_get_type);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_window_set_title);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_local_only);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_action);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_select_multiple);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_do_overwrite_confirmation);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_remove_filter);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_filter_set_name);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_filter_add_pattern);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_add_filter);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_preview_widget);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_get_preview_filename);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_chooser_set_preview_widget_active);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_file_filter_new);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_image_new);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_image_set_from_pixbuf);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gdk_window_set_modal_hint);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_window_focus);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_dialog_get_type);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_dialog_run);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gdk_atom_intern);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gdk_display_get_default);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_display_get_monitor);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_display_get_primary_monitor);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_monitor_get_scale_factor);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gdk_pixbuf_new_from_file_at_size);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_pixbuf_get_has_alpha);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_pixbuf_get_pixels);
|
||||
LOAD_GTK_SYMBOL(lib, gdk_pixbuf_get_width);
|
||||
@@ -120,10 +320,6 @@ void GtkIntegration::load() {
|
||||
|
||||
GdkHelperLoad(lib);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gtk_dialog_get_widget_for_response);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_button_set_label);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_button_get_type);
|
||||
|
||||
LOAD_GTK_SYMBOL(lib, gtk_app_chooser_dialog_new);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_app_chooser_get_app_info);
|
||||
LOAD_GTK_SYMBOL(lib, gtk_app_chooser_get_type);
|
||||
@@ -131,58 +327,233 @@ void GtkIntegration::load() {
|
||||
Loaded = true;
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::scaleFactor() const {
|
||||
if ((gdk_display_get_default == nullptr)
|
||||
|| (gdk_display_get_monitor == nullptr)
|
||||
|| (gdk_display_get_primary_monitor == nullptr)
|
||||
|| (gdk_monitor_get_scale_factor == nullptr)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
int GtkIntegration::exec(const QString &parentDBusName, int ppid) {
|
||||
_private->remoting = false;
|
||||
_private->serviceName = kService.utf16().arg(ppid).toStdString();
|
||||
_private->parentDBusName = parentDBusName.toStdString();
|
||||
|
||||
const auto display = gdk_display_get_default();
|
||||
if (!display) {
|
||||
return std::nullopt;
|
||||
}
|
||||
_private->introspectionData = Gio::DBus::NodeInfo::create_for_xml(
|
||||
std::string(kIntrospectionXML));
|
||||
|
||||
const auto monitor = [&] {
|
||||
if (const auto primary = gdk_display_get_primary_monitor(display)) {
|
||||
return primary;
|
||||
_private->registerId = _private->dbusConnection->register_object(
|
||||
std::string(kObjectPath),
|
||||
_private->introspectionData->lookup_interface(),
|
||||
_private->interfaceVTable);
|
||||
|
||||
rpl::lifetime lifetime;
|
||||
|
||||
File::internal::GtkOpenWithDialogResponse(
|
||||
) | rpl::start_with_next([=](bool response) {
|
||||
try {
|
||||
_private->dbusConnection->emit_signal(
|
||||
std::string(kObjectPath),
|
||||
std::string(kInterface),
|
||||
"OpenWithDialogResponse",
|
||||
_private->parentDBusName,
|
||||
base::Platform::MakeGlibVariant(std::tuple{
|
||||
response,
|
||||
}));
|
||||
} catch (...) {
|
||||
}
|
||||
return gdk_display_get_monitor(display, 0);
|
||||
}();
|
||||
}, lifetime);
|
||||
|
||||
if (!monitor) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return gdk_monitor_get_scale_factor(monitor);
|
||||
}
|
||||
|
||||
std::optional<bool> GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile) const {
|
||||
return FileDialog::Gtk::Get(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
const auto app = Gio::Application::create(_private->serviceName);
|
||||
app->hold();
|
||||
_private->parentServiceWatcherId = base::Platform::DBus::RegisterServiceWatcher(
|
||||
_private->dbusConnection,
|
||||
parentDBusName.toStdString(),
|
||||
[=](
|
||||
const Glib::ustring &service,
|
||||
const Glib::ustring &oldOwner,
|
||||
const Glib::ustring &newOwner) {
|
||||
if (!newOwner.empty()) {
|
||||
return;
|
||||
}
|
||||
app->quit();
|
||||
});
|
||||
return app->run(0, nullptr);
|
||||
}
|
||||
|
||||
bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
|
||||
return File::internal::ShowGtkOpenWithDialog(filepath);
|
||||
const auto parent = [&] {
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
if (const auto integration = WaylandIntegration::Instance()) {
|
||||
if (const auto handle = integration->nativeHandle(
|
||||
activeWindow->widget()->windowHandle())
|
||||
; !handle.isEmpty()) {
|
||||
return qsl("wayland:") + handle;
|
||||
}
|
||||
} else if (Platform::IsX11()) {
|
||||
return qsl("x11:") + QString::number(
|
||||
activeWindow->widget()->winId(),
|
||||
16);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
|
||||
if (_private->remoting) {
|
||||
if (!_private->dbusConnection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
_private->dbusConnection->call_sync(
|
||||
std::string(kObjectPath),
|
||||
std::string(kInterface),
|
||||
"ShowOpenWithDialog",
|
||||
base::Platform::MakeGlibVariant(std::tuple{
|
||||
Glib::ustring(parent.toStdString()),
|
||||
Glib::ustring(filepath.toStdString()),
|
||||
}),
|
||||
_private->serviceName);
|
||||
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
bool result = false;
|
||||
|
||||
const auto signalId = _private->dbusConnection->signal_subscribe(
|
||||
[&](
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender_name,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &signal_name,
|
||||
Glib::VariantContainerBase parameters) {
|
||||
try {
|
||||
auto parametersCopy = parameters;
|
||||
|
||||
result = base::Platform::GlibVariantCast<bool>(
|
||||
parametersCopy.get_child(0));
|
||||
|
||||
loop->quit();
|
||||
} catch (...) {
|
||||
}
|
||||
},
|
||||
_private->serviceName,
|
||||
std::string(kInterface),
|
||||
"OpenWithDialogResponse",
|
||||
std::string(kObjectPath));
|
||||
|
||||
const auto signalGuard = gsl::finally([&] {
|
||||
if (signalId != 0) {
|
||||
_private->dbusConnection->signal_unsubscribe(signalId);
|
||||
}
|
||||
});
|
||||
|
||||
QWindow window;
|
||||
QGuiApplicationPrivate::showModalWindow(&window);
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
QGuiApplicationPrivate::hideModalWindow(&window);
|
||||
|
||||
return result;
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File::internal::ShowGtkOpenWithDialog(parent, filepath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
rpl::lifetime lifetime;
|
||||
bool result = false;
|
||||
|
||||
File::internal::GtkOpenWithDialogResponse(
|
||||
) | rpl::start_with_next([&](bool response) {
|
||||
result = response;
|
||||
loop->quit();
|
||||
}, lifetime);
|
||||
|
||||
QWindow window;
|
||||
QGuiApplicationPrivate::showModalWindow(&window);
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
QGuiApplicationPrivate::hideModalWindow(&window);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage GtkIntegration::getImageFromClipboard() const {
|
||||
QImage data;
|
||||
|
||||
if (_private->remoting) {
|
||||
if (!_private->dbusConnection) {
|
||||
return data;
|
||||
}
|
||||
|
||||
try {
|
||||
Glib::RefPtr<Gio::UnixFDList> outFdList;
|
||||
|
||||
auto reply = _private->dbusConnection->call_sync(
|
||||
std::string(kObjectPath),
|
||||
std::string(kInterface),
|
||||
"GetImageFromClipboard",
|
||||
{},
|
||||
{},
|
||||
outFdList,
|
||||
_private->serviceName);
|
||||
|
||||
const auto streamSize = base::Platform::GlibVariantCast<int>(
|
||||
reply.get_child(1));
|
||||
|
||||
const auto mapped = mmap(
|
||||
nullptr,
|
||||
streamSize,
|
||||
PROT_READ,
|
||||
MAP_SHARED,
|
||||
outFdList->get(0),
|
||||
0);
|
||||
|
||||
if (mapped == MAP_FAILED) {
|
||||
return data;
|
||||
}
|
||||
|
||||
QByteArray streamData;
|
||||
streamData.resize(streamSize);
|
||||
memcpy(streamData.data(), mapped, streamData.size());
|
||||
munmap(mapped, streamData.size());
|
||||
|
||||
int imageWidth = 0;
|
||||
int imageHeight = 0;
|
||||
int imageBytesPerLine = 0;
|
||||
bool imageHasAlphaChannel = false;
|
||||
int imageBitsPerSample = 0;
|
||||
int imageChannels = 0;
|
||||
QVector<uchar> imageData;
|
||||
|
||||
QDataStream stream(streamData);
|
||||
stream
|
||||
>> imageWidth
|
||||
>> imageHeight
|
||||
>> imageBytesPerLine
|
||||
>> imageHasAlphaChannel
|
||||
>> imageBitsPerSample
|
||||
>> imageChannels
|
||||
>> imageData;
|
||||
|
||||
data = QImage(
|
||||
imageData.data(),
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
imageBytesPerLine,
|
||||
imageHasAlphaChannel
|
||||
? QImage::Format_RGBA8888
|
||||
: QImage::Format_RGB888).copy();
|
||||
|
||||
return data;
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
if (!GetImageFromClipboardSupported()) {
|
||||
return data;
|
||||
}
|
||||
@@ -220,5 +591,113 @@ QImage GtkIntegration::getImageFromClipboard() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
QString GtkIntegration::AllowedBackends() {
|
||||
return Platform::IsWayland()
|
||||
? qsl("wayland,x11")
|
||||
: Platform::IsX11()
|
||||
? qsl("x11,wayland")
|
||||
: QString();
|
||||
}
|
||||
|
||||
int GtkIntegration::Exec(
|
||||
Type type,
|
||||
const QString &parentDBusName,
|
||||
int ppid,
|
||||
uint instanceNumber) {
|
||||
Glib::init();
|
||||
Gio::init();
|
||||
|
||||
if (type == Type::Base) {
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
return integration->exec(parentDBusName, ppid);
|
||||
}
|
||||
} else if (type == Type::Webview) {
|
||||
if (const auto instance = Webview::CreateInstance({})) {
|
||||
return instance->exec(
|
||||
parentDBusName.toStdString(),
|
||||
ppid,
|
||||
instanceNumber);
|
||||
}
|
||||
} else if (type == Type::TDesktop) {
|
||||
if (const auto integration = Instance()) {
|
||||
return integration->exec(parentDBusName, ppid);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void GtkIntegration::Start(Type type) {
|
||||
if (type != Type::Base && type != Type::TDesktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dbusName = [] {
|
||||
try {
|
||||
static const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
return QString::fromStdString(connection->get_unique_name());
|
||||
} catch (...) {
|
||||
return QString();
|
||||
}
|
||||
}();
|
||||
|
||||
if (dbusName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QProcess::startDetached(cExeDir() + cExeName(), {
|
||||
(type == Type::Base)
|
||||
? qsl("-basegtkintegration")
|
||||
: qsl("-gtkintegration"),
|
||||
dbusName,
|
||||
QString::number(getpid()),
|
||||
});
|
||||
}
|
||||
|
||||
void GtkIntegration::Autorestart(Type type) {
|
||||
if (type != Type::Base && type != Type::TDesktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
static const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
const auto baseServiceName = [] {
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
return integration->serviceName();
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
|
||||
base::Platform::DBus::RegisterServiceWatcher(
|
||||
connection,
|
||||
(type == Type::Base)
|
||||
? baseServiceName.toStdString()
|
||||
: kService.utf16().arg(getpid()).toStdString(),
|
||||
[=](
|
||||
const Glib::ustring &service,
|
||||
const Glib::ustring &oldOwner,
|
||||
const Glib::ustring &newOwner) {
|
||||
if (newOwner.empty()) {
|
||||
Start(type);
|
||||
} else {
|
||||
if (type == Type::Base) {
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
integration->load(AllowedBackends());
|
||||
}
|
||||
} else if (type == Type::TDesktop) {
|
||||
if (const auto integration = Instance()) {
|
||||
integration->load(AllowedBackends());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
|
||||
@@ -7,34 +7,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class GtkIntegration {
|
||||
public:
|
||||
enum class Type {
|
||||
Base,
|
||||
Webview,
|
||||
TDesktop,
|
||||
};
|
||||
|
||||
static GtkIntegration *Instance();
|
||||
|
||||
void load();
|
||||
|
||||
[[nodiscard]] std::optional<int> scaleFactor() const;
|
||||
|
||||
[[nodiscard]] std::optional<bool> getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile) const;
|
||||
void load(const QString &allowedBackends);
|
||||
int exec(const QString &parentDBusName, int ppid);
|
||||
|
||||
[[nodiscard]] bool showOpenWithDialog(const QString &filepath) const;
|
||||
|
||||
[[nodiscard]] QImage getImageFromClipboard() const;
|
||||
|
||||
static QString AllowedBackends();
|
||||
|
||||
static int Exec(
|
||||
Type type,
|
||||
const QString &parentDBusName,
|
||||
int ppid,
|
||||
uint instanceNumber = 0);
|
||||
|
||||
static void Start(Type type);
|
||||
|
||||
static void Autorestart(Type type);
|
||||
|
||||
private:
|
||||
GtkIntegration();
|
||||
~GtkIntegration();
|
||||
|
||||
class Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
@@ -10,29 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Platform {
|
||||
namespace internal {
|
||||
|
||||
class GtkIntegration::Private {
|
||||
};
|
||||
|
||||
GtkIntegration::GtkIntegration() {
|
||||
}
|
||||
|
||||
GtkIntegration::~GtkIntegration() = default;
|
||||
|
||||
GtkIntegration *GtkIntegration::Instance() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GtkIntegration::load() {
|
||||
void GtkIntegration::load(const QString &allowedBackends) {
|
||||
}
|
||||
|
||||
std::optional<int> GtkIntegration::scaleFactor() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<bool> GtkIntegration::getFileDialog(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
::FileDialog::internal::Type type,
|
||||
QString startFile) const {
|
||||
return std::nullopt;
|
||||
int GtkIntegration::exec(const QString &parentDBusName, int ppid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
|
||||
@@ -43,5 +37,23 @@ QImage GtkIntegration::getImageFromClipboard() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString GtkIntegration::AllowedBackends() {
|
||||
return {};
|
||||
}
|
||||
|
||||
int GtkIntegration:Exec(
|
||||
Type type,
|
||||
const QString &parentDBusName,
|
||||
int ppid,
|
||||
uint instanceNumber) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void GtkIntegration::Start(Type type) {
|
||||
}
|
||||
|
||||
void GtkIntegration::Autorestart(Type type) {
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
|
||||
@@ -16,59 +16,18 @@ namespace Platform {
|
||||
namespace Gtk {
|
||||
|
||||
inline void (*gtk_widget_show)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_hide)(GtkWidget *widget) = nullptr;
|
||||
inline GdkWindow* (*gtk_widget_get_window)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_realize)(GtkWidget *widget) = nullptr;
|
||||
inline gboolean (*gtk_widget_hide_on_delete)(GtkWidget *widget) = nullptr;
|
||||
inline void (*gtk_widget_destroy)(GtkWidget *widget) = nullptr;
|
||||
inline GtkClipboard* (*gtk_clipboard_get)(GdkAtom selection) = nullptr;
|
||||
inline void (*gtk_clipboard_store)(GtkClipboard *clipboard) = nullptr;
|
||||
inline GtkSelectionData* (*gtk_clipboard_wait_for_contents)(GtkClipboard *clipboard, GdkAtom target) = nullptr;
|
||||
inline GdkPixbuf* (*gtk_clipboard_wait_for_image)(GtkClipboard *clipboard) = nullptr;
|
||||
inline gboolean (*gtk_selection_data_targets_include_image)(const GtkSelectionData *selection_data, gboolean writable) = nullptr;
|
||||
inline void (*gtk_selection_data_free)(GtkSelectionData *data) = nullptr;
|
||||
inline GType (*gtk_file_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline GtkWidget* (*gtk_file_chooser_dialog_new)(const gchar *title, GtkWindow *parent, GtkFileChooserAction action, const gchar *first_button_text, ...) G_GNUC_NULL_TERMINATED = nullptr;
|
||||
inline gboolean (*gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename) = nullptr;
|
||||
inline gchar* (*gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name) = nullptr;
|
||||
inline gboolean (*gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename) = nullptr;
|
||||
inline GSList* (*gtk_file_chooser_get_filenames)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline GtkFileFilter* (*gtk_file_chooser_get_filter)(GtkFileChooser *chooser) = nullptr;
|
||||
inline GType (*gtk_window_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline void (*gtk_window_set_title)(GtkWindow *window, const gchar *title) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_local_only)(GtkFileChooser *chooser, gboolean local_only) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_action)(GtkFileChooser *chooser, GtkFileChooserAction action) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_select_multiple)(GtkFileChooser *chooser, gboolean select_multiple) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_do_overwrite_confirmation)(GtkFileChooser *chooser, gboolean do_overwrite_confirmation) = nullptr;
|
||||
inline GType (*gtk_dialog_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline GtkWidget* (*gtk_dialog_get_widget_for_response)(GtkDialog *dialog, gint response_id) = nullptr;
|
||||
inline GType (*gtk_button_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline void (*gtk_button_set_label)(GtkButton *button, const gchar *label) = nullptr;
|
||||
inline void (*gtk_file_chooser_remove_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline void (*gtk_file_filter_set_name)(GtkFileFilter *filter, const gchar *name) = nullptr;
|
||||
inline void (*gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern) = nullptr;
|
||||
inline void (*gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget) = nullptr;
|
||||
inline gchar* (*gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser) = nullptr;
|
||||
inline void (*gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active) = nullptr;
|
||||
inline GtkFileFilter* (*gtk_file_filter_new)(void) = nullptr;
|
||||
inline GType (*gtk_image_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline GtkWidget* (*gtk_image_new)(void) = nullptr;
|
||||
inline void (*gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf) = nullptr;
|
||||
inline GType (*gtk_app_chooser_get_type)(void) G_GNUC_CONST = nullptr;
|
||||
inline GtkWidget* (*gtk_app_chooser_dialog_new)(GtkWindow *parent, GtkDialogFlags flags, GFile *file) = nullptr;
|
||||
inline GAppInfo* (*gtk_app_chooser_get_app_info)(GtkAppChooser *self) = nullptr;
|
||||
inline void (*gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal) = nullptr;
|
||||
inline void (*gdk_window_focus)(GdkWindow *window, guint32 timestamp) = nullptr;
|
||||
inline gint (*gtk_dialog_run)(GtkDialog *dialog) = nullptr;
|
||||
inline GdkAtom (*gdk_atom_intern)(const gchar *atom_name, gboolean only_if_exists) = nullptr;
|
||||
inline GdkDisplay* (*gdk_display_get_default)(void) = nullptr;
|
||||
inline GdkMonitor* (*gdk_display_get_monitor)(GdkDisplay *display, int monitor_num) = nullptr;
|
||||
inline GdkMonitor* (*gdk_display_get_primary_monitor)(GdkDisplay *display) = nullptr;
|
||||
inline int (*gdk_monitor_get_scale_factor)(GdkMonitor *monitor) = nullptr;
|
||||
inline GdkPixbuf* (*gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error) = nullptr;
|
||||
inline gboolean (*gdk_pixbuf_get_has_alpha)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline guchar* (*gdk_pixbuf_get_pixels)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
inline int (*gdk_pixbuf_get_width)(const GdkPixbuf *pixbuf) = nullptr;
|
||||
|
||||
@@ -9,10 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/linux/linux_gtk_integration_p.h"
|
||||
#include "platform/linux/linux_gdk_helper.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include <giomm.h>
|
||||
|
||||
namespace Platform {
|
||||
@@ -22,6 +19,8 @@ namespace {
|
||||
|
||||
using namespace Platform::Gtk;
|
||||
|
||||
rpl::event_stream<bool> GtkOpenWithDialogResponseStream;
|
||||
|
||||
struct GtkWidgetDeleter {
|
||||
void operator()(GtkWidget *widget) {
|
||||
gtk_widget_destroy(widget);
|
||||
@@ -38,22 +37,22 @@ bool Supported() {
|
||||
&& (gtk_widget_destroy != nullptr);
|
||||
}
|
||||
|
||||
class GtkOpenWithDialog : public QWindow {
|
||||
class GtkOpenWithDialog {
|
||||
public:
|
||||
GtkOpenWithDialog(const QString &filepath);
|
||||
|
||||
bool exec();
|
||||
GtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath);
|
||||
|
||||
private:
|
||||
static void handleResponse(GtkOpenWithDialog *dialog, int responseId);
|
||||
|
||||
const Glib::RefPtr<Gio::File> _file;
|
||||
const std::unique_ptr<GtkWidget, GtkWidgetDeleter> _gtkWidget;
|
||||
QEventLoop _loop;
|
||||
std::optional<bool> _result;
|
||||
};
|
||||
|
||||
GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath)
|
||||
GtkOpenWithDialog::GtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath)
|
||||
: _file(Gio::File::create_for_path(filepath.toStdString()))
|
||||
, _gtkWidget(gtk_app_chooser_dialog_new(
|
||||
nullptr,
|
||||
@@ -64,31 +63,19 @@ GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath)
|
||||
"response",
|
||||
G_CALLBACK(handleResponse),
|
||||
this);
|
||||
}
|
||||
|
||||
bool GtkOpenWithDialog::exec() {
|
||||
gtk_widget_realize(_gtkWidget.get());
|
||||
|
||||
if (const auto activeWindow = Core::App().activeWindow()) {
|
||||
Platform::internal::GdkSetTransientFor(
|
||||
gtk_widget_get_window(_gtkWidget.get()),
|
||||
activeWindow->widget()->windowHandle());
|
||||
}
|
||||
Platform::internal::GdkSetTransientFor(
|
||||
gtk_widget_get_window(_gtkWidget.get()),
|
||||
parent);
|
||||
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
gtk_widget_show(_gtkWidget.get());
|
||||
|
||||
if (!_result.has_value()) {
|
||||
_loop.exec();
|
||||
}
|
||||
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
return *_result;
|
||||
}
|
||||
|
||||
void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId) {
|
||||
Glib::RefPtr<Gio::AppInfo> chosenAppInfo;
|
||||
dialog->_result = true;
|
||||
bool result = true;
|
||||
|
||||
switch (responseId) {
|
||||
case GTK_RESPONSE_OK:
|
||||
@@ -110,21 +97,28 @@ void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId
|
||||
break;
|
||||
|
||||
default:
|
||||
dialog->_result = false;
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
dialog->_loop.quit();
|
||||
GtkOpenWithDialogResponseStream.fire_copy(result);
|
||||
delete dialog;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowGtkOpenWithDialog(const QString &filepath) {
|
||||
bool ShowGtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath) {
|
||||
if (!Supported()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return GtkOpenWithDialog(filepath).exec();
|
||||
return new GtkOpenWithDialog(parent, filepath);
|
||||
}
|
||||
|
||||
rpl::producer<bool> GtkOpenWithDialogResponse() {
|
||||
return GtkOpenWithDialogResponseStream.events();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
@@ -11,7 +11,11 @@ namespace Platform {
|
||||
namespace File {
|
||||
namespace internal {
|
||||
|
||||
bool ShowGtkOpenWithDialog(const QString &filepath);
|
||||
bool ShowGtkOpenWithDialog(
|
||||
const QString &parent,
|
||||
const QString &filepath);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> GtkOpenWithDialogResponse();
|
||||
|
||||
} // namespace internal
|
||||
} // namespace File
|
||||
|
||||
@@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <surface.h>
|
||||
#include <xdgforeign.h>
|
||||
#include <plasmashell.h>
|
||||
#include <appmenu.h>
|
||||
|
||||
using namespace KWayland::Client;
|
||||
|
||||
@@ -37,10 +36,6 @@ public:
|
||||
return _plasmaShell.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] AppMenuManager *appMenuManager() {
|
||||
return _appMenuManager.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] QEventLoop &interfacesLoop() {
|
||||
return _interfacesLoop;
|
||||
}
|
||||
@@ -56,7 +51,6 @@ private:
|
||||
Registry _applicationRegistry;
|
||||
std::unique_ptr<XdgExporter> _xdgExporter;
|
||||
std::unique_ptr<PlasmaShell> _plasmaShell;
|
||||
std::unique_ptr<AppMenuManager> _appMenuManager;
|
||||
QEventLoop _interfacesLoop;
|
||||
bool _interfacesAnnounced = false;
|
||||
};
|
||||
@@ -123,21 +117,6 @@ WaylandIntegration::Private::Private()
|
||||
&PlasmaShell::destroy);
|
||||
});
|
||||
|
||||
connect(
|
||||
&_applicationRegistry,
|
||||
&Registry::appMenuAnnounced,
|
||||
[=](uint name, uint version) {
|
||||
_appMenuManager = std::unique_ptr<AppMenuManager>{
|
||||
_applicationRegistry.createAppMenuManager(name, version),
|
||||
};
|
||||
|
||||
connect(
|
||||
_applicationConnection,
|
||||
&ConnectionThread::connectionDied,
|
||||
_appMenuManager.get(),
|
||||
&AppMenuManager::destroy);
|
||||
});
|
||||
|
||||
_connection.initConnection();
|
||||
}
|
||||
|
||||
@@ -208,27 +187,5 @@ void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) {
|
||||
plasmaSurface->setSkipTaskbar(skip);
|
||||
}
|
||||
|
||||
void WaylandIntegration::registerAppMenu(
|
||||
QWindow *window,
|
||||
const QString &serviceName,
|
||||
const QString &objectPath) {
|
||||
const auto manager = _private->appMenuManager();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto surface = Surface::fromWindow(window);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto appMenu = manager->create(surface, surface);
|
||||
if (!appMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
appMenu->setAddress(serviceName, objectPath);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
|
||||
@@ -18,10 +18,6 @@ public:
|
||||
[[nodiscard]] QString nativeHandle(QWindow *window);
|
||||
[[nodiscard]] bool skipTaskbarSupported();
|
||||
void skipTaskbar(QWindow *window, bool skip);
|
||||
void registerAppMenu(
|
||||
QWindow *window,
|
||||
const QString &serviceName,
|
||||
const QString &objectPath);
|
||||
|
||||
private:
|
||||
WaylandIntegration();
|
||||
|
||||
@@ -44,11 +44,5 @@ bool WaylandIntegration::skipTaskbarSupported() {
|
||||
void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) {
|
||||
}
|
||||
|
||||
void WaylandIntegration::registerAppMenu(
|
||||
QWindow *window,
|
||||
const QString &serviceName,
|
||||
const QString &objectPath) {
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Platform
|
||||
|
||||
@@ -578,20 +578,23 @@ int XDPFileDialog::exec() {
|
||||
|
||||
// HACK we have to avoid returning until we emit
|
||||
// that the dialog was accepted or rejected
|
||||
QEventLoop loop;
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
rpl::lifetime lifetime;
|
||||
|
||||
accepted(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
loop->quit();
|
||||
}, lifetime);
|
||||
|
||||
rejected(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
loop->quit();
|
||||
}, lifetime);
|
||||
|
||||
loop.exec();
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
|
||||
if (guard.isNull()) {
|
||||
return QDialog::Rejected;
|
||||
|
||||
@@ -33,19 +33,9 @@ constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_c
|
||||
constexpr auto kXDGDesktopPortalOpenURIInterface = "org.freedesktop.portal.OpenURI"_cs;
|
||||
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
|
||||
|
||||
class XDPOpenWithDialog : public QWindow {
|
||||
public:
|
||||
XDPOpenWithDialog(const QString &filepath)
|
||||
: _filepath(filepath.toStdString()) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool exec();
|
||||
|
||||
private:
|
||||
Glib::ustring _filepath;
|
||||
};
|
||||
|
||||
bool XDPOpenWithDialog::exec() {
|
||||
bool ShowXDPOpenWithDialog(const QString &filepath) {
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
@@ -69,8 +59,10 @@ bool XDPOpenWithDialog::exec() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto filepathUtf8 = filepath.toUtf8();
|
||||
|
||||
const auto fd = open(
|
||||
_filepath.c_str(),
|
||||
filepathUtf8.constData(),
|
||||
O_RDONLY);
|
||||
|
||||
if (fd == -1) {
|
||||
@@ -113,7 +105,9 @@ bool XDPOpenWithDialog::exec() {
|
||||
+ '/'
|
||||
+ handleToken;
|
||||
|
||||
QEventLoop loop;
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
|
||||
const auto signalId = connection->signal_subscribe(
|
||||
[&](
|
||||
@@ -123,7 +117,7 @@ bool XDPOpenWithDialog::exec() {
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters) {
|
||||
loop.quit();
|
||||
loop->quit();
|
||||
},
|
||||
std::string(kXDGDesktopPortalService),
|
||||
"org.freedesktop.portal.Request",
|
||||
@@ -166,9 +160,11 @@ bool XDPOpenWithDialog::exec() {
|
||||
std::string(kXDGDesktopPortalService));
|
||||
|
||||
if (signalId != 0) {
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
loop.exec();
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
QWindow window;
|
||||
QGuiApplicationPrivate::showModalWindow(&window);
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
QGuiApplicationPrivate::hideModalWindow(&window);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -178,12 +174,6 @@ bool XDPOpenWithDialog::exec() {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowXDPOpenWithDialog(const QString &filepath) {
|
||||
return XDPOpenWithDialog(filepath).exec();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace File
|
||||
} // namespace Platform
|
||||
|
||||
@@ -43,15 +43,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QSize>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusMessage>
|
||||
#include <QtDBus/QDBusObjectPath>
|
||||
#include <QtDBus/QDBusMetaType>
|
||||
|
||||
#include <statusnotifieritem.h>
|
||||
#include <dbusmenuexporter.h>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
@@ -74,12 +71,6 @@ constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs;
|
||||
constexpr auto kSNIWatcherObjectPath = "/StatusNotifierWatcher"_cs;
|
||||
constexpr auto kSNIWatcherInterface = kSNIWatcherService;
|
||||
|
||||
constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs;
|
||||
constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs;
|
||||
constexpr auto kAppMenuInterface = kAppMenuService;
|
||||
|
||||
constexpr auto kMainMenuObjectPath = "/MenuBar"_cs;
|
||||
|
||||
bool TrayIconMuted = true;
|
||||
int32 TrayIconCount = 0;
|
||||
base::flat_map<int, QImage> TrayIconImageBack;
|
||||
@@ -364,6 +355,31 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void SendKeySequence(
|
||||
Qt::Key key,
|
||||
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
|
||||
const auto focused = QApplication::focusWidget();
|
||||
if (qobject_cast<QLineEdit*>(focused)
|
||||
|| qobject_cast<QTextEdit*>(focused)
|
||||
|| qobject_cast<HistoryInner*>(focused)) {
|
||||
QApplication::postEvent(
|
||||
focused,
|
||||
new QKeyEvent(QEvent::KeyPress, key, modifiers));
|
||||
|
||||
QApplication::postEvent(
|
||||
focused,
|
||||
new QKeyEvent(QEvent::KeyRelease, key, modifiers));
|
||||
}
|
||||
}
|
||||
|
||||
void ForceDisabled(QAction *action, bool disabled) {
|
||||
if (action->isEnabled()) {
|
||||
if (disabled) action->setDisabled(true);
|
||||
} else if (!disabled) {
|
||||
action->setDisabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
bool IsIndicatorApplication() {
|
||||
// Hack for indicator-application,
|
||||
@@ -520,104 +536,161 @@ uint djbStringHash(const std::string &string) {
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool IsAppMenuSupported() {
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
return base::Platform::DBus::NameHasOwner(
|
||||
connection,
|
||||
std::string(kAppMenuService));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This call must be made from the same bus connection as DBusMenuExporter
|
||||
// So it must use QDBusConnection
|
||||
void RegisterAppMenu(QWindow *window, const QString &menuPath) {
|
||||
if (const auto integration = WaylandIntegration::Instance()) {
|
||||
integration->registerAppMenu(
|
||||
window,
|
||||
QDBusConnection::sessionBus().baseService(),
|
||||
menuPath);
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = QDBusMessage::createMethodCall(
|
||||
kAppMenuService.utf16(),
|
||||
kAppMenuObjectPath.utf16(),
|
||||
kAppMenuInterface.utf16(),
|
||||
qsl("RegisterWindow"));
|
||||
|
||||
message.setArguments({
|
||||
window->winId(),
|
||||
QVariant::fromValue(QDBusObjectPath(menuPath))
|
||||
});
|
||||
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
}
|
||||
|
||||
// This call must be made from the same bus connection as DBusMenuExporter
|
||||
// So it must use QDBusConnection
|
||||
void UnregisterAppMenu(QWindow *window) {
|
||||
if (const auto integration = WaylandIntegration::Instance()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = QDBusMessage::createMethodCall(
|
||||
kAppMenuService.utf16(),
|
||||
kAppMenuObjectPath.utf16(),
|
||||
kAppMenuInterface.utf16(),
|
||||
qsl("UnregisterWindow"));
|
||||
|
||||
message.setArguments({
|
||||
window->winId()
|
||||
});
|
||||
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
}
|
||||
|
||||
void SendKeySequence(
|
||||
Qt::Key key,
|
||||
Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
|
||||
const auto focused = QApplication::focusWidget();
|
||||
if (qobject_cast<QLineEdit*>(focused)
|
||||
|| qobject_cast<QTextEdit*>(focused)
|
||||
|| qobject_cast<HistoryInner*>(focused)) {
|
||||
QApplication::postEvent(
|
||||
focused,
|
||||
new QKeyEvent(QEvent::KeyPress, key, modifiers));
|
||||
|
||||
QApplication::postEvent(
|
||||
focused,
|
||||
new QKeyEvent(QEvent::KeyRelease, key, modifiers));
|
||||
}
|
||||
}
|
||||
|
||||
void ForceDisabled(QAction *action, bool disabled) {
|
||||
if (action->isEnabled()) {
|
||||
if (disabled) action->setDisabled(true);
|
||||
} else if (!disabled) {
|
||||
action->setDisabled(false);
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
} // namespace
|
||||
|
||||
class MainWindow::Private {
|
||||
public:
|
||||
explicit Private(not_null<MainWindow*> window)
|
||||
: _public(window) {
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
|
||||
|
||||
StatusNotifierItem *sniTrayIcon = nullptr;
|
||||
uint sniRegisteredSignalId = 0;
|
||||
uint sniWatcherId = 0;
|
||||
std::unique_ptr<QTemporaryFile> trayIconFile;
|
||||
|
||||
void setSNITrayIcon(int counter, bool muted);
|
||||
void attachToSNITrayIcon();
|
||||
void handleSNIHostRegistered();
|
||||
|
||||
void handleSNIOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner);
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
private:
|
||||
not_null<MainWindow*> _public;
|
||||
};
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void MainWindow::Private::setSNITrayIcon(int counter, bool muted) {
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto panelIconName = GetPanelIconName(counter, muted);
|
||||
|
||||
if (iconName == panelIconName) {
|
||||
if (sniTrayIcon->iconName() == iconName) {
|
||||
return;
|
||||
}
|
||||
|
||||
sniTrayIcon->setIconByName(iconName);
|
||||
sniTrayIcon->setToolTipIconByName(iconName);
|
||||
} else if (IsIndicatorApplication()) {
|
||||
if (!IsIconRegenerationNeeded(counter, muted)
|
||||
&& trayIconFile
|
||||
&& sniTrayIcon->iconName() == trayIconFile->fileName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto icon = TrayIconGen(counter, muted);
|
||||
trayIconFile = TrayIconFile(icon, _public);
|
||||
|
||||
if (trayIconFile) {
|
||||
// indicator-application doesn't support tooltips
|
||||
sniTrayIcon->setIconByName(trayIconFile->fileName());
|
||||
}
|
||||
} else {
|
||||
if (!IsIconRegenerationNeeded(counter, muted)
|
||||
&& !sniTrayIcon->iconPixmap().isEmpty()
|
||||
&& sniTrayIcon->iconName().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto icon = TrayIconGen(counter, muted);
|
||||
sniTrayIcon->setIconByPixmap(icon);
|
||||
sniTrayIcon->setToolTipIconByPixmap(icon);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Private::attachToSNITrayIcon() {
|
||||
sniTrayIcon->setToolTipTitle(AppName.utf16());
|
||||
connect(sniTrayIcon,
|
||||
&StatusNotifierItem::activateRequested,
|
||||
[=](const QPoint &) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
_public->handleTrayIconActication(QSystemTrayIcon::Trigger);
|
||||
});
|
||||
});
|
||||
connect(sniTrayIcon,
|
||||
&StatusNotifierItem::secondaryActivateRequested,
|
||||
[=](const QPoint &) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
_public->handleTrayIconActication(QSystemTrayIcon::MiddleClick);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::Private::handleSNIHostRegistered() {
|
||||
if (_public->_sniAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
_public->_sniAvailable = true;
|
||||
|
||||
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("Switching to SNI tray icon..."));
|
||||
|
||||
if (_public->trayIcon) {
|
||||
_public->trayIcon->setContextMenu(nullptr);
|
||||
_public->trayIcon->deleteLater();
|
||||
}
|
||||
_public->trayIcon = nullptr;
|
||||
|
||||
_public->psSetupTrayIcon();
|
||||
|
||||
SkipTaskbar(
|
||||
_public->windowHandle(),
|
||||
Core::App().settings().workMode() == WorkMode::TrayOnly);
|
||||
}
|
||||
|
||||
void MainWindow::Private::handleSNIOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner) {
|
||||
_public->_sniAvailable = IsSNIAvailable();
|
||||
|
||||
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldOwner.isEmpty() && !newOwner.isEmpty() && _public->_sniAvailable) {
|
||||
LOG(("Switching to SNI tray icon..."));
|
||||
} else if (!oldOwner.isEmpty() && newOwner.isEmpty()) {
|
||||
LOG(("Switching to Qt tray icon..."));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_public->trayIcon) {
|
||||
_public->trayIcon->setContextMenu(0);
|
||||
_public->trayIcon->deleteLater();
|
||||
}
|
||||
_public->trayIcon = nullptr;
|
||||
|
||||
if (_public->trayAvailable()) {
|
||||
_public->psSetupTrayIcon();
|
||||
} else {
|
||||
LOG(("System tray is not available."));
|
||||
}
|
||||
|
||||
SkipTaskbar(
|
||||
_public->windowHandle(),
|
||||
(Core::App().settings().workMode() == WorkMode::TrayOnly)
|
||||
&& _public->trayAvailable());
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
: Window::MainWindow(controller)
|
||||
, _private(std::make_unique<Private>()) {
|
||||
, _private(std::make_unique<Private>(this)) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
qDBusRegisterMetaType<ToolTip>();
|
||||
qDBusRegisterMetaType<IconPixmap>();
|
||||
@@ -626,63 +699,6 @@ MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
}
|
||||
|
||||
void MainWindow::initHook() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
_sniAvailable = IsSNIAvailable();
|
||||
_appMenuSupported = IsAppMenuSupported();
|
||||
|
||||
try {
|
||||
_private->dbusConnection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
_sniRegisteredSignalId = _private->dbusConnection->signal_subscribe(
|
||||
[](
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender_name,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters) {
|
||||
if (signal_name == "StatusNotifierHostRegistered") {
|
||||
crl::on_main([] {
|
||||
if (const auto window = App::wnd()) {
|
||||
window->handleSNIHostRegistered();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
std::string(kSNIWatcherService),
|
||||
std::string(kSNIWatcherInterface),
|
||||
"StatusNotifierHostRegistered",
|
||||
std::string(kSNIWatcherObjectPath));
|
||||
|
||||
_sniWatcherId = base::Platform::DBus::RegisterServiceWatcher(
|
||||
_private->dbusConnection,
|
||||
std::string(kSNIWatcherService),
|
||||
[=](
|
||||
const Glib::ustring &service,
|
||||
const Glib::ustring &oldOwner,
|
||||
const Glib::ustring &newOwner) {
|
||||
handleSNIOwnerChanged(
|
||||
QString::fromStdString(service),
|
||||
QString::fromStdString(oldOwner),
|
||||
QString::fromStdString(newOwner));
|
||||
});
|
||||
|
||||
_appMenuWatcherId = base::Platform::DBus::RegisterServiceWatcher(
|
||||
_private->dbusConnection,
|
||||
std::string(kAppMenuService),
|
||||
[=](
|
||||
const Glib::ustring &service,
|
||||
const Glib::ustring &oldOwner,
|
||||
const Glib::ustring &newOwner) {
|
||||
handleAppMenuOwnerChanged(
|
||||
QString::fromStdString(service),
|
||||
QString::fromStdString(oldOwner),
|
||||
QString::fromStdString(newOwner));
|
||||
});
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
base::install_event_filter(windowHandle(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Expose) {
|
||||
auto ee = static_cast<QExposeEvent*>(e.get());
|
||||
@@ -704,10 +720,47 @@ void MainWindow::initHook() {
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
if (_appMenuSupported) {
|
||||
LOG(("Using D-Bus global menu."));
|
||||
} else {
|
||||
LOG(("Not using D-Bus global menu."));
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
_sniAvailable = IsSNIAvailable();
|
||||
|
||||
try {
|
||||
_private->dbusConnection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
|
||||
_private->sniRegisteredSignalId = _private->dbusConnection->signal_subscribe(
|
||||
[](
|
||||
const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
||||
const Glib::ustring &sender_name,
|
||||
const Glib::ustring &object_path,
|
||||
const Glib::ustring &interface_name,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters) {
|
||||
if (signal_name == "StatusNotifierHostRegistered") {
|
||||
crl::on_main([] {
|
||||
if (const auto window = App::wnd()) {
|
||||
window->_private->handleSNIHostRegistered();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
std::string(kSNIWatcherService),
|
||||
std::string(kSNIWatcherInterface),
|
||||
"StatusNotifierHostRegistered",
|
||||
std::string(kSNIWatcherObjectPath));
|
||||
|
||||
_private->sniWatcherId = base::Platform::DBus::RegisterServiceWatcher(
|
||||
_private->dbusConnection,
|
||||
std::string(kSNIWatcherService),
|
||||
[=](
|
||||
const Glib::ustring &service,
|
||||
const Glib::ustring &oldOwner,
|
||||
const Glib::ustring &newOwner) {
|
||||
_private->handleSNIOwnerChanged(
|
||||
QString::fromStdString(service),
|
||||
QString::fromStdString(oldOwner),
|
||||
QString::fromStdString(newOwner));
|
||||
});
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
if (UseUnityCounter()) {
|
||||
@@ -726,7 +779,7 @@ void MainWindow::initHook() {
|
||||
|
||||
bool MainWindow::hasTrayIcon() const {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
return trayIcon || (_sniAvailable && _sniTrayIcon);
|
||||
return trayIcon || (_sniAvailable && _private->sniTrayIcon);
|
||||
#else
|
||||
return trayIcon;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
@@ -744,147 +797,6 @@ void MainWindow::psShowTrayMenu() {
|
||||
void MainWindow::psTrayMenuUpdated() {
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void MainWindow::setSNITrayIcon(int counter, bool muted) {
|
||||
const auto iconName = GetTrayIconName(counter, muted);
|
||||
const auto panelIconName = GetPanelIconName(counter, muted);
|
||||
|
||||
if (iconName == panelIconName) {
|
||||
if (_sniTrayIcon->iconName() == iconName) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sniTrayIcon->setIconByName(iconName);
|
||||
_sniTrayIcon->setToolTipIconByName(iconName);
|
||||
} else if (IsIndicatorApplication()) {
|
||||
if (!IsIconRegenerationNeeded(counter, muted)
|
||||
&& _trayIconFile
|
||||
&& _sniTrayIcon->iconName() == _trayIconFile->fileName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto icon = TrayIconGen(counter, muted);
|
||||
_trayIconFile = TrayIconFile(icon, this);
|
||||
|
||||
if (_trayIconFile) {
|
||||
// indicator-application doesn't support tooltips
|
||||
_sniTrayIcon->setIconByName(_trayIconFile->fileName());
|
||||
}
|
||||
} else {
|
||||
if (!IsIconRegenerationNeeded(counter, muted)
|
||||
&& !_sniTrayIcon->iconPixmap().isEmpty()
|
||||
&& _sniTrayIcon->iconName().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto icon = TrayIconGen(counter, muted);
|
||||
_sniTrayIcon->setIconByPixmap(icon);
|
||||
_sniTrayIcon->setToolTipIconByPixmap(icon);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::attachToSNITrayIcon() {
|
||||
_sniTrayIcon->setToolTipTitle(AppName.utf16());
|
||||
connect(_sniTrayIcon,
|
||||
&StatusNotifierItem::activateRequested,
|
||||
this,
|
||||
[=](const QPoint &) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
handleTrayIconActication(QSystemTrayIcon::Trigger);
|
||||
});
|
||||
});
|
||||
connect(_sniTrayIcon,
|
||||
&StatusNotifierItem::secondaryActivateRequested,
|
||||
this,
|
||||
[=](const QPoint &) {
|
||||
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
||||
handleTrayIconActication(QSystemTrayIcon::MiddleClick);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::handleSNIHostRegistered() {
|
||||
if (_sniAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sniAvailable = true;
|
||||
|
||||
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("Switching to SNI tray icon..."));
|
||||
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(nullptr);
|
||||
trayIcon->deleteLater();
|
||||
}
|
||||
trayIcon = nullptr;
|
||||
|
||||
psSetupTrayIcon();
|
||||
|
||||
SkipTaskbar(
|
||||
windowHandle(),
|
||||
Core::App().settings().workMode() == WorkMode::TrayOnly);
|
||||
}
|
||||
|
||||
void MainWindow::handleSNIOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner) {
|
||||
_sniAvailable = IsSNIAvailable();
|
||||
|
||||
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldOwner.isEmpty() && !newOwner.isEmpty() && _sniAvailable) {
|
||||
LOG(("Switching to SNI tray icon..."));
|
||||
} else if (!oldOwner.isEmpty() && newOwner.isEmpty()) {
|
||||
LOG(("Switching to Qt tray icon..."));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (trayIcon) {
|
||||
trayIcon->setContextMenu(0);
|
||||
trayIcon->deleteLater();
|
||||
}
|
||||
trayIcon = nullptr;
|
||||
|
||||
if (trayAvailable()) {
|
||||
psSetupTrayIcon();
|
||||
} else {
|
||||
LOG(("System tray is not available."));
|
||||
}
|
||||
|
||||
SkipTaskbar(
|
||||
windowHandle(),
|
||||
(Core::App().settings().workMode() == WorkMode::TrayOnly)
|
||||
&& trayAvailable());
|
||||
}
|
||||
|
||||
void MainWindow::handleAppMenuOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner) {
|
||||
if (oldOwner.isEmpty() && !newOwner.isEmpty()) {
|
||||
_appMenuSupported = true;
|
||||
LOG(("Using D-Bus global menu."));
|
||||
} else if (!oldOwner.isEmpty() && newOwner.isEmpty()) {
|
||||
_appMenuSupported = false;
|
||||
LOG(("Not using D-Bus global menu."));
|
||||
}
|
||||
|
||||
if (_appMenuSupported && _mainMenuExporter) {
|
||||
RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16());
|
||||
} else {
|
||||
UnregisterAppMenu(windowHandle());
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
void MainWindow::psSetupTrayIcon() {
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
@@ -892,16 +804,16 @@ void MainWindow::psSetupTrayIcon() {
|
||||
if (_sniAvailable) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
LOG(("Using SNI tray icon."));
|
||||
if (!_sniTrayIcon) {
|
||||
_sniTrayIcon = new StatusNotifierItem(
|
||||
if (!_private->sniTrayIcon) {
|
||||
_private->sniTrayIcon = new StatusNotifierItem(
|
||||
QCoreApplication::applicationName(),
|
||||
this);
|
||||
|
||||
_sniTrayIcon->setTitle(AppName.utf16());
|
||||
_sniTrayIcon->setContextMenu(trayIconMenu);
|
||||
setSNITrayIcon(counter, muted);
|
||||
_private->sniTrayIcon->setTitle(AppName.utf16());
|
||||
_private->sniTrayIcon->setContextMenu(trayIconMenu);
|
||||
_private->setSNITrayIcon(counter, muted);
|
||||
|
||||
attachToSNITrayIcon();
|
||||
_private->attachToSNITrayIcon();
|
||||
}
|
||||
updateIconCounters();
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
@@ -924,11 +836,11 @@ void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
|
||||
return;
|
||||
} else if (mode == WorkMode::WindowOnly) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (_sniTrayIcon) {
|
||||
_sniTrayIcon->setContextMenu(0);
|
||||
_sniTrayIcon->deleteLater();
|
||||
if (_private->sniTrayIcon) {
|
||||
_private->sniTrayIcon->setContextMenu(0);
|
||||
_private->sniTrayIcon->deleteLater();
|
||||
}
|
||||
_sniTrayIcon = nullptr;
|
||||
_private->sniTrayIcon = nullptr;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
if (trayIcon) {
|
||||
@@ -992,8 +904,8 @@ void MainWindow::updateIconCounters() {
|
||||
}
|
||||
}
|
||||
|
||||
if (_sniTrayIcon) {
|
||||
setSNITrayIcon(counter, muted);
|
||||
if (_private->sniTrayIcon) {
|
||||
_private->setSNITrayIcon(counter, muted);
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
@@ -1007,16 +919,6 @@ void MainWindow::initTrayMenuHook() {
|
||||
_trayIconMenuXEmbed->deleteOnHide(false);
|
||||
}
|
||||
|
||||
#ifdef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
void MainWindow::createGlobalMenu() {
|
||||
}
|
||||
|
||||
void MainWindow::updateGlobalMenuHook() {
|
||||
}
|
||||
|
||||
#else // DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
void MainWindow::createGlobalMenu() {
|
||||
const auto ensureWindowShown = [=] {
|
||||
if (isHidden()) {
|
||||
@@ -1024,7 +926,8 @@ void MainWindow::createGlobalMenu() {
|
||||
}
|
||||
};
|
||||
|
||||
psMainMenu = new QMenu(this);
|
||||
psMainMenu = new QMenuBar(this);
|
||||
psMainMenu->hide();
|
||||
|
||||
auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now));
|
||||
|
||||
@@ -1201,14 +1104,6 @@ void MainWindow::createGlobalMenu() {
|
||||
|
||||
about->setMenuRole(QAction::AboutQtRole);
|
||||
|
||||
_mainMenuExporter = new DBusMenuExporter(
|
||||
kMainMenuObjectPath.utf16(),
|
||||
psMainMenu);
|
||||
|
||||
if (_appMenuSupported) {
|
||||
RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16());
|
||||
}
|
||||
|
||||
updateGlobalMenu();
|
||||
}
|
||||
|
||||
@@ -1327,8 +1222,6 @@ void MainWindow::updateGlobalMenuHook() {
|
||||
ForceDisabled(psClearFormat, !markdownEnabled);
|
||||
}
|
||||
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
void MainWindow::handleNativeSurfaceChanged(bool exist) {
|
||||
if (exist) {
|
||||
SkipTaskbar(
|
||||
@@ -1336,45 +1229,21 @@ void MainWindow::handleNativeSurfaceChanged(bool exist) {
|
||||
(Core::App().settings().workMode() == WorkMode::TrayOnly)
|
||||
&& trayAvailable());
|
||||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (_appMenuSupported && _mainMenuExporter) {
|
||||
if (exist) {
|
||||
RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16());
|
||||
} else {
|
||||
UnregisterAppMenu(windowHandle());
|
||||
}
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (_private->dbusConnection) {
|
||||
if (_sniRegisteredSignalId != 0) {
|
||||
if (_private->sniRegisteredSignalId != 0) {
|
||||
_private->dbusConnection->signal_unsubscribe(
|
||||
_sniRegisteredSignalId);
|
||||
_private->sniRegisteredSignalId);
|
||||
}
|
||||
|
||||
if (_sniWatcherId != 0) {
|
||||
if (_private->sniWatcherId != 0) {
|
||||
_private->dbusConnection->signal_unsubscribe(
|
||||
_sniWatcherId);
|
||||
}
|
||||
|
||||
if (_appMenuWatcherId != 0) {
|
||||
_private->dbusConnection->signal_unsubscribe(
|
||||
_appMenuWatcherId);
|
||||
_private->sniWatcherId);
|
||||
}
|
||||
}
|
||||
|
||||
delete _sniTrayIcon;
|
||||
|
||||
if (_appMenuSupported) {
|
||||
UnregisterAppMenu(windowHandle());
|
||||
}
|
||||
|
||||
delete _mainMenuExporter;
|
||||
delete psMainMenu;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_main_window.h"
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
class QMenuBar;
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
class QTemporaryFile;
|
||||
class DBusMenuExporter;
|
||||
class StatusNotifierItem;
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class MainWindow : public Window::MainWindow {
|
||||
@@ -70,24 +66,13 @@ protected:
|
||||
|
||||
private:
|
||||
class Private;
|
||||
friend class Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
|
||||
bool _sniAvailable = false;
|
||||
base::unique_qptr<Ui::PopupMenu> _trayIconMenuXEmbed;
|
||||
|
||||
void updateIconCounters();
|
||||
void handleNativeSurfaceChanged(bool exist);
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
StatusNotifierItem *_sniTrayIcon = nullptr;
|
||||
uint _sniRegisteredSignalId = 0;
|
||||
uint _sniWatcherId = 0;
|
||||
uint _appMenuWatcherId = 0;
|
||||
std::unique_ptr<QTemporaryFile> _trayIconFile;
|
||||
|
||||
bool _appMenuSupported = false;
|
||||
DBusMenuExporter *_mainMenuExporter = nullptr;
|
||||
|
||||
QMenu *psMainMenu = nullptr;
|
||||
QMenuBar *psMainMenu = nullptr;
|
||||
QAction *psLogout = nullptr;
|
||||
QAction *psUndo = nullptr;
|
||||
QAction *psRedo = nullptr;
|
||||
@@ -108,19 +93,8 @@ private:
|
||||
QAction *psMonospace = nullptr;
|
||||
QAction *psClearFormat = nullptr;
|
||||
|
||||
void setSNITrayIcon(int counter, bool muted);
|
||||
void attachToSNITrayIcon();
|
||||
void handleSNIHostRegistered();
|
||||
|
||||
void handleSNIOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner);
|
||||
|
||||
void handleAppMenuOwnerChanged(
|
||||
const QString &service,
|
||||
const QString &oldOwner,
|
||||
const QString &newOwner);
|
||||
void updateIconCounters();
|
||||
void handleNativeSurfaceChanged(bool exist);
|
||||
|
||||
void psLinuxUndo();
|
||||
void psLinuxRedo();
|
||||
@@ -136,7 +110,6 @@ private:
|
||||
void psLinuxStrikeOut();
|
||||
void psLinuxMonospace();
|
||||
void psLinuxClearFormat();
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <gio/gio.h>
|
||||
#include <glibmm.h>
|
||||
#include <giomm.h>
|
||||
#include <jemalloc/jemalloc.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -85,12 +86,7 @@ constexpr auto kSnapcraftSettingsInterface = kSnapcraftSettingsService;
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
std::unique_ptr<internal::NotificationServiceWatcher> NSWInstance;
|
||||
|
||||
class PortalAutostart : public QWindow {
|
||||
public:
|
||||
PortalAutostart(bool start, bool silent = false);
|
||||
};
|
||||
|
||||
PortalAutostart::PortalAutostart(bool start, bool silent) {
|
||||
void PortalAutostart(bool start, bool silent) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -149,7 +145,9 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
|
||||
+ '/'
|
||||
+ handleToken;
|
||||
|
||||
QEventLoop loop;
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
|
||||
const auto signalId = connection->signal_subscribe(
|
||||
[&](
|
||||
@@ -175,7 +173,7 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
|
||||
}
|
||||
}
|
||||
|
||||
loop.quit();
|
||||
loop->quit();
|
||||
},
|
||||
std::string(kXDGDesktopPortalService),
|
||||
"org.freedesktop.portal.Request",
|
||||
@@ -199,9 +197,11 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
|
||||
std::string(kXDGDesktopPortalService));
|
||||
|
||||
if (signalId != 0) {
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
loop.exec();
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
QWindow window;
|
||||
QGuiApplicationPrivate::showModalWindow(&window);
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
QGuiApplicationPrivate::hideModalWindow(&window);
|
||||
}
|
||||
} catch (const Glib::Error &e) {
|
||||
if (!silent) {
|
||||
@@ -211,12 +211,7 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
|
||||
}
|
||||
}
|
||||
|
||||
class SnapDefaultHandler : public QWindow {
|
||||
public:
|
||||
SnapDefaultHandler(const QString &protocol);
|
||||
};
|
||||
|
||||
SnapDefaultHandler::SnapDefaultHandler(const QString &protocol) {
|
||||
void SnapDefaultHandler(const QString &protocol) {
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::BUS_TYPE_SESSION);
|
||||
@@ -241,7 +236,9 @@ SnapDefaultHandler::SnapDefaultHandler(const QString &protocol) {
|
||||
return;
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
const auto context = Glib::MainContext::create();
|
||||
const auto loop = Glib::MainLoop::create(context);
|
||||
g_main_context_push_thread_default(context->gobj());
|
||||
|
||||
connection->call(
|
||||
std::string(kSnapcraftSettingsObjectPath),
|
||||
@@ -260,13 +257,15 @@ SnapDefaultHandler::SnapDefaultHandler(const QString &protocol) {
|
||||
QString::fromStdString(e.what())));
|
||||
}
|
||||
|
||||
loop.quit();
|
||||
loop->quit();
|
||||
},
|
||||
std::string(kSnapcraftSettingsService));
|
||||
|
||||
QGuiApplicationPrivate::showModalWindow(this);
|
||||
loop.exec();
|
||||
QGuiApplicationPrivate::hideModalWindow(this);
|
||||
QWindow window;
|
||||
QGuiApplicationPrivate::showModalWindow(&window);
|
||||
loop->run();
|
||||
g_main_context_pop_thread_default(context->gobj());
|
||||
QGuiApplicationPrivate::hideModalWindow(&window);
|
||||
} catch (const Glib::Error &e) {
|
||||
LOG(("Snap Default Handler Error: %1").arg(
|
||||
QString::fromStdString(e.what())));
|
||||
@@ -404,22 +403,6 @@ bool GenerateDesktopFile(
|
||||
}
|
||||
}
|
||||
|
||||
void SetGtkScaleFactor() {
|
||||
const auto integration = GtkIntegration::Instance();
|
||||
const auto ratio = Core::Sandbox::Instance().devicePixelRatio();
|
||||
if (!integration || ratio > 1.) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scaleFactor = integration->scaleFactor().value_or(1);
|
||||
if (scaleFactor == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("GTK scale factor: %1").arg(scaleFactor));
|
||||
cSetScreenScale(style::CheckScale(scaleFactor * 100));
|
||||
}
|
||||
|
||||
void SetDarkMode() {
|
||||
static const auto Inited = [] {
|
||||
QObject::connect(
|
||||
@@ -742,6 +725,9 @@ int psFixPrevious() {
|
||||
namespace Platform {
|
||||
|
||||
void start() {
|
||||
auto backgroundThread = true;
|
||||
mallctl("background_thread", nullptr, nullptr, &backgroundThread, sizeof(bool));
|
||||
|
||||
LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName()));
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
|
||||
@@ -757,11 +743,8 @@ void start() {
|
||||
Glib::set_prgname(cExeName().toStdString());
|
||||
Glib::set_application_name(std::string(AppName));
|
||||
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
integration->prepareEnvironment();
|
||||
} else {
|
||||
g_warning("GTK integration is disabled, some features unavailable.");
|
||||
}
|
||||
GtkIntegration::Start(GtkIntegration::Type::Base);
|
||||
GtkIntegration::Start(GtkIntegration::Type::TDesktop);
|
||||
|
||||
#ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE
|
||||
g_warning(
|
||||
@@ -958,13 +941,16 @@ bool OpenSystemSettings(SystemSettingsType type) {
|
||||
namespace ThirdParty {
|
||||
|
||||
void start() {
|
||||
GtkIntegration::Autorestart(GtkIntegration::Type::Base);
|
||||
GtkIntegration::Autorestart(GtkIntegration::Type::TDesktop);
|
||||
|
||||
if (const auto integration = BaseGtkIntegration::Instance()) {
|
||||
integration->load();
|
||||
integration->load(GtkIntegration::AllowedBackends());
|
||||
integration->initializeSettings();
|
||||
}
|
||||
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
integration->load();
|
||||
integration->load(GtkIntegration::AllowedBackends());
|
||||
}
|
||||
|
||||
// wait for interface announce to know if native window frame is supported
|
||||
@@ -972,7 +958,6 @@ void start() {
|
||||
integration->waitForInterfaceAnnounce();
|
||||
}
|
||||
|
||||
SetGtkScaleFactor();
|
||||
crl::async(SetDarkMode);
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
@@ -75,14 +75,6 @@ bool finished = true;
|
||||
QMargins simpleMargins, margins;
|
||||
HICON bigIcon = 0, smallIcon = 0, overlayIcon = 0;
|
||||
|
||||
class _PsInitializer {
|
||||
public:
|
||||
_PsInitializer() {
|
||||
Dlls::start();
|
||||
}
|
||||
};
|
||||
_PsInitializer _psInitializer;
|
||||
|
||||
BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) {
|
||||
uint64 &processId(*(uint64*)lParam);
|
||||
|
||||
@@ -104,7 +96,7 @@ BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void psActivateProcess(uint64 pid) {
|
||||
if (pid) {
|
||||
@@ -244,7 +236,6 @@ void start() {
|
||||
} // namespace ThirdParty
|
||||
|
||||
void start() {
|
||||
Dlls::init();
|
||||
}
|
||||
|
||||
void finish() {
|
||||
|
||||
@@ -12,72 +12,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <VersionHelpers.h>
|
||||
#include <QtCore/QSysInfo>
|
||||
|
||||
#include <d3d11.h>
|
||||
|
||||
#define LOAD_SYMBOL(lib, name) ::base::Platform::LoadMethod(lib, #name, name)
|
||||
|
||||
namespace Platform {
|
||||
namespace Dlls {
|
||||
namespace {
|
||||
|
||||
using base::Platform::SafeLoadLibrary;
|
||||
using base::Platform::LoadMethod;
|
||||
struct SafeIniter {
|
||||
SafeIniter();
|
||||
};
|
||||
|
||||
void init() {
|
||||
static bool inited = false;
|
||||
if (inited) return;
|
||||
inited = true;
|
||||
SafeIniter::SafeIniter() {
|
||||
base::Platform::InitDynamicLibraries();
|
||||
|
||||
// Remove the current directory from the DLL search order.
|
||||
::SetDllDirectory(L"");
|
||||
|
||||
const auto list = {
|
||||
u"dbghelp.dll"_q,
|
||||
u"dbgcore.dll"_q,
|
||||
u"propsys.dll"_q,
|
||||
u"winsta.dll"_q,
|
||||
u"textinputframework.dll"_q,
|
||||
u"uxtheme.dll"_q,
|
||||
u"igdumdim32.dll"_q,
|
||||
u"amdhdl32.dll"_q,
|
||||
u"wtsapi32.dll"_q,
|
||||
u"propsys.dll"_q,
|
||||
u"combase.dll"_q,
|
||||
u"dwmapi.dll"_q,
|
||||
u"rstrtmgr.dll"_q,
|
||||
u"psapi.dll"_q,
|
||||
u"user32.dll"_q,
|
||||
u"d3d11.dll"_q,
|
||||
u"dxgi.dll"_q,
|
||||
};
|
||||
for (const auto &lib : list) {
|
||||
SafeLoadLibrary(lib);
|
||||
}
|
||||
}
|
||||
|
||||
// D3D11.DLL
|
||||
|
||||
HRESULT (__stdcall *D3D11CreateDevice)(
|
||||
_In_opt_ IDXGIAdapter* pAdapter,
|
||||
D3D_DRIVER_TYPE DriverType,
|
||||
HMODULE Software,
|
||||
UINT Flags,
|
||||
_In_reads_opt_(FeatureLevels) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
|
||||
UINT FeatureLevels,
|
||||
UINT SDKVersion,
|
||||
_COM_Outptr_opt_ ID3D11Device** ppDevice,
|
||||
_Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
|
||||
_COM_Outptr_opt_ ID3D11DeviceContext** ppImmediateContext);
|
||||
|
||||
// DXGI.DLL
|
||||
|
||||
HRESULT (__stdcall *CreateDXGIFactory1)(
|
||||
REFIID riid,
|
||||
_COM_Outptr_ void **ppFactory);
|
||||
|
||||
void start() {
|
||||
init();
|
||||
|
||||
const auto LibShell32 = SafeLoadLibrary(u"shell32.dll"_q);
|
||||
const auto LibShell32 = LoadLibrary(L"shell32.dll");
|
||||
LOAD_SYMBOL(LibShell32, SHAssocEnumHandlers);
|
||||
LOAD_SYMBOL(LibShell32, SHCreateItemFromParsingName);
|
||||
LOAD_SYMBOL(LibShell32, SHOpenWithDialog);
|
||||
@@ -86,7 +34,7 @@ void start() {
|
||||
LOAD_SYMBOL(LibShell32, SHChangeNotify);
|
||||
LOAD_SYMBOL(LibShell32, SetCurrentProcessExplicitAppUserModelID);
|
||||
|
||||
const auto LibUxTheme = SafeLoadLibrary(u"uxtheme.dll"_q);
|
||||
const auto LibUxTheme = LoadLibrary(L"uxtheme.dll");
|
||||
LOAD_SYMBOL(LibUxTheme, SetWindowTheme);
|
||||
//if (IsWindows10OrGreater()) {
|
||||
// static const auto kSystemVersion = QOperatingSystemVersion::current();
|
||||
@@ -104,66 +52,27 @@ void start() {
|
||||
// }
|
||||
//}
|
||||
|
||||
if (IsWindowsVistaOrGreater()) {
|
||||
const auto LibWtsApi32 = SafeLoadLibrary(u"wtsapi32.dll"_q);
|
||||
LOAD_SYMBOL(LibWtsApi32, WTSRegisterSessionNotification);
|
||||
LOAD_SYMBOL(LibWtsApi32, WTSUnRegisterSessionNotification);
|
||||
const auto LibWtsApi32 = LoadLibrary(L"wtsapi32.dll");
|
||||
LOAD_SYMBOL(LibWtsApi32, WTSRegisterSessionNotification);
|
||||
LOAD_SYMBOL(LibWtsApi32, WTSUnRegisterSessionNotification);
|
||||
|
||||
const auto LibPropSys = SafeLoadLibrary(u"propsys.dll"_q);
|
||||
LOAD_SYMBOL(LibPropSys, PropVariantToString);
|
||||
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
|
||||
const auto LibPropSys = LoadLibrary(L"propsys.dll");
|
||||
LOAD_SYMBOL(LibPropSys, PropVariantToString);
|
||||
LOAD_SYMBOL(LibPropSys, PSStringFromPropertyKey);
|
||||
|
||||
const auto LibDwmApi = SafeLoadLibrary(u"dwmapi.dll"_q);
|
||||
LOAD_SYMBOL(LibDwmApi, DwmIsCompositionEnabled);
|
||||
LOAD_SYMBOL(LibDwmApi, DwmSetWindowAttribute);
|
||||
}
|
||||
const auto LibDwmApi = LoadLibrary(L"dwmapi.dll");
|
||||
LOAD_SYMBOL(LibDwmApi, DwmIsCompositionEnabled);
|
||||
LOAD_SYMBOL(LibDwmApi, DwmSetWindowAttribute);
|
||||
|
||||
const auto LibPsApi = SafeLoadLibrary(u"psapi.dll"_q);
|
||||
const auto LibPsApi = LoadLibrary(L"psapi.dll");
|
||||
LOAD_SYMBOL(LibPsApi, GetProcessMemoryInfo);
|
||||
|
||||
const auto LibUser32 = SafeLoadLibrary(u"user32.dll"_q);
|
||||
const auto LibUser32 = LoadLibrary(L"user32.dll");
|
||||
LOAD_SYMBOL(LibUser32, SetWindowCompositionAttribute);
|
||||
|
||||
const auto LibD3D11 = SafeLoadLibrary(u"d3d11.dll"_q);
|
||||
LOAD_SYMBOL(LibD3D11, D3D11CreateDevice);
|
||||
|
||||
const auto LibDXGI = SafeLoadLibrary(u"dxgi.dll"_q);
|
||||
LOAD_SYMBOL(LibDXGI, CreateDXGIFactory1);
|
||||
}
|
||||
|
||||
SafeIniter kSafeIniter;
|
||||
|
||||
} // namespace
|
||||
} // namespace Dlls
|
||||
} // namespace Platform
|
||||
|
||||
HRESULT WINAPI D3D11CreateDevice(
|
||||
_In_opt_ IDXGIAdapter* pAdapter,
|
||||
D3D_DRIVER_TYPE DriverType,
|
||||
HMODULE Software,
|
||||
UINT Flags,
|
||||
_In_reads_opt_(FeatureLevels) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
|
||||
UINT FeatureLevels,
|
||||
UINT SDKVersion,
|
||||
_COM_Outptr_opt_ ID3D11Device** ppDevice,
|
||||
_Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
|
||||
_COM_Outptr_opt_ ID3D11DeviceContext** ppImmediateContext) {
|
||||
return Platform::Dlls::D3D11CreateDevice
|
||||
? Platform::Dlls::D3D11CreateDevice(
|
||||
pAdapter,
|
||||
DriverType,
|
||||
Software,
|
||||
Flags,
|
||||
pFeatureLevels,
|
||||
FeatureLevels,
|
||||
SDKVersion,
|
||||
ppDevice,
|
||||
pFeatureLevel,
|
||||
ppImmediateContext)
|
||||
: S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT WINAPI CreateDXGIFactory1(
|
||||
REFIID riid,
|
||||
_COM_Outptr_ void **ppFactory) {
|
||||
return Platform::Dlls::CreateDXGIFactory1
|
||||
? Platform::Dlls::CreateDXGIFactory1(riid, ppFactory)
|
||||
: S_FALSE;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Platform {
|
||||
namespace Dlls {
|
||||
|
||||
void init();
|
||||
void start();
|
||||
|
||||
// KERNEL32.DLL
|
||||
inline BOOL(__stdcall *SetDllDirectory)(LPCWSTR lpPathName);
|
||||
|
||||
// UXTHEME.DLL
|
||||
inline HRESULT(__stdcall *SetWindowTheme)(
|
||||
HWND hWnd,
|
||||
|
||||
@@ -14,8 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/gl/gl_detection.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
#include "boxes/about_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -512,6 +514,79 @@ void SetupAnimations(not_null<Ui::VerticalLayout*> container) {
|
||||
}, container->lifetime());
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void SetupANGLE(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
using ANGLE = Ui::GL::ANGLE;
|
||||
const auto options = std::vector{
|
||||
tr::lng_settings_angle_backend_auto(tr::now),
|
||||
tr::lng_settings_angle_backend_d3d11(tr::now),
|
||||
tr::lng_settings_angle_backend_d3d9(tr::now),
|
||||
tr::lng_settings_angle_backend_d3d11on12(tr::now),
|
||||
tr::lng_settings_angle_backend_opengl(tr::now),
|
||||
tr::lng_settings_angle_backend_disabled(tr::now),
|
||||
};
|
||||
const auto backendIndex = [] {
|
||||
if (Core::App().settings().disableOpenGL()) {
|
||||
return 5;
|
||||
} else switch (Ui::GL::CurrentANGLE()) {
|
||||
case ANGLE::Auto: return 0;
|
||||
case ANGLE::D3D11: return 1;
|
||||
case ANGLE::D3D9: return 2;
|
||||
case ANGLE::D3D11on12: return 3;
|
||||
case ANGLE::OpenGL: return 4;
|
||||
}
|
||||
Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
|
||||
}();
|
||||
const auto button = AddButtonWithLabel(
|
||||
container,
|
||||
tr::lng_settings_angle_backend(),
|
||||
rpl::single(options[backendIndex]),
|
||||
st::settingsButton);
|
||||
button->addClickHandler([=] {
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto save = [=](int index) {
|
||||
if (index == backendIndex) {
|
||||
return;
|
||||
}
|
||||
const auto confirmed = crl::guard(button, [=] {
|
||||
const auto nowDisabled = (index == 5);
|
||||
if (!nowDisabled) {
|
||||
Ui::GL::ChangeANGLE([&] {
|
||||
switch (index) {
|
||||
case 0: return ANGLE::Auto;
|
||||
case 1: return ANGLE::D3D11;
|
||||
case 2: return ANGLE::D3D9;
|
||||
case 3: return ANGLE::D3D11on12;
|
||||
case 4: return ANGLE::OpenGL;
|
||||
}
|
||||
Unexpected("Index in SetupANGLE.");
|
||||
}());
|
||||
}
|
||||
const auto wasDisabled = (backendIndex == 5);
|
||||
if (nowDisabled != wasDisabled) {
|
||||
Core::App().settings().setDisableOpenGL(nowDisabled);
|
||||
Local::writeSettings();
|
||||
}
|
||||
App::restart();
|
||||
});
|
||||
controller->show(Box<ConfirmBox>(
|
||||
tr::lng_settings_need_restart(tr::now),
|
||||
tr::lng_settings_restart_now(tr::now),
|
||||
confirmed));
|
||||
};
|
||||
SingleChoiceBox(box, {
|
||||
.title = tr::lng_settings_angle_backend(),
|
||||
.options = options,
|
||||
.initialSelection = backendIndex,
|
||||
.callback = save,
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
void SetupOpenGL(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
@@ -550,9 +625,13 @@ void SetupPerformance(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
SetupAnimations(container);
|
||||
if (!Platform::IsMac()) {
|
||||
#ifdef Q_OS_WIN
|
||||
SetupANGLE(controller, container);
|
||||
#else // Q_OS_WIN
|
||||
if constexpr (!Platform::IsMac()) {
|
||||
SetupOpenGL(controller, container);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
void SetupSystemIntegration(
|
||||
|
||||
@@ -532,28 +532,30 @@ void MainWindow::initSize() {
|
||||
if (position.w > w) position.w = w;
|
||||
if (position.h > h) position.h = h;
|
||||
const auto rightPoint = position.x + position.w;
|
||||
if (rightPoint > w) {
|
||||
const auto distance = rightPoint - w;
|
||||
const auto screenRightPoint = x + w;
|
||||
if (rightPoint > screenRightPoint) {
|
||||
const auto distance = rightPoint - screenRightPoint;
|
||||
const auto newXPos = position.x - distance;
|
||||
if (newXPos >= x) {
|
||||
position.x = newXPos;
|
||||
} else {
|
||||
position.x = x;
|
||||
const auto newRightPoint = position.x + position.w;
|
||||
const auto newDistance = newRightPoint - w;
|
||||
const auto newDistance = newRightPoint - screenRightPoint;
|
||||
position.w -= newDistance;
|
||||
}
|
||||
}
|
||||
const auto bottomPoint = position.y + position.h;
|
||||
if (bottomPoint > h) {
|
||||
const auto distance = bottomPoint - h;
|
||||
const auto screenBottomPoint = y + h;
|
||||
if (bottomPoint > screenBottomPoint) {
|
||||
const auto distance = bottomPoint - screenBottomPoint;
|
||||
const auto newYPos = position.y - distance;
|
||||
if (newYPos >= y) {
|
||||
position.y = newYPos;
|
||||
} else {
|
||||
position.y = y;
|
||||
const auto newBottomPoint = position.y + position.h;
|
||||
const auto newDistance = newBottomPoint - h;
|
||||
const auto newDistance = newBottomPoint - screenBottomPoint;
|
||||
position.h -= newDistance;
|
||||
}
|
||||
}
|
||||
|
||||
1
Telegram/ThirdParty/mallocng
vendored
1
Telegram/ThirdParty/mallocng
vendored
Submodule Telegram/ThirdParty/mallocng deleted from 5413b277e2
1
Telegram/ThirdParty/qt5ct
vendored
1
Telegram/ThirdParty/qt5ct
vendored
Submodule Telegram/ThirdParty/qt5ct deleted from 9f60cd2352
@@ -635,7 +635,6 @@ RUN echo './configure -prefix '$'\"''$QT_PREFIX'$'\"'' \
|
||||
-qt-harfbuzz \
|
||||
-qt-pcre \
|
||||
-no-icu \
|
||||
-no-gtk \
|
||||
-no-feature-xcb-sm \
|
||||
-no-feature-wayland-server \
|
||||
-static \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 2008003
|
||||
AppVersion 2008005
|
||||
AppVersionStrMajor 2.8
|
||||
AppVersionStrSmall 2.8.3
|
||||
AppVersionStr 2.8.3
|
||||
BetaChannel 0
|
||||
AppVersionStrSmall 2.8.5
|
||||
AppVersionStr 2.8.5
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 2.8.3
|
||||
AppVersionOriginal 2.8.5.beta
|
||||
|
||||
@@ -22,7 +22,7 @@ def error(message):
|
||||
print('[ERROR] ' + message)
|
||||
finish(1)
|
||||
|
||||
if sys.platform == 'win32' and not 'COMSPEC' in os.environ:
|
||||
if sys.platform == 'win32' and 'COMSPEC' not in os.environ:
|
||||
error('COMSPEC environment variable is not set.')
|
||||
|
||||
executePath = os.getcwd()
|
||||
@@ -39,9 +39,9 @@ if os.path.isfile(officialTargetFile):
|
||||
officialTarget = line.strip()
|
||||
|
||||
arch = ''
|
||||
if officialTarget == 'win' or officialTarget == 'uwp':
|
||||
if officialTarget in ['win', 'uwp']:
|
||||
arch = 'x86'
|
||||
elif officialTarget == 'win64' or officialTarget == 'uwp64':
|
||||
elif officialTarget in ['win64', 'uwp64']:
|
||||
arch = 'x64'
|
||||
|
||||
if officialTarget != '':
|
||||
|
||||
Submodule Telegram/lib_base updated: 01d152af4c...f493a8174f
Submodule Telegram/lib_ui updated: e73e1a6c0f...3b3413e618
Submodule Telegram/lib_webview updated: eb812476ab...e7caab807b
@@ -1,3 +1,12 @@
|
||||
2.8.5 beta (02.07.21)
|
||||
|
||||
- Use ANGLE for OpenGL over DirectX 9 / DirectX 11 (Windows).
|
||||
- Use GTK from a child process (Linux).
|
||||
|
||||
2.8.4 (01.07.21)
|
||||
|
||||
- Crash fixes in WebView on Windows.
|
||||
|
||||
2.8.3 (28.06.21)
|
||||
|
||||
- Fix crashes in OpenSSL on macOS.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: 1d59503a24...8432257651
@@ -172,6 +172,21 @@ Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
|
||||
SET PATH=%PATH_BACKUP_%
|
||||
cd ..
|
||||
|
||||
git clone https://chromium.googlesource.com/angle/angle
|
||||
cd angle
|
||||
git checkout chromium/4472
|
||||
python scripts/bootstrap.py
|
||||
gclient sync
|
||||
|
||||
git apply ../patches/angle.patch
|
||||
|
||||
gn gen out/Debug --args="is_component_build = false is_debug = true target_cpu = \"x64\" is_clang = false enable_iterator_debugging = true angle_enable_swiftshader=false angle_enable_vulkan=false"
|
||||
|
||||
gn gen out/Release --args="is_component_build = false is_debug = false target_cpu = \"x64\" is_clang = false enable_iterator_debugging = false angle_enable_swiftshader=false angle_enable_vulkan=false"
|
||||
|
||||
ninja -C out/Debug libANGLE_static libGLESv2_static libEGL_static
|
||||
ninja -C out/Release libANGLE_static libGLESv2_static libEGL_static
|
||||
|
||||
SET LibrariesPath=%cd%
|
||||
git clone git://code.qt.io/qt/qt5.git qt_5_15_2
|
||||
cd qt_5_15_2
|
||||
@@ -190,7 +205,16 @@ Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
|
||||
-confirm-license ^
|
||||
-static ^
|
||||
-static-runtime ^
|
||||
-opengl dynamic ^
|
||||
-opengl es2 -no-angle ^
|
||||
-I "%LibrariesPath%\angle\include" ^
|
||||
-D "GL_APICALL=" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_DEBUG="%LibrariesPath%\angle\out\Debug\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Debug\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_RELEASE="%LibrariesPath%\angle\out\Release\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Release\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
-egl ^
|
||||
-D "EGLAPI=" ^
|
||||
-D "DESKTOP_APP_QT_STATIC_ANGLE=" ^
|
||||
QMAKE_LIBS_EGL_DEBUG="%LibrariesPath%\angle\out\Debug\obj\libEGL_static.lib %LibrariesPath%\angle\out\Debug\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Debug\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
QMAKE_LIBS_EGL_RELEASE="%LibrariesPath%\angle\out\Release\obj\libEGL_static.lib %LibrariesPath%\angle\out\Release\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Release\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
-openssl-linked ^
|
||||
-I "%LibrariesPath%\openssl_1_1_1\include" ^
|
||||
OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out64.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out64.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^
|
||||
|
||||
@@ -172,6 +172,21 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
|
||||
SET PATH=%PATH_BACKUP_%
|
||||
cd ..
|
||||
|
||||
git clone https://chromium.googlesource.com/angle/angle
|
||||
cd angle
|
||||
git checkout chromium/4472
|
||||
python scripts/bootstrap.py
|
||||
gclient sync
|
||||
|
||||
git apply ../patches/angle.patch
|
||||
|
||||
gn gen out/Debug --args="is_component_build = false is_debug = true target_cpu = \"x86\" is_clang = false enable_iterator_debugging = true angle_enable_swiftshader=false angle_enable_vulkan=false"
|
||||
|
||||
gn gen out/Release --args="is_component_build = false is_debug = false target_cpu = \"x86\" is_clang = false enable_iterator_debugging = false angle_enable_swiftshader=false angle_enable_vulkan=false"
|
||||
|
||||
ninja -C out/Debug libANGLE_static libGLESv2_static libEGL_static
|
||||
ninja -C out/Release libANGLE_static libGLESv2_static libEGL_static
|
||||
|
||||
SET LibrariesPath=%cd%
|
||||
git clone git://code.qt.io/qt/qt5.git qt_5_15_2
|
||||
cd qt_5_15_2
|
||||
@@ -190,7 +205,16 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath***
|
||||
-confirm-license ^
|
||||
-static ^
|
||||
-static-runtime ^
|
||||
-opengl dynamic ^
|
||||
-opengl es2 -no-angle ^
|
||||
-I "%LibrariesPath%\angle\include" ^
|
||||
-D "GL_APICALL=" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_DEBUG="%LibrariesPath%\angle\out\Debug\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Debug\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
QMAKE_LIBS_OPENGL_ES2_RELEASE="%LibrariesPath%\angle\out\Release\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Release\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib" ^
|
||||
-egl ^
|
||||
-D "EGLAPI=" ^
|
||||
-D "DESKTOP_APP_QT_STATIC_ANGLE=" ^
|
||||
QMAKE_LIBS_EGL_DEBUG="%LibrariesPath%\angle\out\Debug\obj\libEGL_static.lib %LibrariesPath%\angle\out\Debug\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Debug\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
QMAKE_LIBS_EGL_RELEASE="%LibrariesPath%\angle\out\Release\obj\libEGL_static.lib %LibrariesPath%\angle\out\Release\obj\libGLESv2_static.lib %LibrariesPath%\angle\out\Release\obj\libANGLE_static.lib d3d9.lib dxgi.lib dxguid.lib Gdi32.lib User32.lib" ^
|
||||
-openssl-linked ^
|
||||
-I "%LibrariesPath%\openssl_1_1_1\include" ^
|
||||
OPENSSL_LIBS_DEBUG="%LibrariesPath%\openssl_1_1_1\out32.dbg\libssl.lib %LibrariesPath%\openssl_1_1_1\out32.dbg\libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib" ^
|
||||
|
||||
@@ -117,7 +117,6 @@ parts:
|
||||
- -DTDESKTOP_API_ID=611335
|
||||
- -DTDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c
|
||||
- -DDESKTOP_APP_USE_PACKAGED_LAZY=ON
|
||||
- -DDESKTOP_APP_USE_PACKAGED_LAZY_PLATFORMTHEMES=OFF
|
||||
- -DTDESKTOP_LAUNCHER_BASENAME=telegram-desktop_telegram-desktop
|
||||
override-pull: |
|
||||
snapcraftctl pull
|
||||
@@ -303,6 +302,7 @@ parts:
|
||||
- libfreetype-dev
|
||||
- libgl-dev
|
||||
- libglib2.0-dev
|
||||
- libgtk-3-dev
|
||||
- libharfbuzz-dev
|
||||
- libicu-dev
|
||||
- libpcre2-dev
|
||||
@@ -338,6 +338,7 @@ parts:
|
||||
- libfreetype6
|
||||
- libgl1
|
||||
- libglib2.0-0
|
||||
- libgtk-3-0
|
||||
- libharfbuzz0b
|
||||
- libicu66
|
||||
- libpcre2-16-0
|
||||
@@ -398,7 +399,6 @@ parts:
|
||||
-release \
|
||||
-opensource \
|
||||
-confirm-license \
|
||||
-no-gtk \
|
||||
-no-feature-xcb-sm \
|
||||
-no-feature-xcomposite-egl \
|
||||
-no-feature-xcomposite-glx \
|
||||
@@ -420,6 +420,8 @@ parts:
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/qt5/bin
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/qt5/mkspecs
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/qt5/examples
|
||||
# Allow tdesktop's custom try-portal-and-fallback logic to work
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/qt5/plugins/platformthemes/libqxdgdesktopportal.so
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/*.a
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/*.la
|
||||
- -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/*.prl
|
||||
|
||||
Reference in New Issue
Block a user